diff --git a/README b/README index 35f0225..033f4a5 100644 --- a/README +++ b/README @@ -238,7 +238,6 @@ v0.9 FEAROW * ctr forms of ciphers are missing (blowfish-ctr, aes128-ctr, aes256-ctr) -* would be nice to have an ftp-like interface to sftp (put, get, chdir...) * cool sftp extension: retreive MD5 or SHA1 of section of a file * SFTPClient.from_url('sftp://robey@arch.lag.net/folder/filename', 'r+') keep cache of opened sftp clients by (host, port, username) diff --git a/paramiko/file.py b/paramiko/file.py index c0601b0..13d36ac 100644 --- a/paramiko/file.py +++ b/paramiko/file.py @@ -56,6 +56,9 @@ class BufferedFile (object): # (these may be different because we buffer for line reading) self._pos = self._realpos = 0 + def __del__(self): + self.close() + def __iter__(self): """ Returns an iterator that can be used to iterate over the lines in this diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index 488a415..ab4f505 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -20,6 +20,7 @@ Client-mode SFTP support. """ +import os from sftp import * from sftp_attr import SFTPAttributes from sftp_file import SFTPFile @@ -428,6 +429,62 @@ class SFTPClient (BaseSFTP): @since: 1.4 """ return self._cwd + + def put(self, localpath, remotepath): + """ + Copy a local file (C{localpath}) to the SFTP server as C{remotepath}. + Any exception raised by operations will be passed through. This + method is primarily provided as a convenience. + + @param localpath: the local file to copy + @type localpath: str + @param remotepath: the destination path on the SFTP server + @type remotepath: str + + @since: 1.4 + """ + fl = file(localpath, 'rb') + fr = self.file(remotepath, 'wb') + size = 0 + while True: + data = fl.read(16384) + if len(data) == 0: + break + fr.write(data) + size += len(data) + fl.close() + fr.close() + s = self.stat(remotepath) + if s.st_size != size: + raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) + + def get(self, remotepath, localpath): + """ + Copy a remote file (C{remotepath}) from the SFTP server to the local + host as C{localpath}. Any exception raised by operations will be + passed through. This method is primarily provided as a convenience. + + @param remotepath: the remote file to copy + @type remotepath: str + @param localpath: the destination path on the local host + @type localpath: str + + @since: 1.4 + """ + fr = self.file(remotepath, 'rb') + fl = file(localpath, 'wb') + size = 0 + while True: + data = fr.read(16384) + if len(data) == 0: + break + fl.write(data) + size += len(data) + fl.close() + fr.close() + s = os.stat(localpath) + if s.st_size != size: + raise IOError('size mismatch in get! %d != %d' % (s.st_size, size)) ### internals... diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py index c173d1c..36d6772 100644 --- a/paramiko/sftp_file.py +++ b/paramiko/sftp_file.py @@ -43,7 +43,14 @@ class SFTPFile (BufferedFile): def close(self): BufferedFile.close(self) - self.sftp._request(CMD_CLOSE, self.handle) + try: + self.sftp._request(CMD_CLOSE, self.handle) + except EOFError: + # may have outlived the Transport connection + pass + except IOError: + # may have outlived the Transport connection + pass def _read(self, size): size = min(size, self.MAX_REQUEST_SIZE) diff --git a/tests/test_sftp.py b/tests/test_sftp.py index f4b5fea..992c9dc 100755 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -540,3 +540,32 @@ class SFTPTest (unittest.TestCase): except: pass + def test_J_get_put(self): + """ + verify that get/put work. + """ + import os, warnings + warnings.filterwarnings('ignore', 'tempnam.*') + + localname = os.tempnam() + text = 'All I wanted was a plastic bunny rabbit.\n' + f = open(localname, 'w') + f.write(text) + f.close() + sftp.put(localname, FOLDER + '/bunny.txt') + + f = sftp.open(FOLDER + '/bunny.txt', 'r') + self.assertEquals(text, f.read(128)) + f.close() + + os.unlink(localname) + localname = os.tempnam() + sftp.get(FOLDER + '/bunny.txt', localname) + + f = open(localname, 'r') + self.assertEquals(text, f.read(128)) + f.close() + + os.unlink(localname) + sftp.unlink(FOLDER + '/bunny.txt') +