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 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)

View 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

View 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(),