"""Friendly Python SFTP interface.

pysftp is forked with permission of ssh.py, originally authored by
Zeth @ http://commandline.org.uk/python/sftp-python-really-simple-ssh/

requires:
paramiko - http://www.lag.net/paramiko/
  requires:
  pycrypto - http://www.dlitz.net/software/pycrypto/

License: BSD
"""

import os
import socket
import tempfile
import paramiko
from paramiko import SSHException   # make available
from paramiko import AuthenticationException   # make available

__version__ = "0.2.3"


class ConnectionException(Exception):
    """Exception raised for connection problems

    Attributes:
        msg  -- explanation of the error
    """

    def __init__(self, host, port):
        # Call the base class constructor with the parameters it needs
        Exception.__init__(self, host, port)
        self.message = 'Could not connect to host:port.  %s:%s'

class CredentialException(Exception):
    """Exception raised for credential problems

    Attributes:
        msg  -- explanation of the error
    """

    def __init__(self, message):
        # Call the base class constructor with the parameters it needs
        Exception.__init__(self, message)
        self.message = message


class Connection(object):
    """Connects and logs into the specified hostname.
    Arguments that are not given are guessed from the environment.
        host             - The Hostname of the remote machine.
        username         - Your username at the remote machine.(None)
        private_key 	 - Your private key file.(None)
        password         - Your password at the remote machine.(None)
        port 	         - The SSH port of the remote machine.(22)
        private_key_pass - password to use if private_key is encrypted(None)
        log              - log connection/handshake details (False)
    returns a connection to the requested machine

    srv = pysftp.Connection('example.com')
    """

    def __init__(self,
                 host,
                 username=None,
                 private_key=None,
                 password=None,
                 port=22,
                 private_key_pass=None,
                 log=False,
                ):
        self._sftp_live = False
        self._sftp = None
        if not username:
            username = os.environ['LOGNAME']


        if log:
            # Log to a temporary file.
            templog = tempfile.mkstemp('.txt', 'ssh-')[1]
            paramiko.util.log_to_file(templog)

        # Begin the SSH transport.
        try:
            self._transport = paramiko.Transport((host, port))
            self._tranport_live = True
        except (AttributeError, socket.gaierror):
            # couldn't connect
            raise ConnectionException(host, port)

        # Authenticate the transport. prefer password if given
        if password:
            # Using Password.
            self._transport.connect(username=username, password=password)
        else:
            # Use Private Key.
            if not private_key:
                # Try to use default key.
                if os.path.exists(os.path.expanduser('~/.ssh/id_rsa')):
                    private_key = '~/.ssh/id_rsa'
                elif os.path.exists(os.path.expanduser('~/.ssh/id_dsa')):
                    private_key = '~/.ssh/id_dsa'
                else:
                    raise CredentialException("You have not specified a "\
                                              "password or key.")

            private_key_file = os.path.expanduser(private_key)
            try:  #try rsa
                rsakey = paramiko.RSAKey
                prv_key = rsakey.from_private_key_file(private_key_file,
                                                       private_key_pass)
            except paramiko.SSHException:   #if it fails, try dss
                dsskey = paramiko.DSSKey
                prv_key = dsskey.from_private_key_file(private_key_file,
                                                       private_key_pass)
            self._transport.connect(username=username, pkey=prv_key)

    def _sftp_connect(self):
        """Establish the SFTP connection."""
        if not self._sftp_live:
            self._sftp = paramiko.SFTPClient.from_transport(self._transport)
            self._sftp_live = True

    def get(self, remotepath, localpath=None):
        """Copies a file between the remote host and the local host."""
        if not localpath:
            localpath = os.path.split(remotepath)[1]
        self._sftp_connect()
        self._sftp.get(remotepath, localpath)

    def put(self, localpath, remotepath=None):
        """Copies a file between the local host and the remote host."""
        if not remotepath:
            remotepath = os.path.split(localpath)[1]
        self._sftp_connect()
        self._sftp.put(localpath, remotepath)

    def execute(self, command):
        """Execute the given commands on a remote machine."""
        channel = self._transport.open_session()
        channel.exec_command(command)
        output = channel.makefile('rb', -1).readlines()
        if output:
            return output
        else:
            return channel.makefile_stderr('rb', -1).readlines()

    def chdir(self, path):
        """change the current working directory on the remote"""
        self._sftp_connect()
        self._sftp.chdir(path)

    def getcwd(self):
        """return the current working directory on the remote"""
        self._sftp_connect()
        return self._sftp.getcwd()

    def listdir(self, path='.'):
        """return a list of files for the given path"""
        self._sftp_connect()
        return self._sftp.listdir(path)

    def rename(self, src, dest):
        """rename a file on the remote host."""
        self._sftp_connect()
        self._sftp.rename(src, dest)

    def close(self):
        """Closes the connection and cleans up."""
        # Close SFTP Connection.
        if self._sftp_live:
            self._sftp.close()
            self._sftp_live = False
        # Close the SSH Transport.
        if self._tranport_live:
            self._transport.close()
            self._tranport_live = False

    def __del__(self):
        """Attempt to clean up if not explicitly closed."""
        self.close()

def main():
    """Little test when called directly."""
    # Set these to your own details.
    myssh = Connection('example.com')
    myssh.put('ssh.py')
    myssh.close()

# start the ball rolling.
if __name__ == "__main__":
    main()
