/*
* Copyright (C) 2014 University of Dundee & Open Microscopy Environment.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ome.formats.importer.transfers;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import ome.formats.importer.ImportEvent;
import ome.formats.importer.ImportLibrary;
import ome.formats.importer.util.TimeEstimator;
import ome.util.checksum.ChecksumProvider;
import omero.ServerError;
import omero.api.RawFileStorePrx;
import omero.grid.ImportProcessPrx;
import omero.model.OriginalFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Non-thread-safe argument holder for {@link FileTransfer} implementations.
* A single instance will be created per invocation of
* {@link FileTransfer#transfer(TransferState)}. Several instance methods are
* provided for common reporting actions (See usage in existing
* {@link FileTransfer} implementations.
*
* @since 5.0
*/
public class TransferState implements TimeEstimator {
private static final Logger log = LoggerFactory.getLogger(TransferState.class);
private final File file;
private final long length;
private final int index;
private final int total;
private final ImportProcessPrx proc;
private final ImportLibrary library;
private final TimeEstimator estimator;
private final ChecksumProvider cp;
private final byte[] buf;
private OriginalFile ofile;
private String checksum;
/**
* Cache of the latest return value from
* {@link #getUploader(String)} which can be used to cleanup
* server state.
*/
private RawFileStorePrx prx;
/**
* State of the current file transfer.
*
* @param file Source file which is to be transferred.
* @param index Which of the total files to upload this is.
* @param total Total number of files to upload.
* @param proc {@link ImportProcessPrx} which is being imported to.
* @param library {@link ImportLibrary} to use for notifications.
* @param estimator a time-to-completion estimator.
* @param cp a checksum provider, for calculating file content checksums.
* @param buf optional buffer. Need not be used or updated.
* @throws IOException I/O exception
* @throws ServerError server error
*/
public TransferState(File file,
int index, int total, // as index of
ImportProcessPrx proc, // to
ImportLibrary library,
TimeEstimator estimator,
ChecksumProvider cp,
byte[] buf) throws IOException, ServerError {
this.file = file;
this.length = file.length();
this.index = index;
this.total = total;
this.proc = proc;
this.library = library;
this.estimator = estimator;
this.cp = cp;
this.buf = buf;
}
/**
* Calls {@link RawFileStorePrx#save()} and stores the resultant
* {@link OriginalFile} for future inspection along with the <em>local</em>
* checksum. (The remote checksum is available from the
* {@link OriginalFile}.
*
* @throws ServerError server error
*/
public void save() throws ServerError {
// We don't need write access here, and considering that
// a symlink or similar to a non-executable file may have
// replaced the previous test file (see checkLocation), we
// try to be as conservative as possible.
RawFileStorePrx rawFileStore = getUploader("r");
checksum = cp.checksumAsString();
ofile = rawFileStore.save();
if (log.isDebugEnabled()) {
log.debug(String.format("%s/%s id=%s",
ofile.getPath().getValue(),
ofile.getName().getValue(),
ofile.getId().getValue()));
log.debug(String.format("checksums: client=%s,server=%s",
checksum, ofile.getHash().getValue()));
}
}
//
// ACCESSORS
//
/**
* <em>(Not thread safe)</em> Get a moderately large buffer for use in
* reading/writing data. To prevent the creation of many MB-sized byte
* arrays, this value can be re-used but requires external synchronization.
* @return the buffer
*/
public byte[] getBuffer() {
return this.buf;
}
/**
* Get the digest string for the local file. This will only be available,
* i.e. non-null, after {@link #save()} has been called.
* @return the checksum
*/
public String getChecksum() {
return this.checksum;
}
/**
* Get the {@link ChecksumProvider} passed to the constructor.
* Since the {@link ChecksumProvider} has a number of different usage styles,
* {@link TransferState} doesn't attempt to delegate but just returns the
* instance.
* @return the checksum provider used for calculating the checksum
*/
public ChecksumProvider getChecksumProvider() {
return this.cp;
}
/**
* Return the target file passed to the constructor.
* @return the source file
*/
public File getFile() {
return this.file;
}
/**
* Return the length of the {@link #getFile() target file}.
* @return the length of the source file
*/
public long getLength() {
return this.length;
}
/**
* Find original file as defined by the ID in the {@link RawFileStorePrx}
* regardless of group.
* @return the original file from the upload process
* @throws ServerError server error
*/
public OriginalFile getOriginalFile() throws ServerError {
return library.loadOriginalFile(getUploader());
}
/**
* Find original file represented by the managed repository that
* import is taking place to.
* @return the original file at the root of the repository targeted by the upload process
* @throws ServerError server error
*/
public OriginalFile getRootFile() throws ServerError {
return library.lookupManagedRepository().root();
}
/**
* @return the {@link RawFileStorePrx} instance for this index
* @throws ServerError server error
*/
public RawFileStorePrx getUploader() throws ServerError {
return getUploader(null);
}
/**
* Return the {@link RawFileStorePrx} instance for this index setting
* the mode if not null. Valid values include "r" and "rw". If a non-null
* uploader is available, it will be returned <em>instead</em>.
*
* <em>Every</em> instance which is returned from this method should
* eventually have {@link RawFileStorePrx#close()} called on it.
* {@link RawFileStorePrx#close()} can be used to facilitate this.
* @param mode the mode as understood by
* {@link ome.services.blitz.repo.PublicRepositoryI#file(String, String, Ice.Current)}
* @return the {@link RawFileStorePrx} instance
* @throws ServerError server error
*/
public RawFileStorePrx getUploader(String mode) throws ServerError {
if (prx != null) {
return prx;
} else if (mode != null) {
Map<String, String> ctx = new HashMap<String, String>();
ctx.put("omero.fs.mode", mode);
prx = this.proc.getUploader(this.index, ctx);
} else {
prx = this.proc.getUploader(this.index);
}
return prx;
}
/**
* Call {@link RawFileStorePrx#close()} on the cached uploader
* instance if non-null and null the instance. If
* {@link Ice.ObjectNotExistException} is thrown, the service is
* assumed closed. All other exceptions will be printed at WARN.
*/
public void closeUploader() {
if (prx != null) {
try {
prx.close();
} catch (Ice.ObjectNotExistException onee) {
// no-op
} catch (Exception e) {
log.warn("Exception closing " + prx, e);
} finally {
prx = null;
}
}
}
//
// NOTIFICATIONS AND LOGGING
//
/**
* Raise the {@link ome.formats.importer.ImportEvent.FILE_UPLOAD_STARTED}
* event to all observers.
*/
public void uploadStarted() {
library.notifyObservers(
new ImportEvent.FILE_UPLOAD_STARTED(
file.getAbsolutePath(), index, total,
null, length, null));
}
/**
* Raise the {@link ome.formats.importer.ImportEvent.FILE_UPLOAD_BYTES}
* event to all observers.
* @param offset how many bytes are uploaded
*/
public void uploadBytes(long offset) {
library.notifyObservers(
new ImportEvent.FILE_UPLOAD_BYTES(
file.getAbsolutePath(), index, total,
offset, length, estimator.getUploadTimeLeft(), null));
}
/**
* Raise the {@link ome.formats.importer.ImportEvent.FILE_UPLOAD_COMPLETE}
* event to all observers.
* @param offset how many bytes are uploaded
*/
public void uploadComplete(long offset) {
library.notifyObservers(new ImportEvent.FILE_UPLOAD_COMPLETE(
file.getAbsolutePath(), index, total,
offset, length, null));
}
//
// ESTIMATOR DELEGATION
//
public void start() {
this.estimator.start();
}
public void stop() {
this.estimator.stop();
}
public void stop(long uploadedBytes) {
this.estimator.stop(uploadedBytes);
}
public long getUploadTimeLeft() {
return this.estimator.getUploadTimeLeft();
}
}