-import os
+import os, time
from zope.interface import implements
from twisted.internet import defer
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.
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.
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
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)
"""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.
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.
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
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
+import time
from zope.interface import implements
from twisted.trial import unittest
from allmydata import uri, dirnode, upload
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)
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):
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(),