From 35c8d208d0c5447fd77231d7664988cb5893d14f 2014-07-31 18:57:37
From: MinRK <benjaminrk@gmail.com>
Date: 2014-07-31 18:57:37
Subject: [PATCH] support deleting empty directories

can’t copy directories

---

diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py
index 2c36e05..ef4acf4 100644
--- a/IPython/html/services/contents/filemanager.py
+++ b/IPython/html/services/contents/filemanager.py
@@ -357,7 +357,13 @@ class FileContentsManager(ContentsManager):
         """Delete file by name and path."""
         path = path.strip('/')
         os_path = self._get_os_path(name, path)
-        if not os.path.isfile(os_path):
+        rm = os.unlink
+        if os.path.isdir(os_path):
+            listing = os.listdir(os_path)
+            # don't delete non-empty directories (checkpoints dir doesn't count)
+            if listing and listing != ['.ipynb_checkpoints']:
+                raise web.HTTPError(400, u'Directory %s not empty' % os_path)
+        elif not os.path.isfile(os_path):
             raise web.HTTPError(404, u'File does not exist: %s' % os_path)
 
         # clear checkpoints
@@ -368,8 +374,12 @@ class FileContentsManager(ContentsManager):
                 self.log.debug("Unlinking checkpoint %s", cp_path)
                 os.unlink(cp_path)
 
-        self.log.debug("Unlinking file %s", os_path)
-        os.unlink(os_path)
+        if os.path.isdir(os_path):
+            self.log.debug("Removing directory %s", os_path)
+            shutil.rmtree(os_path)
+        else:
+            self.log.debug("Unlinking file %s", os_path)
+            rm(os_path)
 
     def rename(self, old_name, old_path, new_name, new_path):
         """Rename a file."""
diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py
index cd4231c..871c363 100644
--- a/IPython/html/services/contents/manager.py
+++ b/IPython/html/services/contents/manager.py
@@ -7,6 +7,8 @@ from fnmatch import fnmatch
 import itertools
 import os
 
+from tornado.web import HTTPError
+
 from IPython.config.configurable import LoggingConfigurable
 from IPython.nbformat import current, sign
 from IPython.utils.traitlets import Instance, Unicode, List
@@ -187,6 +189,8 @@ class ContentsManager(LoggingConfigurable):
         """
         path = path.strip('/')
         model = self.get_model(from_name, path)
+        if model['type'] == 'directory':
+            raise HTTPError(400, "Can't copy directories")
         if not to_name:
             base, ext = os.path.splitext(from_name)
             copy_name = u'{0}-Copy{1}'.format(base, ext)
diff --git a/IPython/html/services/contents/tests/test_contents_api.py b/IPython/html/services/contents/tests/test_contents_api.py
index 4c73075..aadf0c9 100644
--- a/IPython/html/services/contents/tests/test_contents_api.py
+++ b/IPython/html/services/contents/tests/test_contents_api.py
@@ -69,6 +69,9 @@ class API(object):
     def upload(self, name, body, path='/'):
         return self._req('PUT', url_path_join(path, name), body)
 
+    def mkdir(self, name, path='/'):
+        return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'}))
+
     def copy(self, copy_from, copy_to, path='/'):
         body = json.dumps({'copy_from':copy_from})
         return self._req('PUT', url_path_join(path, copy_to), body)
@@ -299,9 +302,7 @@ class APITest(NotebookTestBase):
         self._check_created(resp, u'Upload tést.ipynb', u'å b')
 
     def test_mkdir(self):
-        model = {'type': 'directory'}
-        resp = self.api.upload(u'New ∂ir', path=u'å b',
-                                              body=json.dumps(model))
+        resp = self.api.mkdir(u'New ∂ir', path=u'å b')
         self._check_created(resp, u'New ∂ir', u'å b', type='directory')
 
     def test_upload_txt(self):
@@ -362,6 +363,11 @@ class APITest(NotebookTestBase):
         resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
         self._check_created(resp, u'cøpy.ipynb', u'å b')
 
+    def test_copy_dir_400(self):
+        # can't copy directories
+        with assert_http_error(400):
+            resp = self.api.copy(u'å b', u'å c')
+
     def test_delete(self):
         for d, name in self.dirs_nbs:
             resp = self.api.delete('%s.ipynb' % name, d)
@@ -371,6 +377,20 @@ class APITest(NotebookTestBase):
             nbs = notebooks_only(self.api.list(d).json())
             self.assertEqual(len(nbs), 0)
 
+    def test_delete_dirs(self):
+        # depth-first delete everything, so we don't try to delete empty directories
+        for name in sorted(self.dirs + ['/'], key=len, reverse=True):
+            listing = self.api.list(name).json()['content']
+            for model in listing:
+                self.api.delete(model['name'], model['path'])
+        listing = self.api.list('/').json()['content']
+        self.assertEqual(listing, [])
+
+    def test_delete_non_empty_dir(self):
+        """delete non-empty dir raises 400"""
+        with assert_http_error(400):
+            self.api.delete(u'å b')
+
     def test_rename(self):
         resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
         self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')