package ch.cyberduck.core;
/*
* Copyright (c) 2005 David Kocher. All rights reserved.
* http://cyberduck.ch/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Bug fixes, suggestions and comments should be sent to:
* dkocher@cyberduck.ch
*/
import ch.cyberduck.core.i18n.Locale;
import ch.cyberduck.core.threading.BackgroundException;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.*;
import ch.cyberduck.core.service.LoginController;
/**
* @version $Id: Session.java 5830 2010-03-03 12:02:26Z dkocher $
*/
public abstract class Session {
private static Logger log = Logger.getLogger(Session.class);
/**
* Encapsulating all the information of the remote host
*/
protected Host host;
/**
*
*/
protected Path workdir;
protected Session(Host h) {
this.host = h;
}
protected abstract <C> C getClient() throws ConnectionCanceledException;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#getIdentification()
*/
public String getIdentification() {
try {
return this.host.getIp();
}
catch(UnknownHostException e) {
return this.host.getHostname();
}
}
private final String ua = Preferences.instance().getProperty("application") + "/"
+ Preferences.instance().getProperty("version")
+ " (" + System.getProperty("os.name") + "/" + System.getProperty("os.version") + ")"
+ " (" + System.getProperty("os.arch") + ")";
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#getUserAgent()
*/
public String getUserAgent() {
return ua;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#check()
*/
public void check() throws IOException {
try {
try {
if(!this.isConnected()) {
// If not connected anymore, reconnect the session
this.connect();
}
else {
// The session is still supposed to be connected
try {
// Send a 'no operation command' to make sure the session is alive
this.noop();
}
catch(IOException e) {
// Close the underlying socket first
this.interrupt();
// Try to reconnect once more
this.connect();
}
}
}
catch(SocketException e) {
if(e.getMessage().equals("Software caused connection abort")) {
// Do not report as failed if socket opening interrupted
log.warn("Supressed socket exception:" + e.getMessage());
throw new ConnectionCanceledException();
}
if(e.getMessage().equals("Socket closed")) {
// Do not report as failed if socket opening interrupted
log.warn("Supressed socket exception:" + e.getMessage());
throw new ConnectionCanceledException();
}
throw e;
}
catch(SSLHandshakeException e) {
log.error("SSL Handshake failed: " + e.getMessage());
// if(e.getCause() instanceof sun.security.validator.ValidatorException) {
// throw e;
// }
// Most probably caused by user dismissing ceritifcate. No trusted certificate found.
throw new ConnectionCanceledException(e.getMessage());
}
host.setTimestamp(new Date());
}
catch(IOException e) {
this.interrupt();
this.error("Connection failed", e);
throw e;
}
}
/**
* @return The timeout in milliseconds
*/
protected int timeout() {
return (int) Preferences.instance().getDouble("connection.timeout.seconds") * 1000;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isSecure()
*/
public boolean isSecure() {
if(this.isConnected()) {
return this.host.getProtocol().isSecure();
}
return false;
}
/**
* Opens the TCP connection to the server
*
* @throws IOException
* @throws LoginCanceledException
*/
protected abstract void connect() throws IOException, ConnectionCanceledException, LoginCanceledException;
protected LoginController login;
/**
* Sets the callback to ask for login credentials
*
* @param loginController
* @see #login
*/
public void setLoginController(LoginController loginController) {
this.login = loginController;
}
protected void login() throws IOException {
// login.check(host);
final Credentials credentials = host.getCredentials();
// this.message(MessageFormat.format(Locale.localizedString("Authenticating as {0}", "Status"),
// credentials.getUsername()));
this.message(credentials.getUsername());
this.login(credentials);
if(!this.isConnected()) {
throw new ConnectionCanceledException();
}
// login.success(host);
}
/**
* Send the authentication credentials to the server. The connection must be opened first.
*
* @throws IOException
* @throws LoginCanceledException
* @see #connect
*/
protected abstract void login(Credentials credentials) throws IOException;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#mount()
*/
public Path mount() {
try {
if(StringUtils.isNotBlank(host.getDefaultPath())) {
return this.mount(host.getDefaultPath());
}
return this.mount(null);
}
catch(IOException e) {
this.interrupt();
}
return null;
}
/**
* Connect to the remote host and mount the home directory
*
* @param directory
* @return null if we fail, the mounted working directory if we succeed
*/
protected Path mount(String directory) throws IOException {
// this.message(MessageFormat.format(Locale.localizedString("Mounting {0}", "Status"),
// host.getHostname()));
this.message(host.getHostname());
this.check();
if(!this.isConnected()) {
return null;
}
Path home;
if(directory != null) {
if(directory.startsWith(Path.DELIMITER) || directory.equals(this.workdir().getName())) {
home = PathFactory.createPath(this, directory,
directory.equals(Path.DELIMITER) ? Path.VOLUME_TYPE | Path.DIRECTORY_TYPE : Path.DIRECTORY_TYPE);
}
else if(directory.startsWith(Path.HOME)) {
// relative path to the home directory
home = PathFactory.createPath(this,
this.workdir().getAbsolute(), directory.substring(1), Path.DIRECTORY_TYPE);
}
else {
// relative path
home = PathFactory.createPath(this,
this.workdir().getAbsolute(), directory, Path.DIRECTORY_TYPE);
}
if(!home.childs().attributes().isReadable()) {
// the default path does not exist or is not readable due to permission issues
home = this.workdir();
}
}
else {
home = this.workdir();
}
return home;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#close()
*/
public abstract void close();
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#getHost()
*/
public Host getHost() {
return this.host;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#getEncoding()
*/
public String getEncoding() {
if(null == this.host.getEncoding()) {
return Preferences.instance().getProperty("browser.charset.encoding");
}
return this.host.getEncoding();
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#getMaxConnections()
*/
public int getMaxConnections() {
if(null == host.getMaxConnections()) {
return Preferences.instance().getInteger("connection.host.max");
}
return host.getMaxConnections();
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#workdir()
*/
public Path workdir() throws IOException {
if(!this.isConnected()) {
throw new ConnectionCanceledException();
}
if(null == workdir) {
workdir = PathFactory.createPath(this, Path.DELIMITER, Path.VOLUME_TYPE | Path.DIRECTORY_TYPE);
}
return workdir;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#setWorkdir(ch.cyberduck.core.Path)
*/
public void setWorkdir(Path workdir) throws IOException {
if(!this.isConnected()) {
throw new ConnectionCanceledException();
}
this.workdir = workdir;
}
/**
* Send a 'no operation' command
*
* @throws IOException
*/
protected abstract void noop() throws IOException;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#interrupt()
*/
public void interrupt() {
this.close();
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isSendCommandSupported()
*/
public boolean isSendCommandSupported() {
return false;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#sendCommand(java.lang.String)
*/
public abstract void sendCommand(String command) throws IOException;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isArchiveSupported()
*/
public boolean isArchiveSupported() {
return false;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#archive(ch.cyberduck.core.Archive, java.util.List)
*/
public void archive(final Archive archive, final List<Path> files) {
try {
this.check();
this.sendCommand(archive.getCompressCommand(files));
// The directory listing is no more current
for(Path file : files) {
file.getParent().invalidate();
}
}
catch(IOException e) {
this.error("Cannot create archive", e);
}
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isUnarchiveSupported()
*/
public boolean isUnarchiveSupported() {
return false;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#unarchive(ch.cyberduck.core.Archive, ch.cyberduck.core.Path)
*/
public void unarchive(final Archive archive, Path file) {
try {
this.check();
this.sendCommand(archive.getDecompressCommand(file));
// The directory listing is no more current
file.getParent().invalidate();
}
catch(IOException e) {
this.error("Cannot expand archive", e);
}
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isConnected()
*/
public boolean isConnected() {
try {
this.getClient();
}
catch(ConnectionCanceledException e) {
return false;
}
return true;
}
private boolean opening;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isOpening()
*/
public boolean isOpening() {
return opening;
}
private Set<ConnectionListener> connectionListeners
= Collections.synchronizedSet(new HashSet<ConnectionListener>());
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#addConnectionListener(ch.cyberduck.core.ConnectionListener)
*/
public void addConnectionListener(ConnectionListener listener) {
connectionListeners.add(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#removeConnectionListener(ch.cyberduck.core.ConnectionListener)
*/
public void removeConnectionListener(ConnectionListener listener) {
connectionListeners.remove(listener);
}
/**
* Notifies all connection listeners that an attempt is made to open this session
*
* @throws ResolveCanceledException If the name resolution has been canceled by the user
* @throws java.net.UnknownHostException If the name resolution failed
* @see ConnectionListener
*/
protected void fireConnectionWillOpenEvent() throws ResolveCanceledException, UnknownHostException {
log.debug("connectionWillOpen");
ConnectionListener[] l = connectionListeners.toArray(new ConnectionListener[connectionListeners.size()]);
for(ConnectionListener listener : l) {
listener.connectionWillOpen();
}
// Configuring proxy if any
// ProxyFactory.instance().configure(host);
Resolver resolver = new Resolver(this.host.getHostname(true));
// this.message(MessageFormat.format(Locale.localizedString("Resolving {0}", "Status"),
// host.getHostname()));
this.message(host.getHostname());
// Try to resolve the hostname first
resolver.resolve();
// The IP address could successfully be determined
}
/**
* Starts the <code>KeepAliveTask</code> if <code>connection.keepalive</code> is true
* Notifies all connection listeners that the connection has been opened successfully
*
* @see ConnectionListener
*/
protected void fireConnectionDidOpenEvent() {
log.debug("connectionDidOpen");
for(ConnectionListener listener : connectionListeners.toArray(new ConnectionListener[connectionListeners.size()])) {
listener.connectionDidOpen();
}
}
/**
* Notifes all connection listeners that a connection is about to be closed
*
* @see ConnectionListener
*/
protected void fireConnectionWillCloseEvent() {
log.debug("connectionWillClose");
// this.message(MessageFormat.format(Locale.localizedString("Disconnecting {0}", "Status"),
// this.getHost().getHostname()));
for(ConnectionListener listener : connectionListeners.toArray(new ConnectionListener[connectionListeners.size()])) {
listener.connectionWillClose();
}
}
/**
* Notifes all connection listeners that a connection has been closed
*
* @see ConnectionListener
*/
protected void fireConnectionDidCloseEvent() {
log.debug("connectionDidClose");
this.workdir = null;
for(ConnectionListener listener : connectionListeners.toArray(new ConnectionListener[connectionListeners.size()])) {
listener.connectionDidClose();
}
}
private Set<TranscriptListener> transcriptListeners
= Collections.synchronizedSet(new HashSet<TranscriptListener>());
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#addTranscriptListener(ch.cyberduck.core.TranscriptListener)
*/
public void addTranscriptListener(TranscriptListener listener) {
transcriptListeners.add(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#removeTranscriptListener(ch.cyberduck.core.TranscriptListener)
*/
public void removeTranscriptListener(TranscriptListener listener) {
transcriptListeners.remove(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#log(boolean, java.lang.String)
*/
public void log(boolean request, final String message) {
log.info(message);
for(TranscriptListener listener : transcriptListeners) {
listener.log(request, message);
}
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isDownloadResumable()
*/
public boolean isDownloadResumable() {
return true;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#isUploadResumable()
*/
public boolean isUploadResumable() {
return true;
}
private Set<ProgressListener> progressListeners
= Collections.synchronizedSet(new HashSet<ProgressListener>());
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#addProgressListener(ch.cyberduck.core.ProgressListener)
*/
public void addProgressListener(ProgressListener listener) {
progressListeners.add(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#removeProgressListener(ch.cyberduck.core.ProgressListener)
*/
public void removeProgressListener(ProgressListener listener) {
progressListeners.remove(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#message(java.lang.String)
*/
public void message(final String message) {
log.info(message);
for(ProgressListener listener : progressListeners.toArray(new ProgressListener[progressListeners.size()])) {
listener.message(message);
}
}
private Set<ErrorListener> errorListeners
= Collections.synchronizedSet(new HashSet<ErrorListener>());
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#addErrorListener(ch.cyberduck.core.ErrorListener)
*/
public void addErrorListener(ErrorListener listener) {
errorListeners.add(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#removeErrorListener(ch.cyberduck.core.ErrorListener)
*/
public void removeErrorListener(ErrorListener listener) {
errorListeners.remove(listener);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#error(java.lang.String, java.lang.Throwable)
*/
public void error(String message, Throwable e) {
this.error(null, message, e);
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#error(ch.cyberduck.core.Path, java.lang.String, java.lang.Throwable)
*/
public void error(Path path, String message, Throwable e) {
final BackgroundException failure = new BackgroundException(this, path, message, e);
this.message(failure.getMessage());
for(ErrorListener listener : errorListeners.toArray(new ErrorListener[errorListeners.size()])) {
listener.error(failure);
}
}
/**
* Caching files listings of previously visited directories
*/
private Cache<Path> cache;
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#cache()
*/
public Cache<Path> cache() {
if(null == cache) {
cache = new Cache<Path>() {
@Override
public String toString() {
return "Cache for " + Session.this.toString();
}
};
}
return this.cache;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#equals(java.lang.Object)
*/
@Override
public boolean equals(Object other) {
if(null == other) {
return false;
}
if(other instanceof Session) {
return this.getHost().getHostname().equals(((Session) other).getHost().getHostname())
&& this.getHost().getProtocol().equals(((Session) other).getHost().getProtocol());
}
return false;
}
/* (non-Javadoc)
* @see ch.cyberduck.core.SessionService#toString()
*/
public String toString() {
return "Session " + host.toURL();
}
}