diff --git a/radicale/__init__.py b/radicale/__init__.py index f826281..f27a172 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -574,9 +574,6 @@ class Application: tag = tags.get(content_type) if write_whole_collection: - if item: - # Delete old collection - item.delete() new_item = self.Collection.create_collection( path, items, {"tag": tag}) else: diff --git a/radicale/storage.py b/radicale/storage.py index f0de7cc..2af4bac 100644 --- a/radicale/storage.py +++ b/radicale/storage.py @@ -294,6 +294,10 @@ class BaseCollection: def create_collection(cls, href, collection=None, props=None): """Create a collection. + If the collection already exists and neither ``collection`` nor + ``props`` are set, this method shouldn't do anything. Otherwise the + existing collection must be replaced. + ``collection`` is a list of vobject components. ``props`` are metadata values for the collection. @@ -551,6 +555,11 @@ class Collection(BaseCollection): for card in collection: self.upload(self._find_available_file_name(), card) + # This operation is not atomic on the filesystem level but it's + # very unlikely that one rename operations succeeds while the + # other fails or that only one gets written to disk. + if os.path.exists(filesystem_path): + os.rename(filesystem_path, os.path.join(tmp_dir, "delete")) os.rename(tmp_filesystem_path, filesystem_path) sync_directory(parent_dir) diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 8d06dbc..ad061ef 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -119,6 +119,21 @@ class BaseRequests: assert "DTSTART;TZID=Europe/Paris:20140901T180000" in answer assert "DTEND;TZID=Europe/Paris:20140901T210000" in answer + def test_put_whole_collection(self): + """Create and overwrite a whole collection.""" + event = get_file_content("event1.ics") + status, headers, answer = self.request("PUT", "/calendar.ics/", event) + assert status == 201 + status, headers, answer = self.request( + "PUT", "/calendar.ics/event1.ics", event) + assert status == 201 + # Overwrite + status, headers, answer = self.request("PUT", "/calendar.ics/", event) + assert status == 201 + status, headers, answer = self.request( + "GET", "/calendar.ics/event1.ics") + assert status == 404 + def test_delete(self): """Delete an event.""" self.request("MKCOL", "/calendar.ics/")