pull: Mirror behavior with push

If a directory is specified, pull will mirror the contents locally, into the
destination specified by walking the entire subtree.

Change-Id: Iec3541e052bdec26b535216a8baf27714d2df373
diff --git a/mdt/files.py b/mdt/files.py
index b5dd8a7..79f368e 100644
--- a/mdt/files.py
+++ b/mdt/files.py
@@ -19,6 +19,8 @@
 import sys
 import time
 
+from stat import S_ISDIR
+
 from mdt import command
 from mdt import config
 from mdt import console
@@ -232,6 +234,53 @@
 
         return True
 
+    def sftpWalk(self, sftp, remote_dir):
+        dirs_to_walk = [remote_dir]
+        for dir in dirs_to_walk:
+            entries = sftp.listdir_attr(dir)
+            dirs = []
+            files = []
+
+            for entry in entries:
+                if S_ISDIR(entry.st_mode):
+                    dirs.append(entry.filename)
+                    dirs_to_walk.append(dir + '/' + entry.filename)
+                else:
+                    files.append(entry.filename)
+
+            yield (dir, dirs, files)
+
+    def maybeMkdir(self, dir):
+        if not os.path.exists(dir):
+            os.mkdir(dir)
+
+    def pullDir(self, sftp, remote_dir, destination):
+        basename = os.path.basename(remote_dir)
+        destination = os.path.normpath(os.path.join(destination, basename))
+        self.maybeMkdir(destination)
+
+        for path, subdirs, files in self.sftpWalk(sftp, remote_dir):
+            relpath = os.path.relpath(path, start=remote_dir)
+            local_path = os.path.join(destination, relpath)
+
+            for name in subdirs:
+                local_name = os.path.join(local_path, name)
+                self.maybeMkdir(local_name)
+
+            for name in files:
+                self.pullFile(sftp, path + '/' + name, local_path)
+
+    def pullFile(self, sftp, remote_file, destination):
+        base_filename = os.path.basename(remote_file)
+        destination_filename = os.path.join(destination, base_filename)
+
+        sftp_callback = MakeProgressFunc(remote_file, PROGRESS_WIDTH, char='<')
+        sftp_callback(0, 1)
+        sftp.get(remote_file, destination_filename, callback=sftp_callback)
+        sftp_callback(1, 1)
+
+        print()
+
     def runWithClient(self, client, args):
         files_to_pull = args[1:-1]
         destination = args[-1]
@@ -239,18 +288,16 @@
         try:
             sftp = client.openSftp()
             for file in files_to_pull:
-                base_filename = os.path.basename(file)
-                sftp_callback = MakeProgressFunc(file,
-                                                 PROGRESS_WIDTH,
-                                                 char='<')
-                destination_filename = os.path.join(destination, base_filename)
+                stat_result = sftp.stat(file)
 
-                sftp_callback(0, 1)
-                sftp.get(file, destination_filename, callback=sftp_callback)
-                sftp_callback(1, 1)
-                print()
+                if S_ISDIR(stat_result.st_mode):
+                    self.pullDir(sftp, file, destination)
+                else:
+                    self.pullFile(sftp, file, destination)
+        except IOError as e:
+            print("Couldn't download file from device: {0}".format(e))
+            return 1
         finally:
-            print()
             sftp.close()
 
         return 0