From 9dd241a54bc668b3050d672efdc1ff95b643c243 Mon Sep 17 00:00:00 2001 From: Unrud Date: Mon, 1 Aug 2016 13:43:43 +0200 Subject: [PATCH 1/4] Add test for deletion of collection --- radicale/tests/test_base.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index c08e864..525a5da 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -163,6 +163,17 @@ class BaseRequests: assert status == 200 assert "DAV" in headers + def test_delete_collection(self): + """Delete a collection.""" + self.request("MKCOL", "/calendar.ics/") + event = get_file_content("event1.ics") + self.request("PUT", "/calendar.ics/event1.ics", event) + status, headers, answer = self.request("DELETE", "/calendar.ics/") + assert status == 200 + assert "href>/calendar.ics/ Date: Mon, 1 Aug 2016 13:44:27 +0200 Subject: [PATCH 2/4] delete atomic and durable See #440 --- radicale/storage.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/radicale/storage.py b/radicale/storage.py index d14797f..97f168a 100644 --- a/radicale/storage.py +++ b/radicale/storage.py @@ -580,7 +580,21 @@ class Collection(BaseCollection): if href is None: # Delete the collection if os.path.isdir(self._filesystem_path): - shutil.rmtree(self._filesystem_path) + try: + os.rmdir(self._filesystem_path) + except OSError: + while True: + tmp_filesystem_path = os.path.join( + os.path.dirname(self._filesystem_path), + ".Radicale.tmp-" + hex(getrandbits(32))[2:]) + if not os.path.exists(tmp_filesystem_path): + break + os.rename(self._filesystem_path, tmp_filesystem_path) + sync_directory(os.path.dirname(self._filesystem_path)) + # Deferred because it might take a long time + shutil.rmtree(tmp_filesystem_path) + else: + sync_directory(os.path.dirname(self._filesystem_path)) else: # Delete an item if not is_safe_filesystem_path_component(href): @@ -593,6 +607,7 @@ class Collection(BaseCollection): if etag and etag != get_etag(text): raise EtagMismatchError(etag, get_etag(text)) os.remove(path) + sync_directory(os.path.dirname(path)) def get_meta(self, key): if os.path.exists(self._props_path): From f4eb143ba81aa25e761a584d71070a004bd86954 Mon Sep 17 00:00:00 2001 From: Unrud Date: Tue, 2 Aug 2016 07:01:18 +0200 Subject: [PATCH 3/4] use tempfile to delete collection --- radicale/storage.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/radicale/storage.py b/radicale/storage.py index 97f168a..b8cb7bb 100644 --- a/radicale/storage.py +++ b/radicale/storage.py @@ -580,21 +580,17 @@ class Collection(BaseCollection): if href is None: # Delete the collection if os.path.isdir(self._filesystem_path): + parent_dir = os.path.dirname(self._filesystem_path) try: os.rmdir(self._filesystem_path) except OSError: - while True: - tmp_filesystem_path = os.path.join( - os.path.dirname(self._filesystem_path), - ".Radicale.tmp-" + hex(getrandbits(32))[2:]) - if not os.path.exists(tmp_filesystem_path): - break - os.rename(self._filesystem_path, tmp_filesystem_path) - sync_directory(os.path.dirname(self._filesystem_path)) - # Deferred because it might take a long time - shutil.rmtree(tmp_filesystem_path) + with TemporaryDirectory(prefix=".Radicale.tmp-", + dir=parent_dir) as tmp_dir: + os.rename(self._filesystem_path, os.path.join( + tmp_dir, os.path.basename(self._filesystem_path))) + sync_directory(parent_dir) else: - sync_directory(os.path.dirname(self._filesystem_path)) + sync_directory(parent_dir) else: # Delete an item if not is_safe_filesystem_path_component(href): From bfb7aec7bedcb37aa02b3762f8f7fac809c0eeb3 Mon Sep 17 00:00:00 2001 From: Unrud Date: Tue, 2 Aug 2016 19:31:49 +0200 Subject: [PATCH 4/4] Test deletion of root collection --- radicale/tests/test_base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 525a5da..6f763a0 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -174,6 +174,20 @@ class BaseRequests: status, headers, answer = self.request("GET", "/calendar.ics/") assert status == 404 + def test_delete_root_collection(self): + """Delete the root collection.""" + self.request("MKCOL", "/calendar.ics/") + event = get_file_content("event1.ics") + self.request("PUT", "/event1.ics", event) + self.request("PUT", "/calendar.ics/event1.ics", event) + status, headers, answer = self.request("DELETE", "/") + assert status == 200 + assert "href>/