From 622c477e31a3a3c98f2768c6f17bf9ea01e022fd Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 8 Feb 2008 18:43:47 -0700 Subject: [PATCH] dirnode: add ctime/mtime to metadata, update metadata-modifying APIs. Needs more testing and sanity checking. --- src/allmydata/dirnode.py | 39 ++++++++++++++++--- src/allmydata/interfaces.py | 62 ++++++++++++++++++++++-------- src/allmydata/test/test_dirnode.py | 33 +++++++++++++++- 3 files changed, 110 insertions(+), 24 deletions(-) diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 569dc768..17070777 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -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) diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index 7b34365e..bf61994d 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -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 diff --git a/src/allmydata/test/test_dirnode.py b/src/allmydata/test/test_dirnode.py index ae7aacac..1b896403 100644 --- a/src/allmydata/test/test_dirnode.py +++ b/src/allmydata/test/test_dirnode.py @@ -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(), -- 2.45.2