summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--paramiko/sftp_client.py65
-rwxr-xr-xtests/test_sftp.py24
2 files changed, 87 insertions, 2 deletions
diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py
index 99a29e36..ee25aa6a 100644
--- a/paramiko/sftp_client.py
+++ b/paramiko/sftp_client.py
@@ -196,6 +196,71 @@ class SFTPClient(BaseSFTP):
self._request(CMD_CLOSE, handle)
return filelist
+ def listdir_iter(self, path='.', read_ahead_requests=50):
+ """
+ Generator version of `.listdir_attr`.
+
+ See the API docs for `.listdir_attr` for overall details.
+
+ This function adds one more kwarg on top of `.listdir_attr`:
+ ``read_ahead_requests``, an integer controlling how many
+ ``SSH_FXP_READDIR`` requests are made to the server. The default of 50
+ should suffice for most file listings as each request/response cycle
+ may contain multiple files (dependant on server implementation.)
+
+ .. versionadded:: 1.15
+ """
+ path = self._adjust_cwd(path)
+ self._log(DEBUG, 'listdir(%r)' % path)
+ t, msg = self._request(CMD_OPENDIR, path)
+
+ if t != CMD_HANDLE:
+ raise SFTPError('Expected handle')
+
+ handle = msg.get_string()
+
+ nums = list()
+ while True:
+ try:
+ # Send out a bunch of readdir requests so that we can read the
+ # responses later on Section 6.7 of the SSH file transfer RFC
+ # explains this
+ # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
+ for i in range(read_ahead_requests):
+ num = self._async_request(type(None), CMD_READDIR, handle)
+ nums.append(num)
+
+
+ # For each of our sent requests
+ # Read and parse the corresponding packets
+ # If we're at the end of our queued requests, then fire off
+ # some more requests
+ # Exit the loop when we've reached the end of the directory
+ # handle
+ for num in nums:
+ t, pkt_data = self._read_packet()
+ msg = Message(pkt_data)
+ new_num = msg.get_int()
+ if num == new_num:
+ if t == CMD_STATUS:
+ self._convert_status(msg)
+ count = msg.get_int()
+ for i in range(count):
+ filename = msg.get_string()
+ longname = msg.get_string()
+ attr = SFTPAttributes._from_msg(
+ msg, filename, longname)
+ if (filename != '.') and (filename != '..'):
+ yield attr
+
+ # If we've hit the end of our queued requests, reset nums.
+ nums = list()
+
+ except EOFError:
+ self._request(CMD_CLOSE, handle)
+ return
+
+
def open(self, filename, mode='r', bufsize=-1):
"""
Open a file on the remote server. The arguments are the same as for
diff --git a/tests/test_sftp.py b/tests/test_sftp.py
index 2b6aa3b6..1ae9781d 100755
--- a/tests/test_sftp.py
+++ b/tests/test_sftp.py
@@ -279,8 +279,8 @@ class SFTPTest (unittest.TestCase):
def test_7_listdir(self):
"""
- verify that a folder can be created, a bunch of files can be placed in it,
- and those files show up in sftp.listdir.
+ verify that a folder can be created, a bunch of files can be placed in
+ it, and those files show up in sftp.listdir.
"""
try:
sftp.open(FOLDER + '/duck.txt', 'w').close()
@@ -298,6 +298,26 @@ class SFTPTest (unittest.TestCase):
sftp.remove(FOLDER + '/fish.txt')
sftp.remove(FOLDER + '/tertiary.py')
+ def test_7_5_listdir_iter(self):
+ """
+ listdir_iter version of above test
+ """
+ try:
+ sftp.open(FOLDER + '/duck.txt', 'w').close()
+ sftp.open(FOLDER + '/fish.txt', 'w').close()
+ sftp.open(FOLDER + '/tertiary.py', 'w').close()
+
+ x = [x.filename for x in sftp.listdir_iter(FOLDER)]
+ self.assertEqual(len(x), 3)
+ self.assertTrue('duck.txt' in x)
+ self.assertTrue('fish.txt' in x)
+ self.assertTrue('tertiary.py' in x)
+ self.assertTrue('random' not in x)
+ finally:
+ sftp.remove(FOLDER + '/duck.txt')
+ sftp.remove(FOLDER + '/fish.txt')
+ sftp.remove(FOLDER + '/tertiary.py')
+
def test_8_setstat(self):
"""
verify that the setstat functions (chown, chmod, utime, truncate) work.