浏览代码

Merge pull request #469 from Unrud/atomicput

Atomic replacement of whole collection by PUT
Guillaume Ayoub 9 年之前
父节点
当前提交
94ac2c5c8a
共有 3 个文件被更改,包括 24 次插入3 次删除
  1. 0 3
      radicale/__init__.py
  2. 9 0
      radicale/storage.py
  3. 15 0
      radicale/tests/test_base.py

+ 0 - 3
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:

+ 9 - 0
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)
 

+ 15 - 0
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/")