/*******************************************************************************
* Copyright (c) 2011, 2016 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.filesystem.core.internal.operations;
import static java.text.MessageFormat.format;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.DoneOpen;
import org.eclipse.tcf.services.IFileSystem.DoneStat;
import org.eclipse.tcf.services.IFileSystem.FileAttrs;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
import org.eclipse.tcf.te.tcf.core.concurrent.TCFOperationMonitor;
import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.IConfirmCallback;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.FSTreeNode;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.utils.FileState;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.utils.PersistenceManager;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.utils.StatusHelper;
import org.eclipse.tcf.te.tcf.filesystem.core.nls.Messages;
import org.eclipse.tcf.util.TCFFileOutputStream;
/**
* Upload multiple files from local system to a remote system.
*/
public class OpUpload extends AbstractOperation {
private static class WorkItem {
final File fSource;
final FSTreeNode fDestination;
final boolean fDropToDestination;
WorkItem(File source, FSTreeNode destination, boolean isDrop) {
fSource = source;
fDestination = destination;
fDropToDestination = isDrop;
}
}
IConfirmCallback fConfirmCallback;
LinkedList<WorkItem> fWork = new LinkedList<WorkItem>();
private long fStartTime;
public OpUpload(IConfirmCallback confirm) {
fConfirmCallback = confirm;
}
public void addUpload(File source, FSTreeNode destinationFile) {
fWork.add(new WorkItem(source, destinationFile, false));
}
public void addDrop(File source, FSTreeNode destiniationFolder) {
fWork.add(new WorkItem(source, destiniationFolder, true));
}
@Override
public IStatus doRun(IProgressMonitor monitor) {
fStartTime = System.currentTimeMillis();
monitor.beginTask(getName(), IProgressMonitor.UNKNOWN);
while (!fWork.isEmpty()) {
IStatus s = runWorkItem(fWork.remove(), monitor);
if (!s.isOK())
return s;
}
return Status.OK_STATUS;
}
protected IStatus runWorkItem(final WorkItem item, IProgressMonitor monitor) {
final String path;
final FSTreeNode destination = item.fDestination;
FSTreeNode existing;
final String name;
final File source = item.fSource;
if (item.fDropToDestination) {
IStatus status = refresh(destination, fStartTime, monitor);
if (!status.isOK())
return status;
name = item.fSource.getName();
existing = destination.findChild(name);
if (source.isDirectory()) {
if (existing != null) {
if (!existing.isDirectory()) {
return StatusHelper.createStatus(format(Messages.OpCopy_error_noDirectory, existing.getLocation()), null);
}
int replace = confirmCallback(existing, fConfirmCallback);
if (replace == IConfirmCallback.NO) {
return Status.OK_STATUS;
}
if (replace != IConfirmCallback.YES) {
return Status.CANCEL_STATUS;
}
} else {
status = destination.operationNewFolder(name).run(new SubProgressMonitor(monitor, 0));
if (!status.isOK())
return status;
existing = destination.findChild(name);
}
for (File child : source.listFiles()) {
fWork.addFirst(new WorkItem(child, existing, true));
}
return Status.OK_STATUS;
} else if (source.isFile()) {
if (existing != null) {
if (!existing.isFile()) {
return StatusHelper.createStatus(format(Messages.OpCopy_error_noFile, existing.getLocation()), null);
}
int replace = confirmCallback(existing, fConfirmCallback);
if (replace == IConfirmCallback.NO) {
return Status.OK_STATUS;
}
if (replace != IConfirmCallback.YES) {
return Status.CANCEL_STATUS;
}
}
path = getPath(destination, name);
} else {
return Status.OK_STATUS;
}
} else {
name = destination.getName();
existing = destination;
path = destination.getLocation(true);
}
final TCFOperationMonitor<OutputStream> result = new TCFOperationMonitor<OutputStream>();
monitor.subTask(NLS.bind(Messages.OpUpload_UploadSingleFile, item.fSource));
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
IFileSystem fs = destination.getRuntimeModel().getFileSystem();
if (fs == null) {
result.setCancelled();
} else {
tcfGetOutputStream(fs, path, result);
}
}
});
IStatus status = result.waitDone(monitor);
if (!status.isOK())
return status;
OutputStream out = new BufferedOutputStream(result.getValue());
try {
IStatus s = uploadFile(item.fSource, existing, out, new SubProgressMonitor(monitor, 0));
if (!s.isOK())
return s;
} finally {
try {
out.close();
} catch (IOException e) {
}
}
return updateNode(path, name, destination, existing, monitor);
}
private IStatus updateNode(final String path, final String name,
final FSTreeNode destination, final FSTreeNode existing, IProgressMonitor monitor) {
final TCFOperationMonitor<?> r2 = new TCFOperationMonitor<Object>();
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
IFileSystem fs = destination.getRuntimeModel().getFileSystem();
if (fs == null) {
r2.setCancelled();
} else if (!r2.checkCancelled()) {
fs.stat(path, new DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
r2.setError(format(Messages.OpUpload_error_upload, name), error);
} else if (!r2.checkCancelled()) {
if (existing != null) {
existing.setAttributes(attrs, true);
} else {
destination.addNode(new FSTreeNode(destination, name, false, attrs), true);
}
r2.setDone(null);
}
}
});
}
}
});
return r2.waitDone(monitor);
}
protected void tcfGetOutputStream(IFileSystem fileSystem, final String path, final TCFOperationMonitor<OutputStream> result) {
int flags = IFileSystem.TCF_O_WRITE | IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_TRUNC;
if (!result.checkCancelled()) {
fileSystem.open(path, flags, null, new DoneOpen() {
@Override
public void doneOpen(IToken token, FileSystemException error, IFileHandle handle) {
if (error != null) {
result.setError(StatusHelper.createStatus(format(Messages.OpUpload_error_openFile, path), error));
} else {
result.setDone(new TCFFileOutputStream(handle));
}
}
});
}
}
private IStatus uploadFile(File source, FSTreeNode existing, OutputStream output, IProgressMonitor monitor) {
byte[] data = new byte[DEFAULT_CHUNK_SIZE];
// Calculate the total size.
long totalSize = source.length();
// Calculate the chunk size of one percent.
int chunk_size = (int) totalSize / 100;
// The current reading percentage.
int percentRead = 0;
// The current length of read bytes.
long bytesRead = 0;
MessageDigest digest = null;
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(source));
if (existing != null) {
try {
digest = MessageDigest.getInstance(MD_ALG);
input = new DigestInputStream(input, digest);
} catch (NoSuchAlgorithmException e) {
}
}
// Total size displayed on the progress dialog.
String fileLength = formatSize(totalSize);
int length;
while ((length = input.read(data)) >= 0) {
output.write(data, 0, length);
bytesRead += length;
if (chunk_size != 0) {
int percent = (int) bytesRead / chunk_size;
if (percent != percentRead) { // Update the progress.
monitor.worked(percent - percentRead);
percentRead = percent; // Remember the percentage.
// Report the progress.
if (fWork.size() == 0)
monitor.subTask(NLS.bind(Messages.OpUpload_UploadingProgress, new Object[]{source.getName(), formatSize(bytesRead), fileLength}));
}
}
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
}
if (digest != null && existing != null) {
statFile(existing, monitor);
FileState filedigest = PersistenceManager.getInstance().getFileDigest(existing);
filedigest.reset(digest.digest(), existing.getCacheFile().lastModified(), existing.getModificationTime());
}
return Status.OK_STATUS;
} catch (IOException e) {
return StatusHelper.createStatus(format(Messages.OpUpload_error_upload, source), e);
} finally {
if (input != null) {
try {
input.close();
} catch (Exception e) {
}
}
}
}
private void statFile(final FSTreeNode node, IProgressMonitor monitor) {
final TCFOperationMonitor<?> result = new TCFOperationMonitor<Object>();
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
tcfStat(node, result);
}
});
result.waitDone(monitor);
}
protected void tcfStat(final FSTreeNode node, final TCFOperationMonitor<?> result) {
if (!result.checkCancelled()) {
final IFileSystem fs = node.getRuntimeModel().getFileSystem();
if (fs == null) {
result.setCancelled();
return;
}
fs.stat(node.getLocation(true), new DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
handleFSError(node, Messages.OpRefresh_errorReadAttributes, error, result);
} else {
node.setAttributes(attrs, false);
result.setDone(null);
}
}
});
}
}
@Override
public String getName() {
String message;
if(fWork.size()==1)
message = NLS.bind(Messages.OpUpload_UploadSingleFile, fWork.element().fSource);
else
message = NLS.bind(Messages.OpUpload_UploadNFiles, Long.valueOf(fWork.size()));
return message;
}
}