dirnode: add ctime/mtime to metadata, update metadata-modifying APIs. Needs more testing and sanity checking.

This commit is contained in:
Brian Warner
2008-02-08 18:43:47 -07:00
parent 18817c00a3
commit 622c477e31
3 changed files with 110 additions and 24 deletions

View File

@ -1,5 +1,5 @@
import os import os, time
from zope.interface import implements from zope.interface import implements
from twisted.internet import defer from twisted.internet import defer
@ -190,6 +190,19 @@ class NewDirectoryNode:
d.addCallback(lambda children: children[name][1]) d.addCallback(lambda children: children[name][1])
return d 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): def get_child_at_path(self, path):
"""Transform a child path into an IDirectoryNode or IFileNode. """Transform a child path into an IDirectoryNode or IFileNode.
@ -214,7 +227,7 @@ class NewDirectoryNode:
d.addCallback(_got) d.addCallback(_got)
return d 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 """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 that fires with the child node when the operation finishes. I will
replace any existing child of the same name. replace any existing child of the same name.
@ -231,14 +244,14 @@ class NewDirectoryNode:
for e in entries: for e in entries:
if len(e) == 2: if len(e) == 2:
name, child_uri = e name, child_uri = e
metadata = {} metadata = None
else: else:
assert len(e) == 3 assert len(e) == 3
name, child_uri, metadata = e name, child_uri, metadata = e
node_entries.append( (name,self._create_node(child_uri),metadata) ) node_entries.append( (name,self._create_node(child_uri),metadata) )
return self.set_nodes(node_entries) 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 """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 when the operation finishes. This Deferred will fire with the child
node that was just added. I will replace any existing child of the node that was just added. I will replace any existing child of the
@ -256,13 +269,27 @@ class NewDirectoryNode:
return defer.fail(NotMutableError()) return defer.fail(NotMutableError())
d = self._read() d = self._read()
def _add(children): def _add(children):
now = time.time()
for e in entries: for e in entries:
if len(e) == 2: if len(e) == 2:
name, child = e name, child = e
metadata = {} new_metadata = None
else: else:
assert len(e) == 3 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) children[name] = (child, metadata)
new_contents = self._pack_contents(children) new_contents = self._pack_contents(children)
return self._node.replace(new_contents) return self._node.replace(new_contents)

View File

@ -619,6 +619,19 @@ class IDirectoryNode(IMutableFilesystemNode):
"""I return a Deferred that fires with a specific named child node, """I return a Deferred that fires with a specific named child node,
either an IFileNode or an IDirectoryNode.""" 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): def get_child_at_path(path):
"""Transform a child path into an IDirectoryNode or IFileNode. """Transform a child path into an IDirectoryNode or IFileNode.
@ -630,7 +643,7 @@ class IDirectoryNode(IMutableFilesystemNode):
path-name elements. 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 """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 that fires when the operation finishes. I will replace any existing
child of the same name. 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 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() ). 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 If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotMutableError."""
def set_uris(entries): def set_uris(entries):
"""Add multiple (name, child_uri) pairs to a directory node. Returns """Add multiple (name, child_uri) pairs (or (name, child_uri,
a Deferred that fires (with None) when the operation finishes. This metadata) triples) to a directory node. Returns a Deferred that fires
is equivalent to calling set_uri() multiple times, but is much more (with None) when the operation finishes. This is equivalent to
efficient. 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 """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 when the operation finishes. This Deferred will fire with the child
node that was just added. I will replace any existing child of the node that was just added. I will replace any existing child of the
same name. 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 If this directory node is read-only, the Deferred will errback with a
NotMutableError.""" NotMutableError."""
def set_nodes(entries): def set_nodes(entries):
"""Add multiple (name, child_node) pairs to a directory node. Returns """Add multiple (name, child_node) pairs (or (name, child_node,
a Deferred that fires (with None) when the operation finishes. This metadata) triples) to a directory node. Returns a Deferred that fires
is equivalent to calling set_node() multiple times, but is much more (with None) when the operation finishes. This is equivalent to
efficient.""" 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 """I upload a file (using the given IUploadable), then attach the
resulting FileNode to the directory at the given name. I return a resulting FileNode to the directory at the given name. I set metadata
Deferred that fires (with the IFileNode of the uploaded file) when the same way as set_uri and set_node.
the operation completes."""
I return a Deferred that fires (with the IFileNode of the uploaded
file) when the operation completes."""
def delete(name): def delete(name):
"""I remove the child at the specific name. I return a Deferred that """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): 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 """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 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 'new_child_name', which defaults to 'current_child_name'. TODO: what
Deferred that fires when the operation finishes.""" should we do about metadata? I return a Deferred that fires when the
operation finishes."""
def build_manifest(): def build_manifest():
"""Return a frozenset of verifier-capability strings for all nodes """Return a frozenset of verifier-capability strings for all nodes

View File

@ -1,4 +1,5 @@
import time
from zope.interface import implements from zope.interface import implements
from twisted.trial import unittest from twisted.trial import unittest
from allmydata import uri, dirnode, upload from allmydata import uri, dirnode, upload
@ -81,7 +82,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
d = self.client.create_empty_dirnode() d = self.client.create_empty_dirnode()
def _created(dn): def _created(dn):
u = make_mutable_file_uri() u = make_mutable_file_uri()
d = dn.set_uri("child", u) d = dn.set_uri("child", u, {})
d.addCallback(lambda res: dn.list()) d.addCallback(lambda res: dn.list())
def _check1(children): def _check1(children):
self.failUnless("child" in children) self.failUnless("child" in children)
@ -180,7 +181,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
ffu_v = m.get_verifier() ffu_v = m.get_verifier()
assert isinstance(ffu_v, str) assert isinstance(ffu_v, str)
self.expected_manifest.append(ffu_v) 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")) d.addCallback(lambda res: n.create_empty_directory("subdir"))
def _created(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 res: n.get_metadata_for("child"))
d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {})) 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 res: n.delete("subdir"))
d.addCallback(lambda old_child: d.addCallback(lambda old_child:
self.failUnlessEqual(old_child.get_uri(), self.failUnlessEqual(old_child.get_uri(),