dirnode: add ctime/mtime to metadata, update metadata-modifying APIs. Needs more...
authorBrian Warner <warner@allmydata.com>
Sat, 9 Feb 2008 01:43:47 +0000 (18:43 -0700)
committerBrian Warner <warner@allmydata.com>
Sat, 9 Feb 2008 01:43:47 +0000 (18:43 -0700)
src/allmydata/dirnode.py
src/allmydata/interfaces.py
src/allmydata/test/test_dirnode.py

index 569dc768f1765c2b588f8ea69b81beda28adb0f4..17070777fe243139b9d24f19470529121fd09265 100644 (file)
@@ -1,5 +1,5 @@
 
-import os
+import os, time
 
 from zope.interface import implements
 from twisted.internet import defer
@@ -190,6 +190,19 @@ class NewDirectoryNode:
         d.addCallback(lambda children: children[name][1])
         return d
 
+    def set_metadata_for(self, name, metadata):
+        if self.is_readonly():
+            return defer.fail(NotMutableError())
+        assert isinstance(metadata, dict)
+        d = self._read()
+        def _update(children):
+            children[name] = (children[name][0], metadata)
+            new_contents = self._pack_contents(children)
+            return self._node.replace(new_contents)
+        d.addCallback(_update)
+        d.addCallback(lambda res: self)
+        return d
+
     def get_child_at_path(self, path):
         """Transform a child path into an IDirectoryNode or IFileNode.
 
@@ -214,7 +227,7 @@ class NewDirectoryNode:
             d.addCallback(_got)
         return d
 
-    def set_uri(self, name, child_uri, metadata={}):
+    def set_uri(self, name, child_uri, metadata=None):
         """I add a child (by URI) at the specific name. I return a Deferred
         that fires with the child node when the operation finishes. I will
         replace any existing child of the same name.
@@ -231,14 +244,14 @@ class NewDirectoryNode:
         for e in entries:
             if len(e) == 2:
                 name, child_uri = e
-                metadata = {}
+                metadata = None
             else:
                 assert len(e) == 3
                 name, child_uri, metadata = e
             node_entries.append( (name,self._create_node(child_uri),metadata) )
         return self.set_nodes(node_entries)
 
-    def set_node(self, name, child, metadata={}):
+    def set_node(self, name, child, metadata=None):
         """I add a child at the specific name. I return a Deferred that fires
         when the operation finishes. This Deferred will fire with the child
         node that was just added. I will replace any existing child of the
@@ -256,13 +269,27 @@ class NewDirectoryNode:
             return defer.fail(NotMutableError())
         d = self._read()
         def _add(children):
+            now = time.time()
             for e in entries:
                 if len(e) == 2:
                     name, child = e
-                    metadata = {}
+                    new_metadata = None
                 else:
                     assert len(e) == 3
-                    name, child, metadata = e
+                    name, child, new_metadata = e
+                if name in children:
+                    metadata = children[name][1].copy()
+                else:
+                    metadata = {"ctime": now,
+                                "mtime": now}
+                if new_metadata is None:
+                    # update timestamps
+                    if "ctime" not in metadata:
+                        metadata["ctime"] = now
+                    metadata["mtime"] = now
+                else:
+                    # just replace it
+                    metadata = new_metadata.copy()
                 children[name] = (child, metadata)
             new_contents = self._pack_contents(children)
             return self._node.replace(new_contents)
index 7b34365efdf07fbffe1c6fda5979478fd08d9ce2..bf61994d3520aa1ad640e253b896fcb21e953d1d 100644 (file)
@@ -619,6 +619,19 @@ class IDirectoryNode(IMutableFilesystemNode):
         """I return a Deferred that fires with a specific named child node,
         either an IFileNode or an IDirectoryNode."""
 
+    def get_metadata_for(name):
+        """I return a Deferred that fires with the metadata dictionary for a
+        specific named child node. This metadata is stored in the *edge*, not
+        in the child, so it is attached to the parent dirnode rather than the
+        child dir-or-file-node."""
+
+    def set_metadata_for(name, metadata):
+        """I replace any existing metadata for the named child with the new
+        metadata. This metadata is stored in the *edge*, not in the child, so
+        it is attached to the parent dirnode rather than the child
+        dir-or-file-node. I return a Deferred (that fires with this dirnode)
+        when the operation is complete."""
+
     def get_child_at_path(path):
         """Transform a child path into an IDirectoryNode or IFileNode.
 
@@ -630,7 +643,7 @@ class IDirectoryNode(IMutableFilesystemNode):
         path-name elements.
         """
 
-    def set_uri(name, child_uri):
+    def set_uri(name, child_uri, metadata=None):
         """I add a child (by URI) at the specific name. I return a Deferred
         that fires when the operation finishes. I will replace any existing
         child of the same name.
@@ -638,37 +651,53 @@ class IDirectoryNode(IMutableFilesystemNode):
         The child_uri could be for a file, or for a directory (either
         read-write or read-only, using a URI that came from get_uri() ).
 
+        If metadata= is provided, I will use it as the metadata for the named
+        edge. This will replace any existing metadata. If metadata= is left
+        as the default value of None, I will set ['mtime'] to the current
+        time, and I will set ['ctime'] to the current time if there was not
+        already a child by this name present. This roughly matches the
+        ctime/mtime semantics of traditional filesystems.
+
         If this directory node is read-only, the Deferred will errback with a
         NotMutableError."""
 
     def set_uris(entries):
-        """Add multiple (name, child_uri) pairs to a directory node. Returns
-        a Deferred that fires (with None) when the operation finishes. This
-        is equivalent to calling set_uri() multiple times, but is much more
-        efficient.
+        """Add multiple (name, child_uri) pairs (or (name, child_uri,
+        metadata) triples) to a directory node. Returns a Deferred that fires
+        (with None) when the operation finishes. This is equivalent to
+        calling set_uri() multiple times, but is much more efficient.
         """
 
