aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTobias Schwarz2012-05-22 05:26:16 (EDT)
committerUwe Stieber2012-05-22 05:26:16 (EDT)
commit78dae1e69eef0223985ecaa5a1924d83b22d6185 (patch)
treea8779d84af21edc645785ea022a87f6b11f7e32d
parent67a229974ba4ddb718c7e10d41bbf35561955e0a (diff)
downloadorg.eclipse.tcf-78dae1e69eef0223985ecaa5a1924d83b22d6185.zip
org.eclipse.tcf-78dae1e69eef0223985ecaa5a1924d83b22d6185.tar.gz
org.eclipse.tcf-78dae1e69eef0223985ecaa5a1924d83b22d6185.tar.bz2
Target Explorer: ADD possibility to use own channel for process launcher and
files transfer service
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.core/src/org/eclipse/tcf/te/tcf/filesystem/core/services/FileTransferService.java892
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/FileTransferStep.java5
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/LaunchProcessStep.java5
-rw-r--r--target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java2021
4 files changed, 1508 insertions, 1415 deletions
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.core/src/org/eclipse/tcf/te/tcf/filesystem/core/services/FileTransferService.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.core/src/org/eclipse/tcf/te/tcf/filesystem/core/services/FileTransferService.java
index eccf14c..cc11cc1 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.core/src/org/eclipse/tcf/te/tcf/filesystem/core/services/FileTransferService.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.core/src/org/eclipse/tcf/te/tcf/filesystem/core/services/FileTransferService.java
@@ -1,420 +1,472 @@
-/*******************************************************************************
- * Copyright (c) 2012 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.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.net.ConnectException;
-
-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.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.protocol.Protocol;
-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.core.concurrent.BlockingCallProxy;
-import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
-import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel;
-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 {
-
- /**
- * Transfer a file between host and target depending on the {@link IFileTransferItem} data.
- *
- * @param peer The peer.
- * @param item The file transfer item.
- * @param monitor The progress monitor.
- * @param callback The callback.
- */
- public 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;
- }
-
- IFileSystem fileSystem;
- try {
- IChannel channel = openChannel(peer);
- fileSystem = getBlockingFileSystem(channel);
- }
- catch (Exception e) {
- if (callback != null) {
- callback.done(peer, StatusHelper.getStatus(e));
- }
- return;
- }
-
- 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);
- }
- return;
- }
-
- protected static void transferToHost(IPeer peer, IFileSystem fileSystem, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) {
-
- IStatus result = Status.OK_STATUS;
-
- IPath hostPath = item.getHostPath();
- IPath targetPath = item.getTargetPath();
-
- 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];
-
- item.getHostPath().removeLastSegments(1).toFile().mkdirs();
- // If the host file is a directory, append the remote file name
- if (hostPath.toFile().isDirectory()) {
- hostPath = item.getHostPath().append(targetPath.lastSegment());
- }
-
- // 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.toString(), 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) {
- hostPath.toFile().setLastModified(mtime);
- }
- }
- else if (result.getSeverity() == IStatus.ERROR || result.getSeverity() == IStatus.CANCEL) {
- try {
- hostPath.toFile().delete();
- }
- catch (Throwable e) {
- }
- }
- }
- callback.done(peer, result);
- }
-
- protected static void transferToTarget(IPeer peer, IFileSystem fileSystem, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) {
-
- IStatus result = Status.OK_STATUS;
-
- IPath targetPath = item.getTargetPath();
- 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];
-
- // Check the target destination directory
- fileSystem.stat(targetPath.toString(), 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 (error[0] != null || (attrs[0] != null && attrs[0].isDirectory())) {
- targetPath = targetPath.append(item.getHostPath().lastSegment());
- }
-
- try {
- // Open the remote file
- fileSystem.open(targetPath.toString(), 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(), 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, new Long((bytesRead/bytesTotal) * 1000).intValue());
- ProgressHelper.setSubTaskName(monitor, getProgressMessage(bytesDone, bytesTotal, speed));
- }
- }
-
- protected static IChannel openChannel(final IPeer peer) throws TCFChannelException {
- IChannelManager proxy = BlockingCallProxy.newInstance(IChannelManager.class, Tcf.getChannelManager());
- final TCFChannelException[] errors = new TCFChannelException[1];
- final IChannel[] channels = new IChannel[1];
- proxy.openChannel(peer, null, new DoneOpenChannel() {
- @Override
- public void doneOpenChannel(Throwable error, IChannel channel) {
- if (error != null) {
- if (error instanceof ConnectException) {
- String message = NLS.bind(Messages.Operation_NotResponding, peer.getID());
- errors[0] = new TCFChannelException(message);
- }
- else {
- String message = NLS.bind(Messages.Operation_OpeningChannelFailureMessage, peer.getID(), error.getMessage());
- errors[0] = new TCFChannelException(message, error);
- }
- }
- else {
- channels[0] = channel;
- }
- }
- });
- if (errors[0] != null) {
- throw errors[0];
- }
- return channels[0];
- }
-
- protected static IFileSystem getBlockingFileSystem(final IChannel channel) {
- if(Protocol.isDispatchThread()) {
- IFileSystem service = channel.getRemoteService(IFileSystem.class);
- return BlockingCallProxy.newInstance(IFileSystem.class, service);
- }
- final IFileSystem[] service = new IFileSystem[1];
- Protocol.invokeAndWait(new Runnable(){
- @Override
- public void run() {
- service[0] = getBlockingFileSystem(channel);
- }});
- return service[0];
- }
-
- 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 (bytesSpeed > 1024) {
- bytesSpeed /= 1024;
- speed = "GB/s"; //$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 (bytesDone > 1024) {
- bytesDone /= 1024;
- done = "GB"; //$NON-NLS-1$
- }
-
- return bytesDone + done + " of " + bytesTotal + total + " at " + bytesSpeed + speed; //$NON-NLS-1$ //$NON-NLS-2$
- }
-
-}
+/*******************************************************************************
+ * Copyright (c) 2012 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.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.net.ConnectException;
+
+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.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.protocol.Protocol;
+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.core.concurrent.BlockingCallProxy;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel;
+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 {
+
+ /**
+ * 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 = openChannel(peer);
+ }
+ fileSystem = 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 = 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();
+ IPath targetPath = item.getTargetPath();
+
+ 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];
+
+ item.getHostPath().removeLastSegments(1).toFile().mkdirs();
+ // If the host file is a directory, append the remote file name
+ if (hostPath.toFile().isDirectory()) {
+ hostPath = item.getHostPath().append(targetPath.lastSegment());
+ }
+
+ // 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.toString(), 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) {
+ hostPath.toFile().setLastModified(mtime);
+ }
+ }
+ else if (result.getSeverity() == IStatus.ERROR || result.getSeverity() == IStatus.CANCEL) {
+ try {
+ hostPath.toFile().delete();
+ }
+ catch (Throwable e) {
+ }
+ }
+ }
+ callback.done(peer, result);
+ }
+
+ protected static void transferToTarget(IPeer peer, IFileSystem fileSystem, IFileTransferItem item, IProgressMonitor monitor, ICallback callback) {
+
+ IStatus result = Status.OK_STATUS;
+
+ IPath targetPath = item.getTargetPath();
+ 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];
+
+ // Check the target destination directory
+ fileSystem.stat(targetPath.toString(), 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 (error[0] != null || (attrs[0] != null && attrs[0].isDirectory())) {
+ targetPath = targetPath.append(item.getHostPath().lastSegment());
+ }
+
+ try {
+ // Open the remote file
+ fileSystem.open(targetPath.toString(), 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(), 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, new Long((bytesRead/bytesTotal) * 1000).intValue());
+ ProgressHelper.setSubTaskName(monitor, getProgressMessage(bytesDone, bytesTotal, speed));
+ }
+ }
+
+ /**
+ * Open a channel for file transfer.
+ * @param peer
+ * @return
+ * @throws TCFChannelException
+ */
+ protected static IChannel openChannel(final IPeer peer) throws TCFChannelException {
+ IChannelManager proxy = BlockingCallProxy.newInstance(IChannelManager.class, Tcf.getChannelManager());
+ final TCFChannelException[] errors = new TCFChannelException[1];
+ final IChannel[] channels = new IChannel[1];
+ proxy.openChannel(peer, null, new DoneOpenChannel() {
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if (error != null) {
+ if (error instanceof ConnectException) {
+ String message = NLS.bind(Messages.Operation_NotResponding, peer.getID());
+ errors[0] = new TCFChannelException(message);
+ }
+ else {
+ String message = NLS.bind(Messages.Operation_OpeningChannelFailureMessage, peer.getID(), error.getMessage());
+ errors[0] = new TCFChannelException(message, error);
+ }
+ }
+ else {
+ channels[0] = channel;
+ }
+ }
+ });
+ if (errors[0] != null) {
+ throw errors[0];
+ }
+ return channels[0];
+ }
+
+ /**
+ * 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) {
+ IChannelManager proxy = BlockingCallProxy.newInstance(IChannelManager.class, Tcf.getChannelManager());
+ proxy.closeChannel(channel);
+ }
+ }
+
+ /**
+ * Get a blocking file system.
+ * @param channel
+ * @return
+ */
+ protected static IFileSystem getBlockingFileSystem(final IChannel channel) {
+ if(Protocol.isDispatchThread()) {
+ IFileSystem service = channel.getRemoteService(IFileSystem.class);
+ return BlockingCallProxy.newInstance(IFileSystem.class, service);
+ }
+ final IFileSystem[] service = new IFileSystem[1];
+ Protocol.invokeAndWait(new Runnable(){
+ @Override
+ public void run() {
+ service[0] = getBlockingFileSystem(channel);
+ }});
+ return service[0];
+ }
+
+ 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 (bytesSpeed > 1024) {
+ bytesSpeed /= 1024;
+ speed = "GB/s"; //$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 (bytesDone > 1024) {
+ bytesDone /= 1024;
+ done = "GB"; //$NON-NLS-1$
+ }
+
+ return bytesDone + done + " of " + bytesTotal + total + " at " + bytesSpeed + speed; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/FileTransferStep.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/FileTransferStep.java
index 3921b53..a3aed42 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/FileTransferStep.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/FileTransferStep.java
@@ -13,6 +13,7 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
+import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.te.launch.core.lm.interfaces.IFileTransferLaunchAttributes;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
@@ -22,6 +23,7 @@ import org.eclipse.tcf.te.runtime.stepper.interfaces.IFullQualifiedId;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepContext;
import org.eclipse.tcf.te.tcf.filesystem.core.services.FileTransferService;
import org.eclipse.tcf.te.tcf.launch.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.launch.core.interfaces.ICommonTCFLaunchAttributes;
/**
* Launch process step implementation.
@@ -50,9 +52,10 @@ public class FileTransferStep extends AbstractTcfLaunchStep {
*/
@Override
public void execute(IStepContext context, IPropertiesContainer data, IFullQualifiedId fullQualifiedId, IProgressMonitor monitor, final ICallback callback) {
+ IChannel channel = (IChannel)StepperAttributeUtil.getProperty(ICommonTCFLaunchAttributes.ATTR_CHANNEL, fullQualifiedId, data);
IFileTransferItem item = (IFileTransferItem)StepperAttributeUtil.getProperty(IFileTransferLaunchAttributes.ATTR_ACTIVE_FILE_TRANSFER, fullQualifiedId, data);
- FileTransferService.transfer(getActivePeerModel(data).getPeer(), item, monitor, callback);
+ FileTransferService.transfer(getActivePeerModel(data).getPeer(), channel, item, monitor, callback);
}
/* (non-Javadoc)
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/LaunchProcessStep.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/LaunchProcessStep.java
index 177430c..a4791f4 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/LaunchProcessStep.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.launch.core/src/org/eclipse/tcf/te/tcf/launch/core/steps/LaunchProcessStep.java
@@ -17,6 +17,7 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.services.IProcesses;
import org.eclipse.tcf.te.core.utils.text.StringUtil;
import org.eclipse.tcf.te.launch.core.persistence.DefaultPersistenceDelegate;
@@ -29,6 +30,7 @@ import org.eclipse.tcf.te.runtime.stepper.StepperAttributeUtil;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IFullQualifiedId;
import org.eclipse.tcf.te.runtime.stepper.interfaces.IStepContext;
import org.eclipse.tcf.te.tcf.launch.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.launch.core.interfaces.ICommonTCFLaunchAttributes;
import org.eclipse.tcf.te.tcf.launch.core.interfaces.IRemoteAppLaunchAttributes;
import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher;
import org.eclipse.tcf.te.tcf.processes.core.launcher.ProcessLauncher;
@@ -66,8 +68,9 @@ public class LaunchProcessStep extends AbstractTcfLaunchStep {
*/
@Override
public void execute(IStepContext context, final IPropertiesContainer data, final IFullQualifiedId fullQualifiedId, IProgressMonitor monitor, final ICallback callback) {
+ IChannel channel = (IChannel)StepperAttributeUtil.getProperty(ICommonTCFLaunchAttributes.ATTR_CHANNEL, fullQualifiedId, data);
// Construct the launcher object
- ProcessLauncher launcher = new ProcessLauncher();
+ ProcessLauncher launcher = new ProcessLauncher(channel);
Map<String, Object> launchAttributes = new HashMap<String, Object>();
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
index a636ab1..a2469d0 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.processes.core/src/org/eclipse/tcf/te/tcf/processes/core/launcher/ProcessLauncher.java
@@ -1,993 +1,1028 @@
-/*******************************************************************************
- * Copyright (c) 2011, 2012 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.processes.core.launcher;
-
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.core.runtime.Assert;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.PlatformObject;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.tcf.protocol.IChannel;
-import org.eclipse.tcf.protocol.IChannel.IChannelListener;
-import org.eclipse.tcf.protocol.IPeer;
-import org.eclipse.tcf.protocol.IToken;
-import org.eclipse.tcf.protocol.Protocol;
-import org.eclipse.tcf.services.IProcesses;
-import org.eclipse.tcf.services.IProcesses.ProcessContext;
-import org.eclipse.tcf.services.IStreams;
-import org.eclipse.tcf.te.core.async.AsyncCallbackCollector;
-import org.eclipse.tcf.te.runtime.callback.Callback;
-import org.eclipse.tcf.te.runtime.events.DisposedEvent;
-import org.eclipse.tcf.te.runtime.events.EventManager;
-import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
-import org.eclipse.tcf.te.runtime.interfaces.events.IEventListener;
-import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
-import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
-import org.eclipse.tcf.te.runtime.services.ServiceManager;
-import org.eclipse.tcf.te.runtime.services.interfaces.ITerminalService;
-import org.eclipse.tcf.te.runtime.services.interfaces.constants.ITerminalsConnectorConstants;
-import org.eclipse.tcf.te.tcf.core.Tcf;
-import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate;
-import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
-import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
-import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
-import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessContextAwareListener;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessStreamsProxy;
-import org.eclipse.tcf.te.tcf.processes.core.interfaces.tracing.ITraceIds;
-import org.eclipse.tcf.te.tcf.processes.core.nls.Messages;
-
-/**
- * Remote process launcher.
- * <p>
- * The process launcher is implemented fully asynchronous.
- */
-public class ProcessLauncher extends PlatformObject implements IProcessLauncher {
- // The channel instance
- /* default */ IChannel channel;
- // The process properties instance
- private IPropertiesContainer properties;
-
- // The processes service instance
- /* default */ IProcesses svcProcesses;
- // The streams service instance
- /* default */ IStreams svcStreams;
- // The remote process context
- /* default */ IProcesses.ProcessContext processContext;
-
- // The callback instance
- private ICallback callback;
-
- // The streams listener instance
- private IStreams.StreamsListener streamsListener = null;
- // The process listener instance
- private IProcesses.ProcessesListener processesListener = null;
- // The event listener instance
- private IEventListener eventListener = null;
-
- // The streams proxy instance
- private IProcessStreamsProxy streamsProxy = null;
-
- /**
- * Constructor.
- */
- public ProcessLauncher() {
- this(null);
- }
-
- /**
- * Constructor.
- */
- public ProcessLauncher(IProcessStreamsProxy streamsProxy) {
- super();
- this.streamsProxy = streamsProxy;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#dispose()
- */
- @Override
- public void dispose() {
- // Unlink the process context
- processContext = null;
-
- // Store a final reference to the channel instance
- final IChannel finChannel = channel;
-
- // Remove the notification listener
- if (eventListener != null) {
- EventManager.getInstance().removeEventListener(eventListener);
- eventListener = null;
- }
-
- // Create the callback collector
- final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
- @Override
- protected void internalDone(Object caller, IStatus status) {
- Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
- // Close the channel as all disposal is done
- if (finChannel != null) Tcf.getChannelManager().closeChannel(finChannel);
- }
- }, new CallbackInvocationDelegate());
-
- if (streamsListener != null) {
- // Dispose the streams listener
- if (streamsListener instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- }
- streamsListener = null;
- }
-
- // Dispose the processes listener if created
- if (processesListener != null) {
- // Dispose the processes listener
- if (processesListener instanceof ProcessProcessesListener) {
- ((ProcessProcessesListener)processesListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- }
- processesListener = null;
- // Remove the processes listener from the processes service
- getSvcProcesses().removeListener(processesListener);
- }
-
- // Dispose the streams proxy if created
- if (streamsProxy != null) {
- streamsProxy.dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
- streamsProxy = null;
- }
- // Mark the collector initialization as done
- collector.initDone();
-
- // Dissociate the channel
- channel = null;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#terminate()
- */
- @Override
- public void terminate() {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- if (processContext != null && processContext.canTerminate()) {
- // Try to terminate the process the usual way first (sending SIGTERM)
- processContext.terminate(new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- onTerminateDone(processContext, error);
- }
- });
- }
-
- }
- };
-
- if (Protocol.isDispatchThread()) runnable.run();
- else Protocol.invokeAndWait(runnable);
- }
-
- /**
- * Check if the process context really died after sending SIGTERM.
- * <p>
- * Called from {@link #terminate()}.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @param error The exception in case {@link #terminate()} returned with an error or <code>null</code>.
- */
- protected void onTerminateDone(IProcesses.ProcessContext context, Exception error) {
- Assert.isNotNull(context);
-
- // If the terminate of the remote process context failed, give a warning to the user
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processTerminateFailed, context.getName());
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- // No error from terminate, this does not mean that the process went down
- // really -> SIGTERM might have been ignored from the process!
- else {
- final IProcesses.ProcessContext finContext = context;
- // Let's see if we can still get information about the context
- getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
- @Override
- public void doneGetContext(IToken token, Exception error, ProcessContext context) {
- // In case there is no error and we do get back an process context,
- // the process must be still running, having ignored the SIGTERM.
- if (error == null && context != null && context.getID().equals(finContext.getID())) {
- // Let's send a SIGHUP next.
- getSvcProcesses().signal(context.getID(), 15, new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- onSignalSIGHUPDone(finContext, error);
- }
- });
- }
- }
- });
- }
- }
-
- /**
- * Check if the process context died after sending SIGHUP.
- * <p>
- * Called from {@link #onTerminateDone(IProcesses.ProcessContext, Exception)}.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @param error The exception in case sending the signal returned with an error or <code>null</code>.
- */
- protected void onSignalSIGHUPDone(IProcesses.ProcessContext context, Exception error) {
- Assert.isNotNull(context);
-
- // If the terminate of the remote process context failed, give a warning to the user
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGHUP(15)", context.getName()); //$NON-NLS-1$
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- // No error from terminate, this does not mean that the process went down
- // really -> SIGTERM might have been ignored from the process!
- else {
- final IProcesses.ProcessContext finContext = context;
- // Let's see if we can still get information about the context
- getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
- @Override
- public void doneGetContext(IToken token, Exception error, ProcessContext context) {
- // In case there is no error and we do get back an process context,
- // the process must be still running, having ignored the SIGHUP.
- if (error == null && context != null && context.getID().equals(finContext.getID())) {
- // Finally send a SIGKILL.
- getSvcProcesses().signal(context.getID(), 9, new IProcesses.DoneCommand() {
- @Override
- public void doneCommand(IToken token, Exception error) {
- if (error != null) {
- String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGKILL(15)", finContext.getName()); //$NON-NLS-1$
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
-
- IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
- Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
-
- // Dispose the launcher directly
- dispose();
- }
- }
- });
- }
- }
- });
- }
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
- */
- @Override
- public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) {
- Assert.isNotNull(peer);
- Assert.isNotNull(properties);
-
- // Normalize the callback
- if (callback == null) {
- this.callback = new Callback() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
- */
- @Override
- public void internalDone(Object caller, IStatus status) {
- }
- };
- }
- else {
- this.callback = callback;
- }
-
- // Remember the process properties
- this.properties = properties;
-
- // Open a dedicated channel to the given peer
- Map<String, Boolean> flags = new HashMap<String, Boolean>();
- flags.put(IChannelManager.FLAG_FORCE_NEW, Boolean.TRUE);
- Tcf.getChannelManager().openChannel(peer, flags, new IChannelManager.DoneOpenChannel() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel)
- */
- @Override
- public void doneOpenChannel(Throwable error, IChannel channel) {
- if (error == null) {
- ProcessLauncher.this.channel = channel;
-
- // Attach a channel listener so we can dispose ourself if the channel
- // is closed from the remote side.
- channel.addChannelListener(new IChannelListener() {
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened()
- */
- @Override
- public void onChannelOpened() {
- }
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable)
- */
- @Override
- public void onChannelClosed(Throwable error) {
- if (error != null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
- error);
- invokeCallback(status, null);
- }
- }
- /* (non-Javadoc)
- * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int)
- */
- @Override
- public void congestionLevel(int level) {
- }
- });
-
-
- // Check if the channel is in connected state
- if (channel.getState() != IChannel.STATE_OPEN) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- Messages.ProcessLauncher_error_channelNotConnected,
- new IllegalStateException());
- invokeCallback(status, null);
- return;
- }
-
- // Do some very basic sanity checking on the process properties
- if (properties.getStringProperty(PROP_PROCESS_PATH) == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- Messages.ProcessLauncher_error_missingProcessPath,
- new IllegalArgumentException(PROP_PROCESS_PATH));
- invokeCallback(status, null);
- return;
- }
-
- // Get the process and streams services
- svcProcesses = channel.getRemoteService(IProcesses.class);
- if (svcProcesses == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IProcesses.class.getName()),
- null);
-
- invokeCallback(status, null);
- return;
- }
-
- svcStreams = channel.getRemoteService(IStreams.class);
- if (svcStreams == null) {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IStreams.class.getName()),
- null);
- invokeCallback(status, null);
- return;
- }
-
- // Execute the launch now
- executeLaunch();
- } else {
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
- error);
- invokeCallback(status, null);
- }
- }
- });
- }
-
- /**
- * Executes the launch of the remote process.
- */
- protected void executeLaunch() {
- // Get the process properties container
- final IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // If a console should be associated, a streams listener needs to be created
- if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)
- || properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
- // Create the streams listener
- streamsListener = createStreamsListener();
- // If available, we need to subscribe to the streams.
- if (streamsListener != null) {
- getSvcStreams().subscribe(IProcesses.NAME, streamsListener, new IStreams.DoneSubscribe() {
- @Override
- public void doneSubscribe(IToken token, Exception error) {
- // In case the subscribe to the stream fails, we pass on
- // the error to the user and stop the launch
- if (error != null) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, Messages.ProcessLauncher_cause_subscribeFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Initialize the console or output file
- onSubscribeStreamsDone();
- }
- }
- });
- } else {
- // No streams to attach to -> go directly to the process launch
- onAttachStreamsDone();
- }
- } else {
- // No streams to attach to -> go directly to the process launch
- onAttachStreamsDone();
- }
- }
-
- /**
- * Initialize and attach the output console and/or the output file.
- * <p>
- * Called from {@link IStreams#subscribe(String, org.eclipse.tcf.services.IStreams.StreamsListener, org.eclipse.tcf.services.IStreams.DoneSubscribe)}.
- */
- protected void onSubscribeStreamsDone() {
- // Get the process properties container
- IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // The streams got subscribed, check if we shall attach the console
- if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)) {
- // If no specific streams proxy is set, the output redirection will default
- // to the standard terminals console view
- if (streamsProxy == null) {
- // Register the notification listener to listen to the console disposal
- eventListener = new ProcessLauncherEventListener(this);
- EventManager.getInstance().addEventListener(eventListener, DisposedEvent.class);
-
- // Get the terminal service
- ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
- // If not available, we cannot fulfill this request
- if (terminal != null) {
- // Create the terminal streams settings
- PropertiesContainer props = new PropertiesContainer();
- props.setProperty(ITerminalsConnectorConstants.PROP_CONNECTOR_TYPE_ID, "org.eclipse.tcf.te.ui.terminals.type.streams"); //$NON-NLS-1$
- props.setProperty(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tcf.te.ui.terminals.TerminalsView"); //$NON-NLS-1$
- // Set the terminal tab title
- String terminalTitle = getTerminalTitle();
- if (terminalTitle != null) {
- props.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
- }
-
- // Create and store the streams which will be connected to the terminals stdin
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
- // Create and store the streams the terminal will see as stdout
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
- // Create and store the streams the terminal will see as stderr
- props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
-
- // Copy the terminal properties
- props.setProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO));
- props.setProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR, properties.getStringProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR));
-
- // The custom data object is the process launcher itself
- props.setProperty(ITerminalsConnectorConstants.PROP_DATA, this);
-
- // Open the console
- terminal.openConsole(props, null);
- }
- } else {
- // Create and connect the streams which will be connected to the terminals stdin
- streamsProxy.connectInputStreamMonitor(connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
- // Create and store the streams the terminal will see as stdout
- streamsProxy.connectOutputStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
- // Create and store the streams the terminal will see as stderr
- streamsProxy.connectErrorStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
- }
- }
-
- // The streams got subscribed, check if we shall configure the output redirection to a file
- if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
- // Get the file name where to redirect the process output to
- String filename = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE);
- try {
- // Create the receiver instance. If the file already exist, we
- // overwrite the file content.
- StreamsDataReceiver receiver = new StreamsDataReceiver(new BufferedWriter(new FileWriter(filename)),
- new String[] { IProcesses.PROP_STDOUT_ID, IProcesses.PROP_STDERR_ID });
- // Register the receiver to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
- }
- } catch (IOException e) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- e.getLocalizedMessage() != null ? e.getLocalizedMessage() : Messages.ProcessLauncher_cause_ioexception);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e);
- invokeCallback(status, null);
- }
- }
-
- // Launch the process
- onAttachStreamsDone();
- }
-
- /**
- * Returns the terminal title string.
- * <p>
- * The default implementation constructs a title like &quot;<process> (Start time) [channel name]&quot;.
- *
- * @return The terminal title string or <code>null</code>.
- */
- protected String getTerminalTitle() {
- if (properties == null) {
- return null;
- }
-
- StringBuilder title = new StringBuilder();
-
- IPath processPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH));
- IPath monitoredProcessPath = null;
- if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH) != null) {
- monitoredProcessPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH));
- }
-
- // In case, we do have a monitored process path here, we construct the title
- // as <monitor_app_basename>: <monitored_app>"
- if (monitoredProcessPath != null) {
- title.append(processPath.lastSegment());
- title.append(": "); //$NON-NLS-1$
- processPath = monitoredProcessPath;
- }
-
- // Avoid very long terminal title's by shortening the path if it has more than 3 segments
- if (processPath.segmentCount() > 3) {
- title.append(".../"); //$NON-NLS-1$
- title.append(processPath.lastSegment());
- } else {
- title.append(processPath.toString());
- }
-
- // Get the peer name
- final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(IProcessLauncher.PROP_CONNECTION_NAME));
- if (peerName.get() == null) {
- // Query the peer from the open channel
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- if (channel != null) {
- peerName.set(channel.getRemotePeer().getName());
- }
- }
- };
-
- if (Protocol.isDispatchThread()) runnable.run();
- else Protocol.invokeAndWait(runnable);
- }
-
- if (peerName.get() != null) {
- title.append(" [").append(peerName.get()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
- String date = format.format(new Date(System.currentTimeMillis()));
- title.append(" (").append(date).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
-
- return title.toString();
- }
-
- /**
- * Connects the given stream id's to a local {@link InputStream} instance.
- *
- * @param streamsListener The streams listener. Must not be <code>null</code>.
- * @param streamIds The stream id's. Must not be <code>null</code>.
- *
- * @return The local input stream instance or <code>null</code>.
- */
- protected InputStream connectRemoteInputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
- Assert.isNotNull(streamsListener);
- Assert.isNotNull(streamIds);
-
- InputStream stream = null;
-
- // Create the output stream receiving the data from remote
- PipedOutputStream remoteStreamDataReceiverStream = new PipedOutputStream();
- // Create the piped input stream instance
- try { stream = new PipedInputStream(remoteStreamDataReceiverStream); } catch (IOException e) { /* ignored on purpose */ }
-
- // If the input stream creation succeeded, connect the data receiver
- if (stream != null) {
- StreamsDataReceiver receiver = new StreamsDataReceiver(new OutputStreamWriter(remoteStreamDataReceiverStream), streamIds);
- // Register the data receiver to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
- }
- }
-
- return stream;
- }
-
- /**
- * Connects the given stream id's to a local {@link OutputStream} instance.
- *
- * @param streamsListener The streams listener. Must not be <code>null</code>.
- * @param streamIds The stream id's. Must not be <code>null</code>.
- *
- * @return The local output stream instance or <code>null</code>.
- */
- protected OutputStream connectRemoteOutputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
- Assert.isNotNull(streamsListener);
- Assert.isNotNull(streamIds);
-
- PipedInputStream inStream = null;
-
- // Create the output stream receiving the data from local
- PipedOutputStream stream = new PipedOutputStream();
- // Create the piped input stream instance
- try { inStream = new PipedInputStream(stream); } catch (IOException e) { stream = null; }
-
- // If the stream creation succeeded, connect the data provider
- if (stream != null && inStream != null) {
- StreamsDataProvider provider = new StreamsDataProvider(new InputStreamReader(inStream), streamIds);
- // Register the data provider to the streams listener
- if (getStreamsListener() instanceof ProcessStreamsListener) {
- ((ProcessStreamsListener)getStreamsListener()).setDataProvider(provider);
- }
- }
-
- return stream;
- }
-
- /**
- * Queries the initial new process environment from remote.
- */
- protected void onAttachStreamsDone() {
- // Query the default environment for a new process
- getSvcProcesses().getEnvironment(new IProcesses.DoneGetEnvironment() {
- @Override
- public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) {
- if (error != null) {
- // Construct the error message to show to the user
- String message = Messages.ProcessLauncher_error_getEnvironmentFailed;
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Initiate the process launch
- onGetEnvironmentDone(environment);
- }
- }
- });
- }
-
- /**
- * Initiate the process launch.
- * <p>
- * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}.
- */
- protected void onGetEnvironmentDone(final Map<String, String> environment) {
- // Get the process properties container
- final IPropertiesContainer properties = getProperties();
- if (properties == null) {
- // This is an illegal argument. Properties must be set
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
- NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
- new IllegalArgumentException());
- invokeCallback(status, null);
- return;
- }
-
- // Create the process listener
- processesListener = createProcessesListener();
- if (processesListener != null) {
- getSvcProcesses().addListener(processesListener);
- }
-
- // Get the process attributes
- String processPath = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH);
-
- String[] processArgs = (String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS);
- // Assure that the first argument is the process path itself
- if (!(processArgs != null && processArgs.length > 0 && processPath.equals(processArgs[0]))) {
- // Prepend the process path to the list of arguments
- List<String> args = processArgs != null ? new ArrayList<String>(Arrays.asList(processArgs)) : new ArrayList<String>();
- args.add(0, processPath);
- processArgs = args.toArray(new String[args.size()]);
- }
-
- String processCWD = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_CWD);
- // If the process working directory is not explicitly set, default to the process path directory
- if (processCWD == null || "".equals(processCWD.trim())) { //$NON-NLS-1$
- processCWD = new Path(processPath).removeLastSegments(1).toString();
- }
-
- // Merge the initial process environment and the desired process environment
- Map<String, String> processEnv = new HashMap<String, String>(environment);
- Map<String, String> processEnvDiff = (Map<String, String>)properties.getProperty(IProcessLauncher.PROP_PROCESS_ENV);
- if (processEnvDiff != null && !processEnvDiff.isEmpty()) {
- processEnv.putAll(processEnvDiff);
- }
- // Assure that the TERM variable is set to "ansi"
- processEnv.put("TERM", "ansi"); //$NON-NLS-1$ //$NON-NLS-2$
-
- boolean attach = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ATTACH);
-
- // Launch the remote process
- getSvcProcesses().start(processCWD, processPath, processArgs, processEnv, attach, new IProcesses.DoneStart() {
- @Override
- public void doneStart(IToken token, Exception error, ProcessContext process) {
- if (error != null) {
- // Construct the error message to show to the user
- String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
- properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
- makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
- message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
- error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
-
- // Construct the status object
- IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
- invokeCallback(status, null);
- } else {
- // Register the process context to the listener
- onProcessLaunchDone(process);
- }
- }
- });
- }
-
- /**
- * Register the process context to the listeners.
- *
- * @param process The process context or <code>null</code>.
- */
- protected void onProcessLaunchDone(ProcessContext process) {
- // Register the process context with the listeners
- if (process != null) {
- if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_PROCESS_LAUNCHER)) {
- CoreBundleActivator.getTraceHandler().trace("Process context created: id='" + process.getID() + "', name='" + process.getName() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- 0, ITraceIds.TRACE_PROCESS_LAUNCHER, IStatus.INFO, getClass());
- }
-
- // Remember the process context
- processContext = process;
-
- // Push the process context to the listeners
- if (getStreamsListener() instanceof IProcessContextAwareListener) {
- ((IProcessContextAwareListener)getStreamsListener()).setProcessContext(process);
- }
- if (getProcessesListener() instanceof IProcessContextAwareListener) {
- ((IProcessContextAwareListener)getProcessesListener()).setProcessContext(process);
- }
-
- // Send a notification
- ProcessStateChangeEvent event = createRemoteProcessStateChangeEvent(process);
- if (event != null) EventManager.getInstance().fireEvent(event);
- }
-
- // Invoke the callback to signal that we are done
- invokeCallback(Status.OK_STATUS, process);
- }
-
- /**
- * Creates a new remote process state change event instance.
- *
- * @param context The process context. Must not be <code>null</code>.
- * @return The event instance or <code>null</code>.
- */
- protected ProcessStateChangeEvent createRemoteProcessStateChangeEvent(IProcesses.ProcessContext context) {
- Assert.isNotNull(context);
- return new ProcessStateChangeEvent(context, ProcessStateChangeEvent.EVENT_PROCESS_CREATED, Boolean.FALSE, Boolean.TRUE, -1);
- }
-
- /**
- * Invoke the callback with the given parameters. If the given status severity
- * is {@link IStatus#ERROR}, the process launcher object is disposed automatically.
- *
- * @param status The status. Must not be <code>null</code>.
- * @param result The result object or <code>null</code>.
- */
- protected void invokeCallback(IStatus status, Object result) {
- // Dispose the process launcher if we report an error
- if (status.getSeverity() == IStatus.ERROR) {
- dispose();
- }
-
- // Invoke the callback
- ICallback callback = getCallback();
- if (callback != null) {
- callback.setResult(result);
- callback.done(this, status);
- }
- }
-
- /**
- * Returns the channel instance.
- *
- * @return The channel instance or <code>null</code> if none.
- */
- public final IChannel getChannel() {
- return channel;
- }
-
- /**
- * Returns the process properties container.
- *
- * @return The process properties container or <code>null</code> if none.
- */
- public final IPropertiesContainer getProperties() {
- return properties;
- }
-
- /**
- * Returns the processes service instance.
- *
- * @return The processes service instance or <code>null</code> if none.
- */
- public final IProcesses getSvcProcesses() {
- return svcProcesses;
- }
-
- /**
- * Returns the streams service instance.
- *
- * @return The streams service instance or <code>null</code> if none.
- */
- public final IStreams getSvcStreams() {
- return svcStreams;
- }
-
- /**
- * Returns the callback instance.
- *
- * @return The callback instance or <code>null</code> if none.
- */
- protected final ICallback getCallback() {
- return callback;
- }
-
- /**
- * Create the streams listener instance.
- *
- * @return The streams listener instance or <code>null</code> if none.
- */
- protected IStreams.StreamsListener createStreamsListener() {
- return new ProcessStreamsListener(this);
- }
-
- /**
- * Returns the streams listener instance.
- *
- * @return The streams listener instance or <code>null</code>.
- */
- protected final IStreams.StreamsListener getStreamsListener() {
- return streamsListener;
- }
-
- /**
- * Create the processes listener instance.
- *
- * @return The processes listener instance or <code>null</code> if none.
- */
- protected IProcesses.ProcessesListener createProcessesListener() {
- return new ProcessProcessesListener(this);
- }
-
- /**
- * Returns the processes listener instance.
- *
- * @return The processes listener instance or <code>null</code> if none.
- */
- protected final IProcesses.ProcessesListener getProcessesListener() {
- return processesListener;
- }
-
- /* (non-Javadoc)
- * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class)
- */
- @Override
- public Object getAdapter(Class adapter) {
- if (adapter.isAssignableFrom(IProcesses.ProcessesListener.class)) {
- return processesListener;
- }
- else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) {
- return streamsListener;
- }
- else if (adapter.isAssignableFrom(IStreams.class)) {
- return svcStreams;
- }
- else if (adapter.isAssignableFrom(IProcesses.class)) {
- return svcProcesses;
- }
- else if (adapter.isAssignableFrom(IChannel.class)) {
- return channel;
- }
- else if (adapter.isAssignableFrom(IPropertiesContainer.class)) {
- return properties;
- }
- else if (adapter.isAssignableFrom(IProcesses.ProcessContext.class)) {
- return processContext;
- }
- else if (adapter.isAssignableFrom(this.getClass())) {
- return this;
- }
-
-
- return super.getAdapter(adapter);
- }
-
- /**
- * Makes a space separated string from the given array.
- *
- * @param array The string array or <code>null</code>.
- * @return The string.
- */
- String makeString(String[] array) {
- if (array == null) {
- return ""; //$NON-NLS-1$
- }
- StringBuilder result = new StringBuilder();
- for (String element : array) {
- if (result.length() > 0) {
- result.append(' ');
- }
- result.append(element);
- }
- return result.toString();
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 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.processes.core.launcher;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.PlatformObject;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.tcf.protocol.IChannel;
+import org.eclipse.tcf.protocol.IChannel.IChannelListener;
+import org.eclipse.tcf.protocol.IPeer;
+import org.eclipse.tcf.protocol.IToken;
+import org.eclipse.tcf.protocol.Protocol;
+import org.eclipse.tcf.services.IProcesses;
+import org.eclipse.tcf.services.IProcesses.ProcessContext;
+import org.eclipse.tcf.services.IStreams;
+import org.eclipse.tcf.te.core.async.AsyncCallbackCollector;
+import org.eclipse.tcf.te.runtime.callback.Callback;
+import org.eclipse.tcf.te.runtime.events.DisposedEvent;
+import org.eclipse.tcf.te.runtime.events.EventManager;
+import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
+import org.eclipse.tcf.te.runtime.interfaces.events.IEventListener;
+import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer;
+import org.eclipse.tcf.te.runtime.properties.PropertiesContainer;
+import org.eclipse.tcf.te.runtime.services.ServiceManager;
+import org.eclipse.tcf.te.runtime.services.interfaces.ITerminalService;
+import org.eclipse.tcf.te.runtime.services.interfaces.constants.ITerminalsConnectorConstants;
+import org.eclipse.tcf.te.tcf.core.Tcf;
+import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate;
+import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider;
+import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver;
+import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessContextAwareListener;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessStreamsProxy;
+import org.eclipse.tcf.te.tcf.processes.core.interfaces.tracing.ITraceIds;
+import org.eclipse.tcf.te.tcf.processes.core.nls.Messages;
+
+/**
+ * Remote process launcher.
+ * <p>
+ * The process launcher is implemented fully asynchronous.
+ */
+public class ProcessLauncher extends PlatformObject implements IProcessLauncher {
+ // The channel instance
+ /* default */ IChannel channel = null;
+ /* default */ boolean externalChannel = false;
+ // The process properties instance
+ /* default */ IPropertiesContainer properties;
+
+ // The processes service instance
+ /* default */ IProcesses svcProcesses;
+ // The streams service instance
+ /* default */ IStreams svcStreams;
+ // The remote process context
+ /* default */ IProcesses.ProcessContext processContext;
+
+ // The callback instance
+ private ICallback callback;
+
+ // The streams listener instance
+ private IStreams.StreamsListener streamsListener = null;
+ // The process listener instance
+ private IProcesses.ProcessesListener processesListener = null;
+ // The event listener instance
+ private IEventListener eventListener = null;
+
+ // The streams proxy instance
+ private IProcessStreamsProxy streamsProxy = null;
+
+ /**
+ * Constructor.
+ */
+ public ProcessLauncher() {
+ super();
+ }
+
+ /**
+ * Constructor.
+ */
+ public ProcessLauncher(IChannel channel) {
+ super();
+ externalChannel = channel != null;
+ this.channel = channel;
+ }
+
+ /**
+ * Constructor.
+ */
+ public ProcessLauncher(IProcessStreamsProxy streamsProxy) {
+ super();
+ this.streamsProxy = streamsProxy;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#dispose()
+ */
+ @Override
+ public void dispose() {
+ // Unlink the process context
+ processContext = null;
+
+ // Store a final reference to the channel instance
+ final IChannel finChannel = channel;
+
+ // Remove the notification listener
+ if (eventListener != null) {
+ EventManager.getInstance().removeEventListener(eventListener);
+ eventListener = null;
+ }
+
+ // Create the callback collector
+ final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() {
+ @Override
+ protected void internalDone(Object caller, IStatus status) {
+ Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$
+ // Close the channel as all disposal is done
+ if (finChannel != null && externalChannel) {
+ Tcf.getChannelManager().closeChannel(finChannel);
+ }
+ }
+ }, new CallbackInvocationDelegate());
+
+ if (streamsListener != null) {
+ // Dispose the streams listener
+ if (streamsListener instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ streamsListener = null;
+ }
+
+ // Dispose the processes listener if created
+ if (processesListener != null) {
+ // Dispose the processes listener
+ if (processesListener instanceof ProcessProcessesListener) {
+ ((ProcessProcessesListener)processesListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ }
+ processesListener = null;
+ // Remove the processes listener from the processes service
+ getSvcProcesses().removeListener(processesListener);
+ }
+
+ // Dispose the streams proxy if created
+ if (streamsProxy != null) {
+ streamsProxy.dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector));
+ streamsProxy = null;
+ }
+ // Mark the collector initialization as done
+ collector.initDone();
+
+ // Dissociate the channel
+ channel = null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#terminate()
+ */
+ @Override
+ public void terminate() {
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (processContext != null && processContext.canTerminate()) {
+ // Try to terminate the process the usual way first (sending SIGTERM)
+ processContext.terminate(new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ onTerminateDone(processContext, error);
+ }
+ });
+ }
+
+ }
+ };
+
+ if (Protocol.isDispatchThread()) {
+ runnable.run();
+ }
+ else {
+ Protocol.invokeAndWait(runnable);
+ }
+ }
+
+ /**
+ * Check if the process context really died after sending SIGTERM.
+ * <p>
+ * Called from {@link #terminate()}.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @param error The exception in case {@link #terminate()} returned with an error or <code>null</code>.
+ */
+ protected void onTerminateDone(IProcesses.ProcessContext context, Exception error) {
+ Assert.isNotNull(context);
+
+ // If the terminate of the remote process context failed, give a warning to the user
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processTerminateFailed, context.getName());
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ // No error from terminate, this does not mean that the process went down
+ // really -> SIGTERM might have been ignored from the process!
+ else {
+ final IProcesses.ProcessContext finContext = context;
+ // Let's see if we can still get information about the context
+ getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
+ @Override
+ public void doneGetContext(IToken token, Exception error, ProcessContext context) {
+ // In case there is no error and we do get back an process context,
+ // the process must be still running, having ignored the SIGTERM.
+ if (error == null && context != null && context.getID().equals(finContext.getID())) {
+ // Let's send a SIGHUP next.
+ getSvcProcesses().signal(context.getID(), 15, new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ onSignalSIGHUPDone(finContext, error);
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Check if the process context died after sending SIGHUP.
+ * <p>
+ * Called from {@link #onTerminateDone(IProcesses.ProcessContext, Exception)}.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @param error The exception in case sending the signal returned with an error or <code>null</code>.
+ */
+ protected void onSignalSIGHUPDone(IProcesses.ProcessContext context, Exception error) {
+ Assert.isNotNull(context);
+
+ // If the terminate of the remote process context failed, give a warning to the user
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGHUP(15)", context.getName()); //$NON-NLS-1$
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ // No error from terminate, this does not mean that the process went down
+ // really -> SIGTERM might have been ignored from the process!
+ else {
+ final IProcesses.ProcessContext finContext = context;
+ // Let's see if we can still get information about the context
+ getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() {
+ @Override
+ public void doneGetContext(IToken token, Exception error, ProcessContext context) {
+ // In case there is no error and we do get back an process context,
+ // the process must be still running, having ignored the SIGHUP.
+ if (error == null && context != null && context.getID().equals(finContext.getID())) {
+ // Finally send a SIGKILL.
+ getSvcProcesses().signal(context.getID(), 9, new IProcesses.DoneCommand() {
+ @Override
+ public void doneCommand(IToken token, Exception error) {
+ if (error != null) {
+ String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGKILL(15)", finContext.getName()); //$NON-NLS-1$
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, error.getLocalizedMessage());
+
+ IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status);
+
+ // Dispose the launcher directly
+ dispose();
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
+ */
+ @Override
+ public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) {
+ Assert.isNotNull(peer);
+ Assert.isNotNull(properties);
+
+ // Normalize the callback
+ if (callback == null) {
+ this.callback = new Callback() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
+ */
+ @Override
+ public void internalDone(Object caller, IStatus status) {
+ }
+ };
+ }
+ else {
+ this.callback = callback;
+ }
+
+ // Remember the process properties
+ this.properties = properties;
+
+ // Open a dedicated channel to the given peer
+ Map<String, Boolean> flags = new HashMap<String, Boolean>();
+ flags.put(IChannelManager.FLAG_FORCE_NEW, Boolean.TRUE);
+ if (channel != null && externalChannel) {
+ onChannelOpenDone(peer);
+ }
+ else {
+ Tcf.getChannelManager().openChannel(peer, flags, new IChannelManager.DoneOpenChannel() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel)
+ */
+ @Override
+ public void doneOpenChannel(Throwable error, IChannel channel) {
+ if (error == null) {
+ ProcessLauncher.this.channel = channel;
+ onChannelOpenDone(peer);
+ } else {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
+ error);
+ invokeCallback(status, null);
+ }
+ }
+ });
+ }
+ }
+
+ protected void onChannelOpenDone(final IPeer peer) {
+ Protocol.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ // Attach a channel listener so we can dispose ourself if the channel
+ // is closed from the remote side.
+ channel.addChannelListener(new IChannelListener() {
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened()
+ */
+ @Override
+ public void onChannelOpened() {
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable)
+ */
+ @Override
+ public void onChannelClosed(Throwable error) {
+ if (error != null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()),
+ error);
+ invokeCallback(status, null);
+ }
+ }
+ /* (non-Javadoc)
+ * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int)
+ */
+ @Override
+ public void congestionLevel(int level) {
+ }
+ });
+
+
+ // Check if the channel is in connected state
+ if (channel.getState() != IChannel.STATE_OPEN) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ Messages.ProcessLauncher_error_channelNotConnected,
+ new IllegalStateException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Do some very basic sanity checking on the process properties
+ if (properties.getStringProperty(PROP_PROCESS_PATH) == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ Messages.ProcessLauncher_error_missingProcessPath,
+ new IllegalArgumentException(PROP_PROCESS_PATH));
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Get the process and streams services
+ svcProcesses = channel.getRemoteService(IProcesses.class);
+ if (svcProcesses == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IProcesses.class.getName()),
+ null);
+
+ invokeCallback(status, null);
+ return;
+ }
+
+ svcStreams = channel.getRemoteService(IStreams.class);
+ if (svcStreams == null) {
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IStreams.class.getName()),
+ null);
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Execute the launch now
+ executeLaunch();
+ }
+ });
+ }
+
+ /**
+ * Executes the launch of the remote process.
+ */
+ protected void executeLaunch() {
+ // Get the process properties container
+ final IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // If a console should be associated, a streams listener needs to be created
+ if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)
+ || properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
+ // Create the streams listener
+ streamsListener = createStreamsListener();
+ // If available, we need to subscribe to the streams.
+ if (streamsListener != null) {
+ getSvcStreams().subscribe(IProcesses.NAME, streamsListener, new IStreams.DoneSubscribe() {
+ @Override
+ public void doneSubscribe(IToken token, Exception error) {
+ // In case the subscribe to the stream fails, we pass on
+ // the error to the user and stop the launch
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, Messages.ProcessLauncher_cause_subscribeFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Initialize the console or output file
+ onSubscribeStreamsDone();
+ }
+ }
+ });
+ } else {
+ // No streams to attach to -> go directly to the process launch
+ onAttachStreamsDone();
+ }
+ } else {
+ // No streams to attach to -> go directly to the process launch
+ onAttachStreamsDone();
+ }
+ }
+
+ /**
+ * Initialize and attach the output console and/or the output file.
+ * <p>
+ * Called from {@link IStreams#subscribe(String, org.eclipse.tcf.services.IStreams.StreamsListener, org.eclipse.tcf.services.IStreams.DoneSubscribe)}.
+ */
+ protected void onSubscribeStreamsDone() {
+ // Get the process properties container
+ IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // The streams got subscribed, check if we shall attach the console
+ if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)) {
+ // If no specific streams proxy is set, the output redirection will default
+ // to the standard terminals console view
+ if (streamsProxy == null) {
+ // Register the notification listener to listen to the console disposal
+ eventListener = new ProcessLauncherEventListener(this);
+ EventManager.getInstance().addEventListener(eventListener, DisposedEvent.class);
+
+ // Get the terminal service
+ ITerminalService terminal = ServiceManager.getInstance().getService(ITerminalService.class);
+ // If not available, we cannot fulfill this request
+ if (terminal != null) {
+ // Create the terminal streams settings
+ PropertiesContainer props = new PropertiesContainer();
+ props.setProperty(ITerminalsConnectorConstants.PROP_CONNECTOR_TYPE_ID, "org.eclipse.tcf.te.ui.terminals.type.streams"); //$NON-NLS-1$
+ props.setProperty(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tcf.te.ui.terminals.TerminalsView"); //$NON-NLS-1$
+ // Set the terminal tab title
+ String terminalTitle = getTerminalTitle();
+ if (terminalTitle != null) {
+ props.setProperty(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle);
+ }
+
+ // Create and store the streams which will be connected to the terminals stdin
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
+ // Create and store the streams the terminal will see as stdout
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
+ // Create and store the streams the terminal will see as stderr
+ props.setProperty(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
+
+ // Copy the terminal properties
+ props.setProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO));
+ props.setProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR, properties.getStringProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR));
+
+ // The custom data object is the process launcher itself
+ props.setProperty(ITerminalsConnectorConstants.PROP_DATA, this);
+
+ // Open the console
+ terminal.openConsole(props, null);
+ }
+ } else {
+ // Create and connect the streams which will be connected to the terminals stdin
+ streamsProxy.connectInputStreamMonitor(connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID }));
+ // Create and store the streams the terminal will see as stdout
+ streamsProxy.connectOutputStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }));
+ // Create and store the streams the terminal will see as stderr
+ streamsProxy.connectErrorStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }));
+ }
+ }
+
+ // The streams got subscribed, check if we shall configure the output redirection to a file
+ if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) {
+ // Get the file name where to redirect the process output to
+ String filename = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE);
+ try {
+ // Create the receiver instance. If the file already exist, we
+ // overwrite the file content.
+ StreamsDataReceiver receiver = new StreamsDataReceiver(new BufferedWriter(new FileWriter(filename)),
+ new String[] { IProcesses.PROP_STDOUT_ID, IProcesses.PROP_STDERR_ID });
+ // Register the receiver to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
+ }
+ } catch (IOException e) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ e.getLocalizedMessage() != null ? e.getLocalizedMessage() : Messages.ProcessLauncher_cause_ioexception);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e);
+ invokeCallback(status, null);
+ }
+ }
+
+ // Launch the process
+ onAttachStreamsDone();
+ }
+
+ /**
+ * Returns the terminal title string.
+ * <p>
+ * The default implementation constructs a title like &quot;<process> (Start time) [channel name]&quot;.
+ *
+ * @return The terminal title string or <code>null</code>.
+ */
+ protected String getTerminalTitle() {
+ if (properties == null) {
+ return null;
+ }
+
+ StringBuilder title = new StringBuilder();
+
+ IPath processPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH));
+ IPath monitoredProcessPath = null;
+ if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH) != null) {
+ monitoredProcessPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH));
+ }
+
+ // In case, we do have a monitored process path here, we construct the title
+ // as <monitor_app_basename>: <monitored_app>"
+ if (monitoredProcessPath != null) {
+ title.append(processPath.lastSegment());
+ title.append(": "); //$NON-NLS-1$
+ processPath = monitoredProcessPath;
+ }
+
+ // Avoid very long terminal title's by shortening the path if it has more than 3 segments
+ if (processPath.segmentCount() > 3) {
+ title.append(".../"); //$NON-NLS-1$
+ title.append(processPath.lastSegment());
+ } else {
+ title.append(processPath.toString());
+ }
+
+ // Get the peer name
+ final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(IProcessLauncher.PROP_CONNECTION_NAME));
+ if (peerName.get() == null) {
+ // Query the peer from the open channel
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (channel != null) {
+ peerName.set(channel.getRemotePeer().getName());
+ }
+ }
+ };
+
+ if (Protocol.isDispatchThread()) {
+ runnable.run();
+ }
+ else {
+ Protocol.invokeAndWait(runnable);
+ }
+ }
+
+ if (peerName.get() != null) {
+ title.append(" [").append(peerName.get()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
+ String date = format.format(new Date(System.currentTimeMillis()));
+ title.append(" (").append(date).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ return title.toString();
+ }
+
+ /**
+ * Connects the given stream id's to a local {@link InputStream} instance.
+ *
+ * @param streamsListener The streams listener. Must not be <code>null</code>.
+ * @param streamIds The stream id's. Must not be <code>null</code>.
+ *
+ * @return The local input stream instance or <code>null</code>.
+ */
+ protected InputStream connectRemoteInputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
+ Assert.isNotNull(streamsListener);
+ Assert.isNotNull(streamIds);
+
+ InputStream stream = null;
+
+ // Create the output stream receiving the data from remote
+ PipedOutputStream remoteStreamDataReceiverStream = new PipedOutputStream();
+ // Create the piped input stream instance
+ try { stream = new PipedInputStream(remoteStreamDataReceiverStream); } catch (IOException e) { /* ignored on purpose */ }
+
+ // If the input stream creation succeeded, connect the data receiver
+ if (stream != null) {
+ StreamsDataReceiver receiver = new StreamsDataReceiver(new OutputStreamWriter(remoteStreamDataReceiverStream), streamIds);
+ // Register the data receiver to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver);
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * Connects the given stream id's to a local {@link OutputStream} instance.
+ *
+ * @param streamsListener The streams listener. Must not be <code>null</code>.
+ * @param streamIds The stream id's. Must not be <code>null</code>.
+ *
+ * @return The local output stream instance or <code>null</code>.
+ */
+ protected OutputStream connectRemoteOutputStream(IStreams.StreamsListener streamsListener, String[] streamIds) {
+ Assert.isNotNull(streamsListener);
+ Assert.isNotNull(streamIds);
+
+ PipedInputStream inStream = null;
+
+ // Create the output stream receiving the data from local
+ PipedOutputStream stream = new PipedOutputStream();
+ // Create the piped input stream instance
+ try { inStream = new PipedInputStream(stream); } catch (IOException e) { stream = null; }
+
+ // If the stream creation succeeded, connect the data provider
+ if (stream != null && inStream != null) {
+ StreamsDataProvider provider = new StreamsDataProvider(new InputStreamReader(inStream), streamIds);
+ // Register the data provider to the streams listener
+ if (getStreamsListener() instanceof ProcessStreamsListener) {
+ ((ProcessStreamsListener)getStreamsListener()).setDataProvider(provider);
+ }
+ }
+
+ return stream;
+ }
+
+ /**
+ * Queries the initial new process environment from remote.
+ */
+ protected void onAttachStreamsDone() {
+ // Query the default environment for a new process
+ getSvcProcesses().getEnvironment(new IProcesses.DoneGetEnvironment() {
+ @Override
+ public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) {
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = Messages.ProcessLauncher_error_getEnvironmentFailed;
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Initiate the process launch
+ onGetEnvironmentDone(environment);
+ }
+ }
+ });
+ }
+
+ /**
+ * Initiate the process launch.
+ * <p>
+ * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}.
+ */
+ protected void onGetEnvironmentDone(final Map<String, String> environment) {
+ // Get the process properties container
+ final IPropertiesContainer properties = getProperties();
+ if (properties == null) {
+ // This is an illegal argument. Properties must be set
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(),
+ NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$
+ new IllegalArgumentException());
+ invokeCallback(status, null);
+ return;
+ }
+
+ // Create the process listener
+ processesListener = createProcessesListener();
+ if (processesListener != null) {
+ getSvcProcesses().addListener(processesListener);
+ }
+
+ // Get the process attributes
+ String processPath = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH);
+
+ String[] processArgs = (String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS);
+ // Assure that the first argument is the process path itself
+ if (!(processArgs != null && processArgs.length > 0 && processPath.equals(processArgs[0]))) {
+ // Prepend the process path to the list of arguments
+ List<String> args = processArgs != null ? new ArrayList<String>(Arrays.asList(processArgs)) : new ArrayList<String>();
+ args.add(0, processPath);
+ processArgs = args.toArray(new String[args.size()]);
+ }
+
+ String processCWD = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_CWD);
+ // If the process working directory is not explicitly set, default to the process path directory
+ if (processCWD == null || "".equals(processCWD.trim())) { //$NON-NLS-1$
+ processCWD = new Path(processPath).removeLastSegments(1).toString();
+ }
+
+ // Merge the initial process environment and the desired process environment
+ Map<String, String> processEnv = new HashMap<String, String>(environment);
+ Map<String, String> processEnvDiff = (Map<String, String>)properties.getProperty(IProcessLauncher.PROP_PROCESS_ENV);
+ if (processEnvDiff != null && !processEnvDiff.isEmpty()) {
+ processEnv.putAll(processEnvDiff);
+ }
+ // Assure that the TERM variable is set to "ansi"
+ processEnv.put("TERM", "ansi"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ boolean attach = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ATTACH);
+
+ // Launch the remote process
+ getSvcProcesses().start(processCWD, processPath, processArgs, processEnv, attach, new IProcesses.DoneStart() {
+ @Override
+ public void doneStart(IToken token, Exception error, ProcessContext process) {
+ if (error != null) {
+ // Construct the error message to show to the user
+ String message = NLS.bind(Messages.ProcessLauncher_error_processLaunchFailed,
+ properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH),
+ makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS)));
+ message += NLS.bind(Messages.ProcessLauncher_error_possibleCause,
+ error.getLocalizedMessage() != null ? error.getLocalizedMessage() : Messages.ProcessLauncher_cause_startFailed);
+
+ // Construct the status object
+ IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error);
+ invokeCallback(status, null);
+ } else {
+ // Register the process context to the listener
+ onProcessLaunchDone(process);
+ }
+ }
+ });
+ }
+
+ /**
+ * Register the process context to the listeners.
+ *
+ * @param process The process context or <code>null</code>.
+ */
+ protected void onProcessLaunchDone(ProcessContext process) {
+ // Register the process context with the listeners
+ if (process != null) {
+ if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_PROCESS_LAUNCHER)) {
+ CoreBundleActivator.getTraceHandler().trace("Process context created: id='" + process.getID() + "', name='" + process.getName() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ 0, ITraceIds.TRACE_PROCESS_LAUNCHER, IStatus.INFO, getClass());
+ }
+
+ // Remember the process context
+ processContext = process;
+
+ // Push the process context to the listeners
+ if (getStreamsListener() instanceof IProcessContextAwareListener) {
+ ((IProcessContextAwareListener)getStreamsListener()).setProcessContext(process);
+ }
+ if (getProcessesListener() instanceof IProcessContextAwareListener) {
+ ((IProcessContextAwareListener)getProcessesListener()).setProcessContext(process);
+ }
+
+ // Send a notification
+ ProcessStateChangeEvent event = createRemoteProcessStateChangeEvent(process);
+ if (event != null) {
+ EventManager.getInstance().fireEvent(event);
+ }
+ }
+
+ // Invoke the callback to signal that we are done
+ invokeCallback(Status.OK_STATUS, process);
+ }
+
+ /**
+ * Creates a new remote process state change event instance.
+ *
+ * @param context The process context. Must not be <code>null</code>.
+ * @return The event instance or <code>null</code>.
+ */
+ protected ProcessStateChangeEvent createRemoteProcessStateChangeEvent(IProcesses.ProcessContext context) {
+ Assert.isNotNull(context);
+ return new ProcessStateChangeEvent(context, ProcessStateChangeEvent.EVENT_PROCESS_CREATED, Boolean.FALSE, Boolean.TRUE, -1);
+ }
+
+ /**
+ * Invoke the callback with the given parameters. If the given status severity
+ * is {@link IStatus#ERROR}, the process launcher object is disposed automatically.
+ *
+ * @param status The status. Must not be <code>null</code>.
+ * @param result The result object or <code>null</code>.
+ */
+ protected void invokeCallback(IStatus status, Object result) {
+ // Dispose the process launcher if we report an error
+ if (status.getSeverity() == IStatus.ERROR) {
+ dispose();
+ }
+
+ // Invoke the callback
+ ICallback callback = getCallback();
+ if (callback != null) {
+ callback.setResult(result);
+ callback.done(this, status);
+ }
+ }
+
+ /**
+ * Returns the channel instance.
+ *
+ * @return The channel instance or <code>null</code> if none.
+ */
+ public final IChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * Returns the process properties container.
+ *
+ * @return The process properties container or <code>null</code> if none.
+ */
+ public final IPropertiesContainer getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns the processes service instance.
+ *
+ * @return The processes service instance or <code>null</code> if none.
+ */
+ public final IProcesses getSvcProcesses() {
+ return svcProcesses;
+ }
+
+ /**
+ * Returns the streams service instance.
+ *
+ * @return The streams service instance or <code>null</code> if none.
+ */
+ public final IStreams getSvcStreams() {
+ return svcStreams;
+ }
+
+ /**
+ * Returns the callback instance.
+ *
+ * @return The callback instance or <code>null</code> if none.
+ */
+ protected final ICallback getCallback() {
+ return callback;
+ }
+
+ /**
+ * Create the streams listener instance.
+ *
+ * @return The streams listener instance or <code>null</code> if none.
+ */
+ protected IStreams.StreamsListener createStreamsListener() {
+ return new ProcessStreamsListener(this);
+ }
+
+ /**
+ * Returns the streams listener instance.
+ *
+ * @return The streams listener instance or <code>null</code>.
+ */
+ protected final IStreams.StreamsListener getStreamsListener() {
+ return streamsListener;
+ }
+
+ /**
+ * Create the processes listener instance.
+ *
+ * @return The processes listener instance or <code>null</code> if none.
+ */
+ protected IProcesses.ProcessesListener createProcessesListener() {
+ return new ProcessProcessesListener(this);
+ }
+
+ /**
+ * Returns the processes listener instance.
+ *
+ * @return The processes listener instance or <code>null</code> if none.
+ */
+ protected final IProcesses.ProcessesListener getProcessesListener() {
+ return processesListener;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class)
+ */
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (adapter.isAssignableFrom(IProcesses.ProcessesListener.class)) {
+ return processesListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) {
+ return streamsListener;
+ }
+ else if (adapter.isAssignableFrom(IStreams.class)) {
+ return svcStreams;
+ }
+ else if (adapter.isAssignableFrom(IProcesses.class)) {
+ return svcProcesses;
+ }
+ else if (adapter.isAssignableFrom(IChannel.class)) {
+ return channel;
+ }
+ else if (adapter.isAssignableFrom(IPropertiesContainer.class)) {
+ return properties;
+ }
+ else if (adapter.isAssignableFrom(IProcesses.ProcessContext.class)) {
+ return processContext;
+ }
+ else if (adapter.isAssignableFrom(this.getClass())) {
+ return this;
+ }
+
+
+ return super.getAdapter(adapter);
+ }
+
+ /**
+ * Makes a space separated string from the given array.
+ *
+ * @param array The string array or <code>null</code>.
+ * @return The string.
+ */
+ String makeString(String[] array) {
+ if (array == null) {
+ return ""; //$NON-NLS-1$
+ }
+ StringBuilder result = new StringBuilder();
+ for (String element : array) {
+ if (result.length() > 0) {
+ result.append(' ');
+ }
+ result.append(element);
+ }
+ return result.toString();
+ }
+}