mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-31 08:25:35 +00:00
dirnode: add ctime/mtime to metadata, update metadata-modifying APIs. Needs more testing and sanity checking.
This commit is contained in:
parent
18817c00a3
commit
622c477e31
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user