mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-23 14:42:11 +02:00
bug 143417 - Fix notifications for lost ssh sessions
This commit is contained in:
parent
a8614a0cd3
commit
49c9ea405a
6 changed files with 332 additions and 31 deletions
|
@ -14,6 +14,7 @@ package org.eclipse.rse.connectorservice.ssh;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
|
@ -22,17 +23,32 @@ import org.eclipse.core.runtime.NullProgressMonitor;
|
|||
import org.eclipse.core.runtime.OperationCanceledException;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
|
||||
import org.eclipse.jface.operation.IRunnableContext;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.rse.core.SystemBasePlugin;
|
||||
import org.eclipse.rse.core.subsystems.AbstractConnectorService;
|
||||
import org.eclipse.rse.core.subsystems.CommunicationsEvent;
|
||||
import org.eclipse.rse.core.subsystems.IConnectorService;
|
||||
import org.eclipse.rse.core.subsystems.SubSystemConfiguration;
|
||||
import org.eclipse.rse.model.IHost;
|
||||
import org.eclipse.rse.model.ISystemRegistry;
|
||||
import org.eclipse.rse.services.clientserver.messages.SystemMessage;
|
||||
import org.eclipse.rse.services.ssh.ISshSessionProvider;
|
||||
import org.eclipse.rse.ui.ISystemMessages;
|
||||
import org.eclipse.rse.ui.RSEUIPlugin;
|
||||
import org.eclipse.rse.ui.messages.SystemMessageDialog;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
|
||||
import org.eclipse.team.internal.ccvs.core.util.Util;
|
||||
import org.eclipse.team.internal.ccvs.ssh2.CVSSSH2Plugin;
|
||||
import org.eclipse.team.internal.ccvs.ssh2.ISSHContants;
|
||||
import org.eclipse.team.internal.ccvs.ui.KeyboardInteractiveDialog;
|
||||
import org.eclipse.team.internal.ccvs.ui.UserValidationDialog;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
|
@ -52,6 +68,7 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
|
|||
private static final int SSH_DEFAULT_PORT = 22;
|
||||
private static JSch jsch=new JSch();
|
||||
private Session session;
|
||||
private SessionLostHandler fSessionLostHandler;
|
||||
|
||||
public SshConnectorService(IHost host) {
|
||||
//TODO the port parameter doesnt really make sense here since
|
||||
|
@ -59,6 +76,7 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
|
|||
//setPort() on our base class -- I assume the port is meant to
|
||||
//be a local port.
|
||||
super("SSH Connector Service", "SSH Connector Service Description", host, 0);
|
||||
fSessionLostHandler = null;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
@ -243,35 +261,61 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
|
|||
session.setTimeout(getSshTimeoutInMillis());
|
||||
String password = getPasswordInformation().getPassword();
|
||||
session.setPassword(password);
|
||||
MyUserInfo ui = new MyUserInfo(user, password);
|
||||
session.setUserInfo(ui);
|
||||
MyUserInfo userInfo = new MyUserInfo(user, password);
|
||||
session.setUserInfo(userInfo);
|
||||
session.setSocketFactory(new ResponsiveSocketFacory(monitor));
|
||||
|
||||
//java.util.Hashtable config=new java.util.Hashtable();
|
||||
//config.put("StrictHostKeyChecking", "no");
|
||||
//session.setConfig(config);
|
||||
ui.aboutToConnect();
|
||||
userInfo.aboutToConnect();
|
||||
try {
|
||||
Activator.trace("connecting..."); //$NON-NLS-1$
|
||||
Activator.trace("SshConnectorService.connecting..."); //$NON-NLS-1$
|
||||
session.connect();
|
||||
Activator.trace("connected"); //$NON-NLS-1$
|
||||
Activator.trace("SshConnectorService.connected"); //$NON-NLS-1$
|
||||
} catch (JSchException e) {
|
||||
Activator.trace("connect failed: "+e.toString()); //$NON-NLS-1$
|
||||
Activator.trace("SshConnectorService.connect failed: "+e.toString()); //$NON-NLS-1$
|
||||
if (session.isConnected())
|
||||
session.disconnect();
|
||||
throw e;
|
||||
}
|
||||
ui.connectionMade();
|
||||
userInfo.connectionMade();
|
||||
fSessionLostHandler = new SessionLostHandler(this);
|
||||
notifyConnection();
|
||||
}
|
||||
|
||||
public void internalDisconnect(IProgressMonitor monitor)
|
||||
public void internalDisconnect(IProgressMonitor monitor) throws Exception
|
||||
{
|
||||
//TODO: Check, Is disconnect being called because the network (connection) went down?
|
||||
//TODO: Fire communication event (aboutToDisconnect) -- see DStoreConnectorService.internalDisconnect()
|
||||
//TODO: Wrap exception in an InvocationTargetException -- see DStoreConnectorService.internalDisconnect()
|
||||
//Will services like the sftp service be disconnected too? Or notified?
|
||||
Activator.trace("disconnect"); //$NON-NLS-1$
|
||||
session.disconnect();
|
||||
//TODO Will services like the sftp service be disconnected too? Or notified?
|
||||
Activator.trace("SshConnectorService.disconnect"); //$NON-NLS-1$
|
||||
try
|
||||
{
|
||||
if (session != null) {
|
||||
// Is disconnect being called because the network (connection) went down?
|
||||
if (fSessionLostHandler != null && fSessionLostHandler.isSessionLost()) {
|
||||
notifyError();
|
||||
}
|
||||
else {
|
||||
// Fire comm event to signal state about to change
|
||||
fireCommunicationsEvent(CommunicationsEvent.BEFORE_DISCONNECT);
|
||||
}
|
||||
|
||||
session.disconnect();
|
||||
|
||||
// Fire comm event to signal state changed
|
||||
notifyDisconnection();
|
||||
//TODO MOB - keep the session to avoid NPEs in services (disables gc for the session!)
|
||||
// session = null;
|
||||
fSessionLostHandler = null;
|
||||
// DKM - no need to clear uid cache
|
||||
clearPasswordCache(false); // clear in-memory password
|
||||
//clearUserIdCache(); // Clear any cached local user IDs
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new java.lang.reflect.InvocationTargetException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO avoid having jsch type "Session" in the API.
|
||||
|
@ -282,7 +326,206 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
|
|||
return session;
|
||||
}
|
||||
|
||||
private static Display getStandardDisplay() {
|
||||
/**
|
||||
* Handle session-lost events.
|
||||
* This is generic for any sort of connector service.
|
||||
* Most of this is extracted from dstore's ConnectionStatusListener.
|
||||
*
|
||||
* TODO should be refactored to make it generally available, and allow
|
||||
* dstore to derive from it.
|
||||
*/
|
||||
public static class SessionLostHandler implements Runnable, IRunnableWithProgress
|
||||
{
|
||||
private IConnectorService _connection;
|
||||
private boolean fSessionLost;
|
||||
|
||||
public SessionLostHandler(IConnectorService cs)
|
||||
{
|
||||
_connection = cs;
|
||||
fSessionLost = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that the connection has been lost. This may be called
|
||||
* multiple times from multiple subsystems. The SessionLostHandler
|
||||
* ensures that actual user feedback and disconnect actions are
|
||||
* done only once, on the first invocation.
|
||||
*/
|
||||
public void sessionLost()
|
||||
{
|
||||
//avoid duplicate execution of sessionLost
|
||||
boolean showSessionLostDlg=false;
|
||||
synchronized(this) {
|
||||
if (!fSessionLost) {
|
||||
fSessionLost = true;
|
||||
showSessionLostDlg=true;
|
||||
}
|
||||
}
|
||||
if (showSessionLostDlg) {
|
||||
Display.getDefault().asyncExec(this);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isSessionLost() {
|
||||
return fSessionLost;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
Shell shell = getShell();
|
||||
//TODO need a more correct message for "session lost"
|
||||
//TODO allow users to reconnect from this dialog
|
||||
//SystemMessage msg = RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_CONNECT_UNKNOWNHOST);
|
||||
SystemMessage msg = RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_CONNECT_CANCELLED);
|
||||
msg.makeSubstitution(_connection.getPrimarySubSystem().getHost().getAliasName());
|
||||
SystemMessageDialog dialog = new SystemMessageDialog(getShell(), msg);
|
||||
dialog.open();
|
||||
try
|
||||
{
|
||||
IRunnableContext runnableContext = getRunnableContext(getShell());
|
||||
// will do this.run(IProgressMonitor mon)
|
||||
runnableContext.run(false,true,this); // inthread, cancellable, IRunnableWithProgress
|
||||
_connection.reset();
|
||||
ISystemRegistry sr = RSEUIPlugin.getDefault().getSystemRegistry();
|
||||
sr.connectedStatusChange(_connection.getPrimarySubSystem(), false, true, true);
|
||||
}
|
||||
catch (InterruptedException exc) // user cancelled
|
||||
{
|
||||
if (shell != null)
|
||||
showDisconnectCancelledMessage(shell, _connection.getHostName(), _connection.getPort());
|
||||
}
|
||||
catch (java.lang.reflect.InvocationTargetException invokeExc) // unexpected error
|
||||
{
|
||||
Exception exc = (Exception)invokeExc.getTargetException();
|
||||
if (shell != null)
|
||||
showDisconnectErrorMessage(shell, _connection.getHostName(), _connection.getPort(), exc);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SystemBasePlugin.logError("ConnectionStatusListener: Error disconnecting", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void run(IProgressMonitor monitor)
|
||||
throws InvocationTargetException, InterruptedException
|
||||
{
|
||||
String message = null;
|
||||
message = SubSystemConfiguration.getDisconnectingMessage(
|
||||
_connection.getHostName(), _connection.getPort());
|
||||
monitor.beginTask(message, IProgressMonitor.UNKNOWN);
|
||||
try {
|
||||
_connection.disconnect(monitor);
|
||||
} catch (Exception exc) {
|
||||
if (exc instanceof java.lang.reflect.InvocationTargetException)
|
||||
throw (java.lang.reflect.InvocationTargetException) exc;
|
||||
if (exc instanceof java.lang.InterruptedException)
|
||||
throw (java.lang.InterruptedException) exc;
|
||||
throw new java.lang.reflect.InvocationTargetException(exc);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
|
||||
public Shell getShell() {
|
||||
Shell activeShell = SystemBasePlugin.getActiveWorkbenchShell();
|
||||
if (activeShell != null) {
|
||||
return activeShell;
|
||||
}
|
||||
|
||||
IWorkbenchWindow window = null;
|
||||
try {
|
||||
window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
if (window == null) {
|
||||
IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
|
||||
.getWorkbenchWindows();
|
||||
if (windows != null && windows.length > 0) {
|
||||
return windows[0].getShell();
|
||||
}
|
||||
} else {
|
||||
return window.getShell();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progress monitor dialog for this operation. We try to use one
|
||||
* for all phases of a single operation, such as connecting and
|
||||
* resolving.
|
||||
*/
|
||||
protected IRunnableContext getRunnableContext(Shell rshell) {
|
||||
Shell shell = getShell();
|
||||
// for other cases, use statusbar
|
||||
IWorkbenchWindow win = SystemBasePlugin.getActiveWorkbenchWindow();
|
||||
if (win != null) {
|
||||
Shell winShell = RSEUIPlugin.getDefault().getWorkbench()
|
||||
.getActiveWorkbenchWindow().getShell();
|
||||
if (winShell != null && !winShell.isDisposed()
|
||||
&& winShell.isVisible()) {
|
||||
SystemBasePlugin
|
||||
.logInfo("Using active workbench window as runnable context");
|
||||
shell = winShell;
|
||||
return win;
|
||||
} else {
|
||||
win = null;
|
||||
}
|
||||
}
|
||||
if (shell == null || shell.isDisposed() || !shell.isVisible()) {
|
||||
SystemBasePlugin
|
||||
.logInfo("Using progress monitor dialog with given shell as parent");
|
||||
shell = rshell;
|
||||
}
|
||||
IRunnableContext dlg = new ProgressMonitorDialog(rshell);
|
||||
return dlg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message when the disconnection fails. Shows a common
|
||||
* message by default. Overridable.
|
||||
*/
|
||||
protected void showDisconnectErrorMessage(Shell shell, String hostName, int port, Exception exc)
|
||||
{
|
||||
//SystemMessage.displayMessage(SystemMessage.MSGTYPE_ERROR,shell,RSEUIPlugin.getResourceBundle(),
|
||||
// ISystemMessages.MSG_DISCONNECT_FAILED,
|
||||
// hostName, exc.getMessage());
|
||||
//RSEUIPlugin.logError("Disconnect failed",exc); // temporary
|
||||
SystemMessageDialog msgDlg = new SystemMessageDialog(shell,
|
||||
RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_DISCONNECT_FAILED).makeSubstitution(hostName,exc));
|
||||
msgDlg.setException(exc);
|
||||
msgDlg.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message when the user cancels the disconnection.
|
||||
* Shows a common message by default.
|
||||
* Overridable.
|
||||
*/
|
||||
protected void showDisconnectCancelledMessage(Shell shell, String hostName, int port)
|
||||
{
|
||||
//SystemMessage.displayMessage(SystemMessage.MSGTYPE_ERROR, shell, RSEUIPlugin.getResourceBundle(),
|
||||
// ISystemMessages.MSG_DISCONNECT_CANCELLED, hostName);
|
||||
SystemMessageDialog msgDlg = new SystemMessageDialog(shell,
|
||||
RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_DISCONNECT_CANCELLED).makeSubstitution(hostName));
|
||||
msgDlg.open();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Notification from sub-services that our session was lost.
|
||||
* Notify all subsystems properly.
|
||||
* TODO allow user to try and reconnect?
|
||||
*/
|
||||
public void handleSessionLost() {
|
||||
Activator.trace("SshConnectorService: handleSessionLost"); //$NON-NLS-1$
|
||||
if (fSessionLostHandler!=null) {
|
||||
fSessionLostHandler.sessionLost();
|
||||
}
|
||||
}
|
||||
|
||||
private static Display getStandardDisplay() {
|
||||
Display display = Display.getCurrent();
|
||||
if( display==null ) {
|
||||
display = Display.getDefault();
|
||||
|
@ -404,7 +647,14 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
|
|||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return (session!=null && session.isConnected());
|
||||
if (session!=null) {
|
||||
if (session.isConnected()) {
|
||||
return true;
|
||||
} else if (fSessionLostHandler!=null) {
|
||||
fSessionLostHandler.sessionLost();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasRemoteServerLauncherProperties() {
|
||||
|
|
|
@ -25,23 +25,26 @@ __Usage:__
|
|||
Window > Preferences > Team > CVS > SSh2 Connection Method > General
|
||||
to set the ssh home directory, and private key types to be used.
|
||||
* Select the "Shells" node and choose Contextmenu > Launch Shell.
|
||||
* Enter your username on the remote system. For the password, just
|
||||
enter anything (this is not checked, since ssh has its own method
|
||||
of acquiring password information).
|
||||
* Enter your username and password on the remote system. If you want
|
||||
to use private-key authentication, just enter any dummy password -
|
||||
you will be asked for the passphrase of your keyring later, or
|
||||
the connection will succeed without further prompting if your key
|
||||
ring has an empty passphrase.
|
||||
* When asked to accept remote host authenticity, press OK.
|
||||
* Enter the correct password for ssh on the remote system (this is only
|
||||
necessary if you are not using a private key).
|
||||
|
||||
__Known Limitations:__
|
||||
* Symbolic Links are not resolved (readlink not supported by jsch-0.1.28)
|
||||
* Ssh passwords can not be stored (no key ring; use private keys instead)
|
||||
* Ssh timeouts are not observed (no automatic reconnect)
|
||||
- after auto-logout, shell just doesnt react to input any more
|
||||
- after auto-logout, further actions will show a "connect cancel"
|
||||
message and connection will go down
|
||||
* Password and passphrase internal handling has not been checked for
|
||||
security against malicious reading from other Eclipse plugins.
|
||||
|
||||
__Known Issues:__
|
||||
* A dummy password must be entered on initial connect (can be saved)
|
||||
* A dummy password must be entered on initial connect, empty
|
||||
password should be allowed if private key authentication is used
|
||||
* After some time, the connection may freeze and need to be
|
||||
disconnected.
|
||||
* Command service should be provided in addition to the remote shell service.
|
||||
|
@ -49,14 +52,15 @@ __Known Issues:__
|
|||
due to memory exhaustion (ArrayIndexOutOfBoundsException)
|
||||
* "Break" can not be sent to the remote system in order to cancel
|
||||
long-running jobs on the remote side.
|
||||
* Moving files with a space in the name doesn't currently work.
|
||||
Renaming them works though.
|
||||
* Copy&paste, or drag&drop of remote files remotely does'nt currently work.
|
||||
* The plugin currently uses some "internal" classes from the
|
||||
org.eclipse.team.cvs.ui plugin. This needs to be cleaned up.
|
||||
* For other internal coding issues, see TODO items in the code.
|
||||
|
||||
__Changelog:__
|
||||
v0.3:
|
||||
* support Keyboard Interactive Authentication.
|
||||
* Fix interaction with RSE passwords.
|
||||
* Fix connection lost notifications
|
||||
v0.2:
|
||||
* Re-use Team/CVS/ssh2 preferences for ssh2 home and private keys specification
|
||||
Allows to do the ssh login without password if private/public key are set up.
|
||||
|
|
|
@ -4,5 +4,10 @@ import com.jcraft.jsch.Session;
|
|||
|
||||
public interface ISshSessionProvider
|
||||
{
|
||||
/* Return an active SSH session from a ConnectorService. */
|
||||
public Session getSession();
|
||||
|
||||
/* Inform the connectorService that a session has been lost. */
|
||||
public void handleSessionLost();
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ public class SftpFileService extends AbstractFileService implements IFileService
|
|||
return "Access a remote file system via Ssh / Sftp protocol";
|
||||
}
|
||||
|
||||
//TODO specify Exception more clearly
|
||||
public void connect() throws Exception {
|
||||
Activator.trace("SftpFileService.connecting..."); //$NON-NLS-1$
|
||||
try {
|
||||
|
@ -77,16 +78,35 @@ public class SftpFileService extends AbstractFileService implements IFileService
|
|||
}
|
||||
}
|
||||
|
||||
protected ChannelSftp getChannel(String task) {
|
||||
//TODO specify Exception more clearly
|
||||
protected ChannelSftp getChannel(String task) throws Exception
|
||||
{
|
||||
Activator.trace(task);
|
||||
if (fChannelSftp==null || !fChannelSftp.isConnected()) {
|
||||
Activator.trace(task + ": channel not connected: "+fChannelSftp); //$NON-NLS-1$
|
||||
Session session = fSessionProvider.getSession();
|
||||
if (session!=null) {
|
||||
if (!session.isConnected()) {
|
||||
//notify of lost session. May reconnect asynchronously later.
|
||||
fSessionProvider.handleSessionLost();
|
||||
//dont throw an exception here, expect jsch to throw something useful
|
||||
} else {
|
||||
//session connected but channel not: try to reconnect
|
||||
//(may throw Exception)
|
||||
connect();
|
||||
}
|
||||
}
|
||||
//TODO might throw NPE if session has been disconnected
|
||||
}
|
||||
return fChannelSftp;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
getChannel("SftpFileService.disconnect").disconnect(); //$NON-NLS-1$
|
||||
try {
|
||||
getChannel("SftpFileService.disconnect").disconnect(); //$NON-NLS-1$
|
||||
} catch(Exception e) {
|
||||
/*nothing to do*/
|
||||
}
|
||||
fChannelSftp = null;
|
||||
}
|
||||
|
||||
|
@ -113,7 +133,12 @@ public class SftpFileService extends AbstractFileService implements IFileService
|
|||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return getChannel("SftpFileService.isConnected()").isConnected(); //$NON-NLS-1$
|
||||
try {
|
||||
return getChannel("SftpFileService.isConnected()").isConnected(); //$NON-NLS-1$
|
||||
} catch(Exception e) {
|
||||
/*cannot be connected when we cannot get a channel*/
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected IHostFile[] internalFetch(IProgressMonitor monitor, String parentPath, String fileFilter, int fileType)
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.eclipse.rse.services.shells.IHostShellOutputReader;
|
|||
import org.eclipse.rse.services.ssh.ISshSessionProvider;
|
||||
|
||||
import com.jcraft.jsch.Channel;
|
||||
import com.jcraft.jsch.Session;
|
||||
|
||||
/**
|
||||
* A Shell subsystem for SSH.
|
||||
|
@ -62,12 +63,22 @@ public class SshHostShell extends AbstractHostShell {
|
|||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return !fChannel.isEOF();
|
||||
if (fChannel!=null && !fChannel.isEOF()) {
|
||||
return true;
|
||||
}
|
||||
// shell is not active: check for session lost
|
||||
Session session = fSessionProvider.getSession();
|
||||
if (session!=null && !session.isConnected()) {
|
||||
fSessionProvider.handleSessionLost();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void writeToShell(String command) {
|
||||
fStdinHandler.println(command);
|
||||
fStdinHandler.flush();
|
||||
if (isActive()) {
|
||||
fStdinHandler.println(command);
|
||||
fStdinHandler.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public IHostShellOutputReader getStandardOutputReader() {
|
||||
|
|
|
@ -40,6 +40,12 @@ public class SshShellOutputReader extends AbstractHostShellOutputReader
|
|||
fReader = reader;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
//check for active session and notify lost session if necessary
|
||||
getHostShell().isActive();
|
||||
}
|
||||
|
||||
protected Object internalReadLine() {
|
||||
if (fReader == null) {
|
||||
//Our workaround sets the stderr reader to null, so we never give any stderr output.
|
||||
|
|
Loading…
Add table
Reference in a new issue