/******************************************************************************* * Copyright (c) 2012, 2015 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 * Anna Dushistova(Montavista) - [386484]Allow file transfer from target to host into existing directories * Anna Dushistova(Montavista) - [387819]File Transfer stopped working *******************************************************************************/ package org.eclipse.tcf.te.tcf.filesystem.core.services; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.IToken; import org.eclipse.tcf.services.IFileSystem; 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.runtime.interfaces.callback.ICallback; import org.eclipse.tcf.te.runtime.services.interfaces.filetransfer.IFileTransferItem; import org.eclipse.tcf.te.runtime.utils.ProgressHelper; import org.eclipse.tcf.te.runtime.utils.StatusHelper; import org.eclipse.tcf.te.tcf.core.Tcf; import org.eclipse.tcf.te.tcf.filesystem.core.internal.exceptions.TCFChannelException; import org.eclipse.tcf.te.tcf.filesystem.core.nls.Messages; import org.eclipse.tcf.util.TCFFileInputStream; import org.eclipse.tcf.util.TCFFileOutputStream; /** * TCF file transfer service. */ public class FileTransferService { /** * Returns the target path file attribute. * * @param peer The peer, must not be <code>null</code>. * @param channel The channel or <code>null</code>. * @param item The file transfer item, must not be <code>null</code>. * * @return The target path file attributes or <code>null</code>. */ public static FileAttrs getRemoteFileAttrs(IPeer peer, IChannel channel, IFileTransferItem item) { final AtomicReference<FileAttrs> attrs = new AtomicReference<FileAttrs>(); boolean ownChannel = false; IFileSystem fileSystem; try { if (channel == null) { ownChannel = true; channel = Operation.openChannel(peer); } fileSystem = Operation.getBlockingFileSystem(channel); Assert.isNotNull(fileSystem); String targetPath = item.getTargetPathString(); if (targetPath != null) { final AtomicReference<FileSystemException> error = new AtomicReference<FileSystemException>(); fileSystem.stat(targetPath.toString(), new IFileSystem.DoneStat() { @Override public void doneStat(IToken token, FileSystemException e, FileAttrs a) { error.set(e); attrs.set(e == null ? a : null); } }); } if (ownChannel) { closeChannel(peer, channel); } } catch (Exception e) { attrs.set(null); } return attrs.get(); } /** * Transfer a file between host and target depending on the {@link IFileTransferItem} data. * * @param peer The peer, must not be <code>null</code>. * @param channel The channel or <code>null</code>. * @param item The file transfer item, must not be <code>null</code>. * @param monitor The progress monitor or <code>null</code>. * @param callback The callback or <code>null</code>. */ public static void transfer(IPeer peer, IChannel channel, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) { boolean ownChannel = false; IFileSystem fileSystem; try { if (channel == null) { ownChannel = true; channel = Operation.openChannel(peer); } fileSystem = Operation.getBlockingFileSystem(channel); Assert.isNotNull(fileSystem); // Check the direction of the transfer if (item.getDirection() == IFileTransferItem.TARGET_TO_HOST) { transferToHost(peer, fileSystem, item, monitor, callback); } else { transferToTarget(peer, fileSystem, item, monitor, callback); } if (ownChannel) { closeChannel(peer, channel); } } catch (Exception e) { if (callback != null) { callback.done(peer, StatusHelper.getStatus(e)); } } } /** * Transfer a file between host and target depending on the {@link IFileTransferItem} data. * * @param peer The peer, must not be <code>null</code>. * @param item The file transfer item, must not be <code>null</code>. * @param monitor The progress monitor or <code>null</code>. * @param callback The callback or <code>null</code>. */ protected static void transfer(IPeer peer, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) { // Check if we can skip the transfer if (!item.isEnabled()) { if (callback != null) { callback.done(peer, Status.OK_STATUS); } return; } try { IChannel channel = Operation.openChannel(peer); transfer(peer, channel, item, monitor, callback); closeChannel(peer, channel); } catch (Exception e) { if (callback != null) { callback.done(peer, StatusHelper.getStatus(e)); } } } protected static void transferToHost(IPeer peer, IFileSystem fileSystem, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) { IStatus result = Status.OK_STATUS; IPath hostPath = item.getHostPath(); String targetPath = item.getTargetPathString(); BufferedOutputStream outStream = null; TCFFileInputStream inStream = null; final IFileSystem.IFileHandle[] handle = new IFileSystem.IFileHandle[1]; final FileSystemException[] error = new FileSystemException[1]; final IFileSystem.FileAttrs[] attrs = new IFileSystem.FileAttrs[1]; // Create necessary parent directory structure on host side boolean rc = hostPath.removeLastSegments(1).toFile().exists(); if(!rc){ rc = hostPath.removeLastSegments(1).toFile().mkdirs(); if (!rc) { IOException e = new IOException(NLS.bind(Messages.FileTransferService_error_mkdirFailed, hostPath.removeLastSegments(1).toOSString())); result = StatusHelper.getStatus(e); if (callback != null) callback.done(peer, result); return; } } // If the host file is a directory, append the remote file name if (hostPath.toFile().isDirectory()) { hostPath = item.getHostPath().append(lastSegment(targetPath)); } // Remember the modification time of the remote file. // We need this value to set the modification time of the host file // _after_ the stream closed. long mtime = -1; try { // Open the remote file fileSystem.open(targetPath, IFileSystem.TCF_O_READ, null, new IFileSystem.DoneOpen() { @Override public void doneOpen(IToken token, FileSystemException e, IFileHandle h) { error[0] = e; handle[0] = h; } }); if (error[0] != null) { throw error[0]; } // Get the remote file attributes fileSystem.fstat(handle[0], new IFileSystem.DoneStat() { @Override public void doneStat(IToken token, FileSystemException e, FileAttrs a) { error[0] = e; attrs[0] = a; } }); if (error[0] != null) { throw error[0]; } // Remember the modification time mtime = attrs[0].mtime; // Open a output stream to the host file outStream = new BufferedOutputStream(new FileOutputStream(hostPath.toFile())); // And open the input stream to the target file handle inStream = new TCFFileInputStream(handle[0]); ProgressHelper.setSubTaskName(monitor, "Transfer '" + targetPath.toString() + "' to '" + hostPath.toOSString() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ long bytesTotal = attrs[0].size; copy(inStream, outStream, bytesTotal, monitor); } catch (OperationCanceledException e) { result = Status.CANCEL_STATUS; } catch (Exception e) { result = StatusHelper.getStatus(e); } finally { // Close all streams and cleanup if (outStream != null) { try { outStream.close(); outStream = null; } catch (IOException e) { } } if (inStream != null) { try { inStream.close(); inStream = null; } catch (IOException e) { } } if (result.isOK()) { if (mtime >= 0) { rc = hostPath.toFile().setLastModified(mtime); if (!rc && Platform.inDebugMode()) { System.err.println("Failed to set mtime for " + hostPath.toOSString()); //$NON-NLS-1$ } } } else if (result.getSeverity() == IStatus.ERROR || result.getSeverity() == IStatus.CANCEL) { try { rc = hostPath.toFile().delete(); if (!rc && Platform.inDebugMode()) { System.err.println("Failed to delete host file " + hostPath.toOSString()); //$NON-NLS-1$ } } catch (Throwable e) { } } } if (callback != null) callback.done(peer, result); } private static String lastSegment(String targetPath) { int idx = targetPath.lastIndexOf('/'); if (idx > 0) return targetPath.substring(idx+1); return targetPath; } protected static void transferToTarget(IPeer peer, IFileSystem fileSystem, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) { IStatus result = Status.OK_STATUS; String targetPath = item.getTargetPathString(); IPath hostPath = item.getHostPath(); BufferedInputStream inStream = null; TCFFileOutputStream outStream = null; final IFileSystem.IFileHandle[] handle = new IFileSystem.IFileHandle[1]; final FileSystemException[] error = new FileSystemException[1]; final FileAttrs[] attrs = new FileAttrs[1]; // Read the attributes of the target error[0] = null; attrs[0] = null; fileSystem.stat(targetPath, new IFileSystem.DoneStat() { @Override public void doneStat(IToken token, FileSystemException e, FileAttrs a) { error[0] = e; attrs[0] = a; } }); // If we get the attributes back, the name at least exist in the target file system if (attrs[0] != null) { if (attrs[0].isDirectory()) { targetPath = targetPath + '/' + item.getHostPath().lastSegment(); } } else { // Try to create the parent directory for (int i = targetPath.indexOf('/'); i>=0; i = targetPath.indexOf('/', i+1)) { if (i > 0) { String path = targetPath.substring(0, i); error[0] = null; attrs[0] = null; fileSystem.stat(path, new IFileSystem.DoneStat() { @Override public void doneStat(IToken token, FileSystemException e, FileAttrs a) { error[0] = e; attrs[0] = a; } }); if (attrs[0] == null) { error[0] = null; attrs[0] = null; fileSystem.mkdir(path, null, new IFileSystem.DoneMkDir() { @Override public void doneMkDir(IToken token, FileSystemException e) { error[0] = e; } }); if (error[0] != null) { result = StatusHelper.getStatus(error[0]); if (callback != null) callback.done(peer, result); return; } } } } } try { // Open the remote file fileSystem.open(targetPath, IFileSystem.TCF_O_CREAT | IFileSystem.TCF_O_WRITE | IFileSystem.TCF_O_TRUNC, null, new IFileSystem.DoneOpen() { @Override public void doneOpen(IToken token, FileSystemException e, IFileHandle h) { error[0] = e; handle[0] = h; } }); if (error[0] != null) { throw error[0]; } // Open a input stream from the host file inStream = new BufferedInputStream(new FileInputStream(hostPath.toFile())); // Open the output stream for the target file handle outStream = new TCFFileOutputStream(handle[0]); ProgressHelper.setSubTaskName(monitor, "Transfer '" + hostPath.toOSString() + "' to '" + targetPath.toString() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ copy(inStream, outStream, hostPath.toFile().length(), monitor); // Get the remote file attributes fileSystem.fstat(handle[0], new IFileSystem.DoneStat() { @Override public void doneStat(IToken token, FileSystemException e, FileAttrs a) { error[0] = e; attrs[0] = a; } }); // Update the remote file attributes IFileSystem.FileAttrs newAttrs = new FileAttrs(attrs[0].flags, attrs[0].size, attrs[0].uid, attrs[0].gid, attrs[0].permissions, attrs[0].atime, hostPath.toFile().lastModified(), attrs[0].attributes); // Set the remote file attributes fileSystem.fsetstat(handle[0], newAttrs, new IFileSystem.DoneSetStat() { @Override public void doneSetStat(IToken token, FileSystemException e) { error[0] = e; } }); } catch (OperationCanceledException e) { result = Status.CANCEL_STATUS; } catch (Exception e) { result = StatusHelper.getStatus(e); } finally { // Close all streams and cleanup if (outStream != null) { try { outStream.close(); outStream = null; } catch (IOException e) { } } if (inStream != null) { try { inStream.close(); inStream = null; } catch (IOException e) { } } if (result.getSeverity() == IStatus.ERROR || result.getSeverity() == IStatus.CANCEL) { fileSystem.remove(targetPath.toString(), new IFileSystem.DoneRemove() { @Override public void doneRemove(IToken token, FileSystemException error) { } }); } } if (callback != null) callback.done(peer, result); } private static void copy(InputStream in, OutputStream out, long bytesTotal, IProgressMonitor monitor) throws IOException { long bytesDone = 0; long speed; long startTimeStamp = System.currentTimeMillis(); byte[] dataBuffer = new byte[12 * 1024]; // Copy from the input stream to the output stream (always binary). while (true) { if (ProgressHelper.isCanceled(monitor)) { throw new OperationCanceledException(); } // Read the data from the remote file int bytesRead = in.read(dataBuffer); // If reached EOF, we are done and break the loop if (bytesRead < 0) { break; } if (ProgressHelper.isCanceled(monitor)) { throw new OperationCanceledException(); } // Write back to the host file out.write(dataBuffer, 0, bytesRead); bytesDone += bytesRead; long timestamp = System.currentTimeMillis(); speed = ((bytesDone) * 1000) / Math.max(timestamp - startTimeStamp, 1); ProgressHelper.worked(monitor, (int)((bytesRead/(bytesTotal > 0 ? bytesTotal : 1000)) * 1000)); ProgressHelper.setSubTaskName(monitor, getProgressMessage(bytesDone, bytesTotal, speed)); } } /** * Close the channel for file transfer. * @param peer * @param channel * @throws TCFChannelException */ protected static void closeChannel(final IPeer peer, final IChannel channel) throws TCFChannelException { if (channel != null) { Tcf.getChannelManager().closeChannel(channel); } } private static String getProgressMessage(long bytesDone, long bytesTotal, long bytesSpeed) { String done = "B"; //$NON-NLS-1$ String total = "B"; //$NON-NLS-1$ String speed = "B/s"; //$NON-NLS-1$ if (bytesDone > 1024) { bytesDone /= 1024; done = "KB"; //$NON-NLS-1$ } if (bytesDone > 1024) { bytesDone /= 1024; done = "MB"; //$NON-NLS-1$ } if (bytesDone > 1024) { bytesDone /= 1024; done = "GB"; //$NON-NLS-1$ } if (bytesTotal > 1024) { bytesTotal /= 1024; total = "KB"; //$NON-NLS-1$ } if (bytesTotal > 1024) { bytesTotal /= 1024; total = "MB"; //$NON-NLS-1$ } if (bytesTotal > 1024) { bytesTotal /= 1024; total = "GB"; //$NON-NLS-1$ } if (bytesSpeed > 1024) { bytesSpeed /= 1024; speed = "KB/s"; //$NON-NLS-1$ } if (bytesSpeed > 1024) { bytesSpeed /= 1024; speed = "MB/s"; //$NON-NLS-1$ } if (bytesSpeed > 1024) { bytesSpeed /= 1024; speed = "GB/s"; //$NON-NLS-1$ } return bytesDone + done + " of " + (bytesTotal > 0 ? Long.toString(bytesTotal) : "N/A") + total + " at " + bytesSpeed + speed; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }