mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-06-23 09:15:32 +00:00
dirnode: add ctime/mtime to metadata, update metadata-modifying APIs. Needs more testing and sanity checking.
This commit is contained in:
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
|
Reference in New Issue
Block a user