blob: 2e524d97c41518816389c60aa408c5e281e7954c [file] [log] [blame]
package org.eclipse.dltk.ssh.internal.core;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Vector;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.dltk.ssh.core.ISshConnection;
import org.eclipse.dltk.ssh.core.ISshFileHandle;
import org.eclipse.jsch.core.IJSchService;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
import com.jcraft.jsch.ChannelSftp.LsEntry;
/**
* TODO: Add correct operation synchronization.
*
*/
public class SshConnection implements ISshConnection {
private long disabledTime = 0;
private final class LocalUserInfo implements UserInfo,
UIKeyboardInteractive {
public void showMessage(String arg0) {
}
public boolean promptYesNo(String arg0) {
return false;
}
public boolean promptPassword(String arg0) {
return true;
}
public boolean promptPassphrase(String arg0) {
return false;
}
public String getPassword() {
return password;
}
public String getPassphrase() {
return ""; //$NON-NLS-1$
}
public String[] promptKeyboardInteractive(String destination,
String name, String instruction, String[] prompt, boolean[] echo) {
final String p = password;
return p != null ? new String[] { p } : null;
}
}
private static abstract class Operation {
boolean finished = false;
public abstract void perform() throws JSchException, SftpException;
public void setFinished() {
finished = true;
}
public boolean isFinished() {
return finished;
}
public void failed() {
}
}
private class GetStatOperation extends Operation {
private IPath path;
private SftpATTRS attrs;
public GetStatOperation(IPath path) {
this.path = path;
}
@Override
public String toString() {
return "Get information for file:" + path; //$NON-NLS-1$
}
@Override
public void perform() throws JSchException, SftpException {
attrs = getChannel().stat(path.toString());
}
public SftpATTRS getAttrs() {
return attrs;
}
}
private class ResolveLinkOperation extends Operation {
private IPath path;
private IPath resolvedPath;
public ResolveLinkOperation(IPath path) {
this.path = path;
}
@Override
public String toString() {
return "Resolve link information for file:" + path; //$NON-NLS-1$
}
@Override
public void perform() throws JSchException, SftpException {
SftpATTRS attrs = channel.stat(path.toString());
boolean isRoot = (path.segmentCount() == 0);
String linkTarget = null;
String canonicalPath;
String parentPath = path.removeLastSegments(1).toString();
if (attrs.isLink() && !isRoot) {
try {
String fullPath = path.toString();
boolean readlinkDone = false;
try {
linkTarget = channel.readlink(fullPath);
readlinkDone = true;
} catch (Throwable t) {
channel.cd(fullPath);
linkTarget = channel.pwd();
canonicalPath = linkTarget;
}
if (linkTarget != null && !linkTarget.equals(fullPath)) {
if (readlinkDone) {
String curdir = channel.pwd();
if (!parentPath.equals(curdir)) {
channel.cd(parentPath);
}
}
SftpATTRS attrsTarget = channel.stat(linkTarget);
if (readlinkDone && attrsTarget.isDir()) {
channel.cd(fullPath);
canonicalPath = channel.pwd();
}
} else {
linkTarget = null;
}
} catch (Exception e) {
if (e instanceof SftpException
&& ((SftpException) e).id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
if (linkTarget == null) {
linkTarget = ":dangling link"; //$NON-NLS-1$
} else {
linkTarget = ":dangling link:" + linkTarget; //$NON-NLS-1$
}
}
}
resolvedPath = new Path(linkTarget);
}
}
public IPath getResolvedPath() {
return resolvedPath;
}
}
private class GetOperation extends Operation {
private IPath path;
private InputStream stream;
public GetOperation(IPath path) {
this.path = path;
}
@Override
public void perform() throws JSchException, SftpException {
stream = channel.get(path.toString());
performStreamOperation = true;
}
@Override
public String toString() {
return "Get input stream for file:" + path; //$NON-NLS-1$
}
@Override
public void failed() {
// channel.disconnect();
// channel = null;
}
public InputStream getStream() {
if (stream != null) {
InputStream wrapperStream = new BufferedInputStream(stream,
32000) {
@Override
public void close() throws IOException {
super.close();
// channel.disconnect();
// channel = null;
doneStreamOperation();
}
};
return wrapperStream;
}
return stream;
}
}
protected synchronized void doneStreamOperation() {
performStreamOperation = false;
notifyAll();
}
private class PutOperation extends Operation {
private IPath path;
private OutputStream stream;
public PutOperation(IPath path) {
this.path = path;
}
@Override
public String toString() {
return "Get output stream for file:" + path; //$NON-NLS-1$
}
@Override
public void perform() throws JSchException, SftpException {
stream = channel.put(path.toString(), ChannelSftp.OVERWRITE);
performStreamOperation = true;
}
public OutputStream getStream() {
if (stream != null) {
OutputStream wrapperStream = new BufferedOutputStream(stream,
32000) {
@Override
public void close() throws IOException {
super.close();
channel.disconnect();
channel = null;
doneStreamOperation();
}
};
return wrapperStream;
}
return stream;
}
}
private class ListFolderOperation extends Operation {
private IPath path;
private Vector<LsEntry> v;
public ListFolderOperation(IPath path) {
this.path = path;
}
@Override
public String toString() {
return "List folder:" + path + " for files"; //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
@SuppressWarnings("unchecked")
public void perform() throws JSchException, SftpException {
v = getChannel().ls(path.toString());
}
public Vector<LsEntry> getVector() {
return v;
}
}
private static final int DEFAULT_RETRY_COUNT = 2;
// private static final long TIMEOUT = 3000; // One second timeout
private Session session;
private String userName;
private String password;
private int port;
private ChannelSftp channel;
private String hostName;
public SshConnection(String userName, String hostName, int port) {
this.userName = userName;
this.hostName = hostName;
this.port = port;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.dltk.ssh.core.ISshConnection#setPassword(java.lang.String)
*/
public void setPassword(String password) {
this.password = password;
}
public boolean connect() {
return connect(0);
}
public synchronized boolean connect(int trycount) {
try {
if (session == null) {
IJSchService service = Activator.getDefault().getJSch();
session = service.createSession(hostName, port, userName);
session.setTimeout(0);
session.setServerAliveInterval(300000);
session.setServerAliveCountMax(6);
session.setPassword(password); // Set password directly
UserInfo ui = new LocalUserInfo();
session.setUserInfo(ui);
}
if (!session.isConnected()) {
session.connect(60 * 1000); // Connect with defautl timeout
}
if (channel == null) {
channel = (ChannelSftp) session.openChannel("sftp"); //$NON-NLS-1$
}
if (!channel.isConnected()) {
channel.connect();
}
} catch (JSchException e) {
String eToStr = e.toString();
boolean needLog = true;
if (eToStr.indexOf("Auth cancel") >= 0 || eToStr.indexOf("Auth fail") >= 0 || eToStr.indexOf("session is down") >= 0) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (session.isConnected()) {
session.disconnect();
session = null;
}
}
if (session != null) {
session.disconnect();
session = null;
}
if (needLog) {
Activator.error("Failed to create direct connection", e); //$NON-NLS-1$
}
}
if (session == null || !session.isConnected() || channel == null
|| !channel.isConnected()) {
if (trycount > 0) {
if (session == null || !session.isConnected()) {
session = null;
}
channel = null;
return connect(trycount - 1);
} else {
// Lets disable connection for a while.
setDisabled(1000 * 10); // 10 seconds
// seconds
}
return false;
} else {
return true;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.dltk.ssh.core.ISshConnection#disconnect()
*/
public void disconnect() {
if (channel != null && channel.isConnected()) {
channel.disconnect();
channel = null;
}
if (session != null && session.isConnected()) {
session.disconnect();
session = null;
}
}
private boolean performStreamOperation = false;
private synchronized void performOperation(final Operation op, int tryCount) {
while (performStreamOperation) {
try {
this.wait(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
connect();
op.perform();
op.setFinished();
} catch (JSchException ex) {
Activator.log(ex);
// if (!channel.isConnected()) {
if (tryCount > 0) {
performOperation(op, tryCount - 1);
}
// }
} catch (SftpException e) {
if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
if (e.id == ChannelSftp.SSH_FX_PERMISSION_DENIED) {
Activator.log("Permission denied to perform:" //$NON-NLS-1$
+ op.toString());
} else {
Activator.log(e);
}
}
} finally {
if (!op.isFinished()) {
op.failed();
}
}
}
private ChannelSftp getChannel() {
return channel;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.dltk.ssh.core.ISshConnection#getHandle(org.eclipse.core.runtime
* .IPath)
*/
public ISshFileHandle getHandle(IPath path) throws Exception {
if (isDisabled()) {
return null;
}
// GetStatOperation op = new GetStatOperation(path);
// performOperation(op, DEFAULT_RETRY_COUNT);
// if (op.isFinished()) {
// return new SshFileHandle(this, path, op.getAttrs());
// }
return new SshFileHandle(this, path, null);
}
public boolean isDisabled() {
return disabledTime > System.currentTimeMillis();
};
public void setDisabled(int timeout) {
disabledTime = System.currentTimeMillis() + timeout;
};
SftpATTRS getAttrs(IPath path) {
GetStatOperation op = new GetStatOperation(path);
performOperation(op, DEFAULT_RETRY_COUNT);
if (op.isFinished()) {
return op.getAttrs();
}
return null;
}
IPath getResolvedPath(IPath path) {
ResolveLinkOperation op = new ResolveLinkOperation(path);
performOperation(op, DEFAULT_RETRY_COUNT);
if (op.isFinished()) {
return op.getResolvedPath();
}
return null;
}
Vector<LsEntry> list(IPath path) {
ListFolderOperation op = new ListFolderOperation(path);
performOperation(op, DEFAULT_RETRY_COUNT);
if (op.isFinished()) {
return op.getVector();
}
return null;
}
void setLastModified(final IPath path, final long timestamp) {
Operation op = new Operation() {
@Override
public void perform() throws JSchException, SftpException {
Date date = new Date(timestamp);
System.out.println(date.toString());
getChannel().setMtime(path.toString(),
(int) (timestamp / 1000L));
}
};
performOperation(op, DEFAULT_RETRY_COUNT);
}
void delete(final IPath path, final boolean dir) {
Operation op = new Operation() {
@Override
public void perform() throws JSchException, SftpException {
if (!dir) {
getChannel().rm(path.toString());
} else {
getChannel().rmdir(path.toString());
}
}
};
performOperation(op, DEFAULT_RETRY_COUNT);
}
void mkdir(final IPath path) {
Operation op = new Operation() {
@Override
public void perform() throws JSchException, SftpException {
getChannel().mkdir(path.toString());
}
};
performOperation(op, DEFAULT_RETRY_COUNT);
}
InputStream get(IPath path) {
GetOperation op = new GetOperation(path);
performOperation(op, DEFAULT_RETRY_COUNT);
if (op.isFinished()) {
return op.getStream();
}
return null;
}
OutputStream put(IPath path) {
PutOperation op = new PutOperation(path);
performOperation(op, DEFAULT_RETRY_COUNT);
if (op.isFinished()) {
return op.getStream();
}
return null;
}
public boolean isConnected() {
if (session != null) {
return session.isConnected();
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((hostName == null) ? 0 : hostName.hashCode());
result = prime * result + port;
result = prime * result
+ ((userName == null) ? 0 : userName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SshConnection other = (SshConnection) obj;
if (hostName == null) {
if (other.hostName != null)
return false;
} else if (!hostName.equals(other.hostName))
return false;
if (port != other.port)
return false;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
return true;
}
}