-    def set_node(name, child):
+    def set_node(name, child, metadata=None):
         """I add a child at the specific name. I return a Deferred that fires
         when the operation finishes. This Deferred will fire with the child
         node that was just added. I will replace any existing child of the
         same name.
 
+        If metadata= is provided, I will use it as the metadata for the named
+        edge. This will replace any existing metadata. If metadata= is left
+        as the default value of None, I will set ['mtime'] to the current
+        time, and I will set ['ctime'] to the current time if there was not
+        already a child by this name present. This roughly matches the
+        ctime/mtime semantics of traditional filesystems.
+
         If this directory node is read-only, the Deferred will errback with a
         NotMutableError."""
 
     def set_nodes(entries):
-        """Add multiple (name, child_node) pairs to a directory node. Returns
-        a Deferred that fires (with None) when the operation finishes. This
-        is equivalent to calling set_node() multiple times, but is much more
-        efficient."""
+        """Add multiple (name, child_node) pairs (or (name, child_node,
+        metadata) triples) to a directory node. Returns a Deferred that fires
+        (with None) when the operation finishes. This is equivalent to
+        calling set_node() multiple times, but is much more efficient."""
 
 
-    def add_file(name, uploadable):
+    def add_file(name, uploadable, metadata=None):
         """I upload a file (using the given IUploadable), then attach the
-        resulting FileNode to the directory at the given name. I return a
-        Deferred that fires (with the IFileNode of the uploaded file) when
-        the operation completes."""
+        resulting FileNode to the directory at the given name. I set metadata
+        the same way as set_uri and set_node.
+
+        I return a Deferred that fires (with the IFileNode of the uploaded
+        file) when the operation completes."""
 
     def delete(name):
         """I remove the child at the specific name. I return a Deferred that
@@ -681,8 +710,9 @@ class IDirectoryNode(IMutableFilesystemNode):
     def move_child_to(current_child_name, new_parent, new_child_name=None):
         """I take one of my children and move them to a new parent. The child
         is referenced by name. On the new parent, the child will live under
-        'new_child_name', which defaults to 'current_child_name'. I return a
-        Deferred that fires when the operation finishes."""
+        'new_child_name', which defaults to 'current_child_name'. TODO: what
+        should we do about metadata? I return a Deferred that fires when the
+        operation finishes."""
 
     def build_manifest():
         """Return a frozenset of verifier-capability strings for all nodes
index ae7aacac4120fed61fad4a94d4c4815c0debdbbe..1b8964034637410038276403d28a813452f07692 100644 (file)
@@ -1,4 +1,5 @@
 
+import time
 from zope.interface import implements
 from twisted.trial import unittest
 from allmydata import uri, dirnode, upload
@@ -81,7 +82,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
         d = self.client.create_empty_dirnode()
         def _created(dn):
             u = make_mutable_file_uri()
-            d = dn.set_uri("child", u)
+            d = dn.set_uri("child", u, {})
             d.addCallback(lambda res: dn.list())
             def _check1(children):
                 self.failUnless("child" in children)
@@ -180,7 +181,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
             ffu_v = m.get_verifier()
             assert isinstance(ffu_v, str)
             self.expected_manifest.append(ffu_v)
-            d.addCallback(lambda res: n.set_uri("child", fake_file_uri))
+            d.addCallback(lambda res: n.set_uri("child", fake_file_uri, {}))
 
             d.addCallback(lambda res: n.create_empty_directory("subdir"))
             def _created(subdir):
@@ -216,6 +217,34 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
             d.addCallback(lambda res: n.get_metadata_for("child"))
             d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
 
+            d.addCallback(lambda res:
+                          n.set_metadata_for("child",
+                                             {"tags": "web2.0-compatible"}))
+            d.addCallback(lambda n1: n1.get_metadata_for("child"))
+            d.addCallback(lambda metadata:
+                          self.failUnlessEqual(metadata,
+                                               {"tags": "web2.0-compatible"}))
+
+            def _start(res):
+                self._start_timestamp = time.time()
+            d.addCallback(_start)
+            d.addCallback(lambda res: n.add_file("timestamps",
+                                                 upload.Data("stamp me")))
+            def _stop(res):
+                self._stop_timestamp = time.time()
+            d.addCallback(_stop)
+
+            d.addCallback(lambda res: n.get_metadata_for("timestamps"))
+            def _check_timestamp(metadata):
+                self.failUnless("ctime" in metadata)
+                self.failUnless("mtime" in metadata)
+                self.failUnless(metadata["ctime"] >= self._start_timestamp)
+                self.failUnless(metadata["ctime"] <= self._stop_timestamp)
+                self.failUnless(metadata["mtime"] >= self._start_timestamp)
+                self.failUnless(metadata["mtime"] <= self._stop_timestamp)
+                return n.delete("timestamps")
+            d.addCallback(_check_timestamp)
+
             d.addCallback(lambda res: n.delete("subdir"))
             d.addCallback(lambda old_child:
                           self.failUnlessEqual(old_child.get_uri(),