]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
snapshot filetree work: it's getting close
authorBrian Warner <warner@lothar.com>
Fri, 19 Jan 2007 09:23:03 +0000 (02:23 -0700)
committerBrian Warner <warner@lothar.com>
Fri, 19 Jan 2007 09:23:03 +0000 (02:23 -0700)
src/allmydata/filetree/directory.py
src/allmydata/filetree/interfaces.py
src/allmydata/filetree/opener.py
src/allmydata/filetree/redirect.py [new file with mode: 0644]
src/allmydata/filetree/specification.py
src/allmydata/filetree/vdrive.py [new file with mode: 0644]
src/allmydata/test/test_filetree_new.py
src/allmydata/upload.py
src/allmydata/workqueue.py

index a52c9ed96530448ad9d3f0bcc3baf6b990c78569..6e469e780e96221ca49f2d8191c0a5191b7eef34 100644 (file)
@@ -21,27 +21,67 @@ class SubTreeNode:
 
     def __init__(self, tree):
         self.enclosing_tree = tree
-        # node_children maps child name to another SubTreeNode instance. This
-        # is only for internal directory nodes. All Files and external links
-        # are listed in child_specifications instead.
-        self.node_children = {}
-        # child_specifications maps child name to a string which describes
-        # how to obtain the actual child. For example, if "foo.jpg" in this
-        # node represents a FILE with a uri of "fooURI", then
-        # self.child_specifications["foo.jpg"] = "(FILE,fooURI")
+        # subdirectory_node_children maps child name to another SubTreeNode
+        # instance. This is only for internal directory nodes. All other
+        # nodes are listed in child_specifications instead.
+        self.subdirectory_node_children = {}
+        # child_specifications maps child name to a specification tuple which
+        # describes how to obtain the actual child. For example, if "foo.jpg"
+        # in this node represents a CHK-encoded FILE with a uri of "fooURI",
+        # then self.child_specifications["foo.jpg"] = ("CHKFILE","fooURI")
         self.child_specifications = {}
 
+    def is_directory(self):
+        return True
+
     def list(self):
-        return sorted(self.node_children.keys() +
+        return sorted(self.subdirectory_node_children.keys() +
                       self.child_specifications.keys())
 
-    def serialize(self):
+    def get(self, childname):
+        if childname in self.subdirectory_node_children:
+            return self.subdirectory_node_children[childname]
+        elif childname in self.child_specifications:
+            return to_node(self.child_specifications[childname])
+        else:
+            raise NoSuchChildError("no child named '%s'" % (childname,))
+
+    def get_subtree(self):
+        return self.enclosing_tree
+
+    def delete(self, childname):
+        assert self.enclosing_tree.is_mutable()
+        if childname in self.subdirectory_node_children:
+            del self.subdirectory_node_children[childname]
+        elif childname in self.child_specifications:
+            del to_node(self.child_specifications[childname])
+        else:
+            raise NoSuchChildError("no child named '%s'" % (childname,))
+
+    def add_subdir(self, childname):
+        assert childname not in self.subdirectory_node_children
+        assert childname not in self.child_specifications
+        newnode = SubTreeNode(self.enclosing_tree)
+        self.subdirectory_node_children[childname] = newnode
+        return newnode
+
+    def add(self, childname, node):
+        assert childname not in self.subdirectory_node_children
+        assert childname not in self.child_specifications
+        spec = to_spec(node)
+        self.child_specifications[childname] = spec
+        return self
+
+    def serialize_to_sexprs(self):
         # note: this is a one-pass recursive serialization that will result
         # in the whole file table being held in memory. This is only
         # appropriate for directories with fewer than, say, 10k nodes. If we
         # support larger directories, we should turn this into some kind of
         # generator instead, and write the serialized data directly to a
         # tempfile.
+        #
+        # ["DIRECTORY", name1, child1, name2, child2..]
+
         data = ["DIRECTORY"]
         for name in sorted(self.node_children.keys()):
             data.append(name)
@@ -51,7 +91,7 @@ class SubTreeNode:
             data.append(self.child_specifications[name].serialize())
         return data
 
-    def unserialize(self, data):
+    def populate_from_sexprs(self, data):
         assert data[0] == "DIRECTORY"
         assert len(data) % 2 == 1
         for i in range(1, len(data), 2):
@@ -61,97 +101,14 @@ class SubTreeNode:
             child_type = child_data[0]
             if child_type == "DIRECTORY":
                 child = SubTreeNode(self.enclosing_tree)
-                child.unserialize(child_data)
+                child.populate_from_sexprs(child_data)
                 self.node_children[name] = child
             else:
                 self.child_specifications[name] = child_data
 
-class _SubTreeMixin(object):
-
-    def get(self, path, opener):
-        """Return a Deferred that fires with the node at the given path, or
-        None if there is no such node. This will traverse and even create
-        subtrees as necessary."""
-        d = self.get_node_for_path(path)
-        def _done(res):
-            if res == None:
-                # traversal done, unable to find the node
-                return None
-            if res[0] == True:
-                # found the node
-                node = res[1]
-                assert INode.providedBy(node)
-                return node
-            # otherwise, we must open and recurse into a new subtree
-            next_subtree_spec = res[1]
-            subpath = res[2]
-            d1 = opener.open(next_subtree_spec, self.is_mutable())
-            def _opened(next_subtree):
-                assert ISubTree.providedBy(next_subtree)
-                return next_subtree.get(subpath, opener)
-            d1.addCallback(_opened)
-            return d1
-        d.addCallback(_done)
-        return d
-
-    def find_lowest_containing_subtree_for_path(self, path, opener):
-        """Find the subtree which contains the target path, opening new
-        subtrees if necessary. Return a Deferred that fires with (subtree,
-        prepath, postpath), where prepath is the list of path components that
-        got to the subtree, and postpath is the list of remaining path
-        components (indicating a subpath within the resulting subtree). This
-        will traverse and even create subtrees as necessary."""
-        d = self.get_or_create_node_for_path(path)
-        def _done(res):
-            if res[0] == True:
-                node = res[1]
-                # found the node in our own tree. The whole path we were
-                # given was used internally, and is therefore the postpath
-                return (self, [], path)
-            # otherwise, we must open and recurse into a new subtree
-            ignored, next_subtree_spec, prepath, postpath = res
-            d1 = opener.open(next_subtree_spec, self.is_mutable())
-            def _opened(next_subtree):
-                assert ISubTree.providedBy(next_subtree)
-                f = next_subtree.find_lowest_containing_subtree_for_path
-                return f(postpath, opener)
-            d1.addCallback(_opened)
-            def _found(res2):
-                subtree, prepath2, postpath2 = res2
-                return (subtree, prepath + prepath2, postpath2)
-            d1.addCallback(_found)
-            return d1
-        d.addCallback(_done)
-        return d
-
-
-class _MutableSubTreeMixin(object):
-
-    def add(self, path, child, opener, work_queue):
-        assert len(path) > 0
-        d = self.find_lowest_containing_subtree_for_path(path[:-1], opener)
-        def _found(res):
-            subtree, prepath, postpath = res
-            assert IMutableSubTree.providedBy(subtree)
-            # postpath is from the top of the subtree to the directory where
-            # this child should be added. add_subpath wants the path from the
-            # top of the subtree to the child itself, so we need to append
-            # the child's name here.
-            addpath = postpath + [path[-1]]
-            # this add_path will cause some steps to be added, as well as the
-            # internal node to be modified
-            d1 = subtree.add_subpath(addpath, child, work_queue)
-            if subtree.mutation_affects_parent():
-                def _added(boxname):
-                    work_queue.add_addpath(boxname, prepath)
-                d1.addCallback(_added)
-            return d1
-        d.addCallback(_found)
-        return d
 
 
-
-class _DirectorySubTree(_SubTreeMixin):
+class _DirectorySubTree(object):
     """I represent a set of connected directories that all share the same
     access control: any given person can read or write anything in this tree
     as a group, and it is not possible to give access to some pieces of this
@@ -167,99 +124,60 @@ class _DirectorySubTree(_SubTreeMixin):
     """
     implements(ISubTree)
 
+
     def new(self):
         self.root = SubTreeNode(self)
+        self.mutable = True # sure, why not
 
-    def unserialize(self, serialized_data):
-        """Populate all nodes from serialized_data, previously created by
-        calling my serialize() method. 'serialized_data' is a series of
-        nested lists (s-expressions), probably recorded in bencoded form."""
-        self.root = SubTreeNode(self)
-        self.root.unserialize(serialized_data)
+    def populate_from_specification(self, spec, parent_is_mutable, downloader):
+        return self.populate_from_node(to_node(spec),
+                                       parent_is_mutable, downloader)
+
+    def populate_from_data(self, data):
+        self.root = SubTreeNode()
+        self.root.populate_from_sexprs(bencode.bdecode(data))
         return self
 
     def serialize(self):
         """Return a series of nested lists which describe my structure
         in a form that can be bencoded."""
-        return self.root.serialize()
+        return self.root.serialize_to_sexprs()
+
+    def serialize_to_file(self, f):
+        f.write(bencode.bencode(self.serialize()))
 
     def is_mutable(self):
-        return IMutableSubTree.providedBy(self)
+        return self.mutable
 
     def get_node_for_path(self, path):
-        # this is restricted to traversing our own subtree.
-        subpath = path
+        # this is restricted to traversing our own subtree. Returns
+        # (found_path, node, remaining_path)
+        found_path = []
+        remaining_path = path[:]
         node = self.root
-        while subpath:
-            name = subpath.pop(0)
+        while remaining_path:
+            name = remaining_path[0]
             if name in node.node_children:
                 node = node.node_children[name]
                 assert isinstance(node, SubTreeNode)
+                found_path.append(name)
+                remaining_path.pop(0)
                 continue
             if name in node.child_specifications:
-                # the path takes us out of this SubTree and into another
+                # the path takes us out of this subtree and into another
                 next_subtree_spec = node.child_specifications[name]
-                result = (False, next_subtree_spec, subpath)
-                return defer.succeed(result)
-            return defer.succeed(None)
-        # we've run out of path components, so we must be at the terminus
-        result = (True, node)
-        return defer.succeed(result)
-
-    def get_or_create_node_for_path(self, path):
-        # this is restricted to traversing our own subtree, but will create
-        # internal directory nodes as necessary
-        prepath = []
-        postpath = path[:]
-        node = self.root
-        while postpath:
-            name = postpath.pop(0)
-            prepath.append(name)
-            if name in node.node_children:
-                node = node.node_children[name]
-                assert isinstance(node, SubTreeNode)
-                continue
-            if name in node.child_specifications:
-                # the path takes us out of this SubTree and into another
-                next_subtree_spec = node.child_specifications[name]
-                result = (False, next_subtree_spec, prepath, postpath)
-                return defer.succeed(result)
-            # need to create a new node
-            new_node = SubTreeNode(self)
-            node.node_children[name] = new_node
-            node = new_node
-            continue
-        # we've run out of path components, so we must be at the terminus
-        result = (True, node)
-        return defer.succeed(result)
-
-class ImmutableDirectorySubTree(_DirectorySubTree):
-    pass
-
-class _MutableDirectorySubTree(_DirectorySubTree, _MutableSubTreeMixin):
-    implements(IMutableSubTree)
-
-    def add_subpath(self, subpath, child, work_queue):
-        prepath = subpath[:-1]
-        name = subpath[-1]
-        d = self.get_node_for_path(prepath)
-        def _found(results):
-            assert results is not None
-            assert results[0] == True
-            node = results[1]
-            # modify the in-RAM copy
-            node.child_specifications[name] = child
-            # now serialize and upload ourselves
-            boxname = self.upload_my_serialized_form(work_queue)
-            # our caller will perform the addpath, if necessary
-            return boxname
-        d.addCallback(_found)
-        return d
-
-    def serialize_to_file(self, f):
-        f.write(bencode.bencode(self.serialize()))
-
-class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
+                node = to_node(next_subtree_spec)
+                found_path.append(name)
+                remaining_path.pop(0)
+                break
+            # The node *would* be in this subtree if it existed, but it
+            # doesn't. Leave found_path and remaining_path alone, and node
+            # points at the last parent node that was on the path.
+            break
+        return (found_path, node, remaining_path)
+
+class CHKDirectorySubTree(_DirectorySubTree):
+    # maybe mutable, maybe not
 
     def mutation_affects_parent(self):
         return True
@@ -267,7 +185,14 @@ class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
     def set_uri(self, uri):
         self.old_uri = uri
 
-    def upload_my_serialized_form(self, work_queue):
+    def populate_from_node(self, node, parent_is_mutable, downloader):
+        node = ICHKDirectoryNode(node)
+        self.mutable = parent_is_mutable
+        d = downloader.download(node.get_uri(), download.Data())
+        d.addCallback(self.populate_from_data)
+        return d
+
+    def update(self, prepath, work_queue):
         # this is the CHK form
         f, filename = work_queue.create_tempfile(".chkdir")
         self.serialize_to_file(f)
@@ -277,21 +202,32 @@ class MutableCHKDirectorySubTree(_MutableDirectorySubTree):
         work_queue.add_delete_tempfile(filename)
         work_queue.add_retain_uri_from_box(boxname)
         work_queue.add_delete_box(boxname)
+        work_queue.add_addpath(boxname, prepath)
         work_queue.add_unlink_uri(self.old_uri)
         # TODO: think about how self.old_uri will get updated. I *think* that
         # this whole instance will get replaced, so it ought to be ok. But
         # this needs investigation.
         return boxname
 
-class MutableSSKDirectorySubTree(_MutableDirectorySubTree):
+class SSKDirectorySubTree(_DirectorySubTree):
 
     def new(self):
-        _MutableDirectorySubTree.new(self)
+        _DirectorySubTree.new(self)
         self.version = 0
+        # TODO: populate
 
     def mutation_affects_parent(self):
         return False
 
+    def populate_from_node(self, node, parent_is_mutable, downloader):
+        node = ISSKDirectoryNode(node)
+        self.read_capability = node.get_read_capability()
+        self.write_capability = node.get_write_capability()
+        self.mutable = bool(self.write_capability)
+        d = downloader.download_ssk(self.read_capability, download.Data())
+        d.addCallback(self.populate_from_data)
+        return d
+
     def set_version(self, version):
         self.version = version
 
@@ -300,9 +236,9 @@ class MutableSSKDirectorySubTree(_MutableDirectorySubTree):
         f, filename = work_queue.create_tempfile(".sskdir")
         self.serialize_to_file(f)
         f.close()
-        work_queue.add_upload_ssk(filename, self.get_write_capability(),
+        work_queue.add_upload_ssk(filename, self.write_capability,
                                   self.version)
         self.version = self.version + 1
         work_queue.add_delete_tempfile(filename)
-        work_queue.add_retain_ssk(self.get_read_capability())
+        work_queue.add_retain_ssk(self.read_capability)
 
index 8934a7f391b1ee4ba7397b48c25351a02b92f0a5..2051916c96d26e18e5a902a15b87b25146cb7e91 100644 (file)
@@ -2,16 +2,39 @@
 from zope.interface import Interface
 
 class INode(Interface):
-    """This is some sort of retrievable node."""
+    """This is some sort of retrievable node. All objects which implement
+    other I*Node interfaces also implement this one."""
+    def is_directory():
+        """Return True if this node is an internal directory node."""
 
 class IFileNode(Interface):
     """This is a file which can be retrieved."""
+    def get_uri():
+        """Return the URI of the target file. This URI can be passed
+        to an IDownloader to retrieve the data."""
 
 class IDirectoryNode(Interface):
     """This is a directory which can be listed."""
+    # these calls do not modify the subtree
     def list():
-        """Return a list of names which are children of this node."""
-
+        """Return a dictionary mapping each childname to a node. These nodes
+        implement various I*Node interfaces depending upon what they can do."""
+    def get(childname):
+        """Return a child node. Raises NoSuchChildError if there is no
+        child of that name."""
+    def get_subtree():
+        """Return the ISubTree which contains this node."""
+
+    # the following calls modify the subtree. After calling them, you must
+    # tell the enclosing subtree to serialize and upload itself. They can
+    # only be called if this directory node is associated with a mutable
+    # subtree.
+    def delete(childname):
+        """Delete any child referenced by this name."""
+    def add_subdir(childname):
+        """Create a new directory node, and return it."""
+    def add(childname, node):
+        """Add a new node to this path. Returns self."""
 
 class ISubTree(Interface):
     """A subtree is a collection of Nodes: files, directories, other trees.
@@ -27,25 +50,27 @@ class ISubTree(Interface):
     a DirectoryNode, or it might be a FileNode.
     """
 
-    def get(path, opener):
-        """Return a Deferred that fires with the node at the given path, or
-        None if there is no such node. This will traverse and create subtrees
-        as necessary."""
+    def populate_from_specification(spec, parent_is_mutable, downloader):
+        """Given a specification tuple, arrange to populate this subtree by
+        pulling data from some source (possibly the mesh, or the queen, or an
+        HTTP server, or the local filesystem). Return a Deferred that will
+        fire (with self) when this subtree is ready for use (specifically
+        when it is ready for get() and add() calls).
+        """
 
-    def add(path, child, opener, work_queue):
-        """Add 'child' (which must implement INode) to the tree at 'path'
-        (which must be a list of pathname components). This will schedule all
-        the work necessary to cause the child to be added reliably."""
+    def populate_from_node(node, parent_is_mutable, downloader):
+        """Like populate_from_specification."""
 
-    def find_lowest_containing_subtree_for_path(path, opener):
-        # not for external use. This is used internally by add().
-        """Find the subtree which contains the target path, opening new
-        subtrees if necessary. Return a Deferred that fires with (subtree,
-        prepath, postpath), where prepath is the list of path components that
-        got to the subtree, and postpath is the list of remaining path
-        components (indicating a subpath within the resulting subtree). This
-        will traverse and even create subtrees as necessary."""
+    def populate_from_data(data):
+        """Used internally by populate_from_specification. This is called
+        with a sequence of bytes that describes the contents of the subtree,
+        probably a bencoded tuple or s-expression. Returns self.
+        """
 
+    def unserialize(serialized_data):
+        """Populate all nodes from serialized_data, previously created by
+        calling my serialize() method. 'serialized_data' is a series of
+        nested lists (s-expressions), probably recorded in bencoded form."""
 
     def is_mutable():
         """This returns True if we have the ability to modify this subtree.
@@ -54,63 +79,85 @@ class ISubTree(Interface):
         """
 
     def get_node_for_path(path):
-        """Ask this subtree to follow the path through its internal nodes. If
-        the path terminates within this subtree, return (True, node), where
-        'node' implements INode (and also IMutableNode if this subtree
-        is_mutable). If the path takes us beyond this subtree, return (False,
-        next_subtree_spec, subpath), where 'next_subtree_spec' is a string
-        that can be passed to an Opener to create a new subtree, and
-        'subpath' is the subset of 'path' that can be passed to this new
-        subtree. If the path cannot be found within the subtree (and it is
-        not in the domain of some child subtree), return None.
+        """Ask this subtree to follow the path through its internal nodes.
+
+        Returns a tuple of (found_path, node, remaining_path). This method
+        operations synchronously, and does not return a Deferred.
+
+        (found_path=path, found_node, [])
+        If the path terminates within this subtree, found_path=path and
+        remaining_path=[], and the node will be an internal IDirectoryNode.
+
+        (found_path, last_node, remaining_path)
+        If the path does not terminate within this subtree but neither does
+        it exit this subtree, the last internal IDirectoryNode that *was* on
+        the path will be returned in 'node'. The path components that led to
+        this node will be in found_path, and the remaining components will be
+        in remaining_path. If you want to create the target node, loop over
+        remaining_path as follows::
+
+         while remaining_path:
+           node = node.add_subdir(remaining_path.pop(0))
+
+        (found_path, exit_node, remaining_path)
+        If the path leaves this subtree, 'node' will be a different kind of
+        INode (probably one that points at a child directory of some sort),
+        found_path will be the components that led to this point, and
+        remaining_path will be the remaining components. If you still wish to
+        locate the target, use 'node' to open a new subtree, then provide
+        'remaining_path' to the new subtree's get_node_for_path() method.
+
         """
 
-    def get_or_create_node_for_path(path):
-        """Like get_node_for_path, but instead of returning None, the subtree
-        will create internal nodes as necessary. Therefore it always returns
-        either (True, node), or (False, next_subtree_spec, prepath, postpath).
+    def update(prepath, workqueue):
+        """Perform and schedule whatever work is necessary to record this
+        subtree to persistent storage and update the parent at 'prepath'
+        with a new child specification.
+
+        For directory subtrees, this will cause the subtree to serialize
+        itself to a file, then add instructions to the workqueue to first
+        upload this file to the mesh, then add the file's URI to the parent's
+        subtree. The second instruction will possibly cause recursion, until
+        some subtree is updated which does not require notifying the parent.
         """
 
     def serialize():
         """Return a series of nested lists which describe my structure
         in a form that can be bencoded."""
 
-    def unserialize(serialized_data):
-        """Populate all nodes from serialized_data, previously created by
-        calling my serialize() method. 'serialized_data' is a series of
-        nested lists (s-expressions), probably recorded in bencoded form."""
-
-
-class IMutableSubTree(Interface):
-    def mutation_affects_parent():
-        """This returns True for CHK nodes where you must inform the parent
-        of the new URI each time you change the child subtree. It returns
-        False for SSK nodes (or other nodes which have a pointer stored in
-        some mutable form).
-        """
 
-    def add_subpath(subpath, child_spec, work_queue):
-        """Ask this subtree to add the given child to an internal node at the
-        given subpath. The subpath must not exit the subtree through another
-        subtree (specifically get_subtree_for_path(subpath) must either
-        return None or (True,node), and in the latter case, this subtree will
-        create new internal nodes as necessary).
-
-        The subtree will probably serialize itself to a file and add steps to
-        the work queue to accomplish its goals.
-
-        This returns a Deferred (the value of which is ignored) when
-        everything has been added to the work queue.
-        """
-
-    def serialize_to_file(f):
-        """Write a bencoded data structure to the given filehandle that can
-        be used to reproduce the contents of this subtree."""
-
-class ISubTreeSpecification(Interface):
-    def serialize():
-        """Return a tuple that describes this subtree. This tuple can be
-        passed to IOpener.open() to reconstitute the subtree."""
+#class IMutableSubTree(Interface):
+#    def mutation_affects_parent():
+#        """This returns True for CHK nodes where you must inform the parent
+#        of the new URI each time you change the child subtree. It returns
+#        False for SSK nodes (or other nodes which have a pointer stored in
+#        some mutable form).
+#        """
+#
+#    def add_subpath(subpath, child_spec, work_queue):
+#        """Ask this subtree to add the given child to an internal node at the
+#        given subpath. The subpath must not exit the subtree through another
+#        subtree (specifically get_subtree_for_path(subpath) must either
+#        return None or (True,node), and in the latter case, this subtree will
+#        create new internal nodes as necessary).
+#
+#        The subtree will probably serialize itself to a file and add steps to
+#        the work queue to accomplish its goals.
+#
+#        This returns a Deferred (the value of which is ignored) when
+#        everything has been added to the work queue.
+#        """
+#
+#    def serialize_to_file(f):
+#        """Write a bencoded data structure to the given filehandle that can
+#        be used to reproduce the contents of this subtree."""
+#
+#class ISubTreeSpecification(Interface):
+#    def serialize():
+#        """Return a tuple that describes this subtree. This tuple can be
+#        passed to IOpener.open() to reconstitute the subtree. It can also be
+#        bencoded and stuffed in a series of persistent bytes somewhere on the
+#        mesh or in a file."""
 
 class IOpener(Interface):
     def open(subtree_specification, parent_is_mutable):
@@ -121,3 +168,51 @@ class IOpener(Interface):
         local disk, or asking some central-service node for the current
         value."""
 
+
+class IVirtualDrive(Interface):
+
+    # commands to manipulate files
+
+    def list(path):
+        """List the contents of the directory at the given path.
+
+        'path' is a list of strings (empty to refer to the root directory)
+        and must refer to a DIRECTORY node. This method returns a Deferred
+        that fires with a dictionary that maps strings to filetypes. The
+        strings are useful as path name components. The filetypes are
+        Interfaces: either IDirectoryNode if path+[childname] can be used in
+        a 'list' method, or IFileNode if path+[childname] can be used in a
+        'download' method.
+        """
+
+    def download(path, target):
+        """Download the file at the given path to 'target'.
+
+        'path' must refer to a FILE. 'target' must implement IDownloadTarget.
+        This returns a Deferred that fires (with 'target') when the download
+        is complete.
+        """
+
+    def upload_now(path, uploadable):
+        """Upload a file to the given path. The path must not already exist.
+
+        path[:-1] must refer to a writable DIRECTORY node. 'uploadable' must
+        implement IUploadable. This returns a Deferred that fires (with
+        'uploadable') when the upload is complete.
+        """
+
+    def upload_later(path, filename):
+        """Upload a file from disk to the given path.
+        """
+
+    def delete(path):
+        """Delete the file or directory at the given path.
+
+        Returns a Deferred that fires (with self) when the delete is
+        complete.
+        """
+
+    # commands to manipulate subtrees
+
+    # ... detach subtree, merge subtree, etc
+
index cccb39dac359ec6df9fb066b7466036dd84e65ef..ce6dd39e361b24440b160450a899bdc66e4b119e 100644 (file)
@@ -1,35 +1,38 @@
 
 from zope.interface import implements
 from twisted.internet import defer
-from allmydata.util import bencode
-from allmydata.filetree import interfaces, directory
-from allmydata.filetree import specification as fspec
-from allmydata.filetree.file import CHKFile, MutableSSKFile, ImmutableSSKFile
-
-def unserialize_subtree_specification(serialized_spec):
-    assert isinstance(serialized_spec, tuple)
-    for stype in [fspec.CHKDirectorySpecification,
-                  fspec.ImmutableSSKDirectorySpecification,
-                  fspec.MutableSSKDirectorySpecification,
-                  fspec.LocalFileRedirection,
-                  fspec.QueenRedirection,
-                  fspec.HTTPRedirection,
-                  fspec.QueenOrLocalFileRedirection,
-                  ]:
-        if tuple[0] == stype:
-            spec = stype()
-            spec.unserialize(serialized_spec)
-            return spec
-    raise RuntimeError("unable to unserialize subtree specification '%s'" %
-                       (serialized_spec,))
-
+from allmydata.filetree import interfaces, directory, redirect
+#from allmydata.filetree.file import CHKFile, MutableSSKFile, ImmutableSSKFile
+#from allmydata.filetree.specification import unserialize_subtree_specification
+
+all_openable_subtree_types = [
+    directory.CHKDirectorySubTree,
+    directory.SSKDirectorySubTree,
+    redirect.LocalFileRedirection,
+    redirect.QueenRedirection,
+    redirect.HTTPRedirection,
+    redirect.QueenOrLocalFileRedirection,
+    ]
 
 class Opener(object):
     implements(interfaces.IOpener)
-    def __init__(self, queen):
+    def __init__(self, queen, downloader):
         self._queen = queen
+        self._downloader = downloader
         self._cache = {}
 
+    def _create(self, spec, parent_is_mutable):
+        assert isinstance(spec, tuple)
+        for subtree_class in all_openable_subtree_types:
+            if spec[0] == subtree_class.stype:
+                subtree = subtree_class()
+                d = subtree.populate_from_specification(spec,
+                                                        parent_is_mutable,
+                                                        self._downloader)
+                return d
+        raise RuntimeError("unable to handle subtree specification '%s'"
+                           % (spec,))
+
     def open(self, subtree_specification, parent_is_mutable):
         spec = interfaces.ISubTreeSpecification(subtree_specification)
 
@@ -37,38 +40,16 @@ class Opener(object):
         if spec in self._cache:
             return defer.succeed(self._cache[spec])
 
-        # is it a file?
-        if isinstance(spec, fspec.CHKFileSpecification):
-            return self._get_chk_file(spec)
-        if isinstance(spec, (fspec.MutableSSKFileSpecification,
-                             fspec.ImmutableSSKFileSpecification)):
-            return self._get_ssk_file(spec)
-
-        # is it a directory?
-        if isinstance(spec, fspec.CHKDirectorySpecification):
-            return self._get_chk_dir(spec, parent_is_mutable)
-        if isinstance(spec, (fspec.ImmutableSSKDirectorySpecification,
-                             fspec.MutableSSKDirectorySpecification)):
-            return self._get_ssk_dir(spec)
-
-        # is it a redirection to a file or directory?
-        if isinstance(spec, fspec.LocalFileRedirection):
-            return self._get_local_redir(spec)
-        if isinstance(spec, fspec.QueenRedirection):
-            return self._get_queen_redir(spec)
-        if isinstance(spec, fspec.HTTPRedirection):
-            return self._get_http_redir(spec)
-        if isinstance(spec, fspec.QueenOrLocalFileRedirection):
-            return self._get_queen_or_local_redir(spec)
-
-        # none of the above
-        raise RuntimeError("I do not know how to open '%s'" % (spec,))
+        d = defer.maybeDeferred(self._create, spec, parent_is_mutable)
+        d.addCallback(self._add_to_cache, spec)
+        return d
 
     def _add_to_cache(self, subtree, spec):
         self._cache[spec] = subtree
         # TODO: remove things from the cache eventually
         return subtree
 
+"""
     def _get_chk_file(self, spec):
         subtree = CHKFile(spec.get_uri())
         return defer.succeed(subtree)
@@ -82,93 +63,4 @@ class Opener(object):
             subtree = ImmutableSSKFile(spec.get_read_cap())
         return defer.succeed(subtree)
 
-    def _get_chk_dir(self, spec, parent_is_mutable):
-        uri = spec.get_uri()
-        if parent_is_mutable:
-            subtree = directory.MutableCHKDirectorySubTree()
-            subtree.set_uri(uri)
-        else:
-            subtree = directory.ImmutableDirectorySubTree()
-        d = self.downloader.get_chk(uri)
-        d.addCallback(subtree.unserialize)
-        d.addCallback(self._add_to_cache, spec)
-        return d
-
-    def _get_ssk_dir(self, spec):
-        mutable = isinstance(spec, fspec.ImmutableSSKDirectorySpecification)
-        if mutable:
-            subtree = directory.ImmutableDirectorySubTree()
-        else:
-            assert isinstance(spec, fspec.MutableSSKDirectorySpecification)
-            subtree = directory.MutableSSKDirectorySubTree()
-            subtree.set_write_capability(spec.get_write_capability())
-        read_cap = spec.get_read_capability()
-        subtree.set_read_capability(read_cap)
-        d = self.downloader.get_ssk_latest(read_cap)
-        def _set_version(res):
-            version, data = res
-            if mutable:
-                subtree.set_version(version)
-            return data
-        d.addCallback(_set_version)
-        d.addCallback(subtree.unserialize)
-        d.addCallback(self._add_to_cache, spec)
-        return d
-
-    def _get_local_redir(self, spec):
-        # there is a local file which contains a bencoded serialized
-        # subtree specification.
-        filename = spec.get_filename()
-        # TODO: will this enable outsiders to cause us to read from
-        # arbitrary files? Think about this.
-        f = open(filename, "rb")
-        data = bencode.bdecode(f.read())
-        f.close()
-        # note: we don't cache the contents of the file. TODO: consider
-        # doing this based upon mtime. It is important that we be able to
-        # notice if the file has been changed.
-        new_spec = unserialize_subtree_specification(data)
-        return self.open(new_spec, True)
-
-    def _get_queen_redir(self, spec):
-        # this specifies a handle for which the Queen maintains a
-        # serialized subtree specification.
-        handle = spec.get_handle()
-        d = self._queen.callRemote("lookup_handle", handle)
-        d.addCallback(unserialize_subtree_specification)
-        d.addCallback(self.open, True)
-        return d
-
-    def _get_http_redir(self, spec):
-        # this specifies a URL at which there is a bencoded serialized
-        # subtree specification.
-        url = spec.get_url()
-        from twisted.web import client
-        d = client.getPage(url)
-        d.addCallback(bencode.bdecode)
-        d.addCallback(unserialize_subtree_specification)
-        d.addCallback(self.open, False)
-        return d
-
-    def _get_queen_or_local_redir(self, spec):
-        # there is a local file which contains a bencoded serialized
-        # subtree specification. The queen also has a copy. Whomever has
-        # the higher version number wins.
-        filename = spec.get_filename()
-        f = open(filename, "rb")
-        local_version, local_data = bencode.bdecode(f.read())
-        f.close()
-        handle = spec.get_handle()
-        # TODO: pubsub so we can cache the queen's results
-        d = self._queen.callRemote("lookup_handle", handle)
-        def _got_queen(response):
-            queen_version, queen_data = response
-            if queen_version > local_version:
-                return queen_data
-            return local_data
-        d.addCallback(_got_queen)
-        d.addCallback(unserialize_subtree_specification)
-        d.addCallback(self.open, True)
-        return d
-
-
+"""
diff --git a/src/allmydata/filetree/redirect.py b/src/allmydata/filetree/redirect.py
new file mode 100644 (file)
index 0000000..1c4fe24
--- /dev/null
@@ -0,0 +1,98 @@
+
+from allmydata.util import bencode
+
+class LocalFileRedirection(object):
+    stype = "LocalFileRedirection"
+
+    def populate_from_specification(self, spec, parent_is_mutable, downloader):
+        # return a Deferred that fires (with self) when this node is ready
+        # for use
+
+        (stype, filename) = spec
+        assert stype == self.stype
+        #filename = spec.get_filename()
+        # there is a local file which contains a bencoded serialized
+        # subtree specification.
+
+        # TODO: will this enable outsiders to cause us to read from
+        # arbitrary files? Think about this.
+        f = open(filename, "rb")
+        data = f.read()
+        f.close()
+        # note: we don't cache the contents of the file. TODO: consider
+        # doing this based upon mtime. It is important that we be able to
+        # notice if the file has been changed.
+
+        return self.populate_from_data(data)
+
+    def populate_from_data(self, data):
+        # data is a subtree specification for our one child
+        self.child_spec = bencode.bdecode(data)
+        return self
+
+class QueenRedirection(object):
+    stype = "QueenRedirection"
+
+    def populate_from_specification(self, spec, parent_is_mutable, downloader):
+        # this specifies a handle for which the Queen maintains a
+        # serialized subtree specification.
+        (stype, handle) = spec
+
+        # TODO: queen?
+        d = self._queen.callRemote("lookup_handle", handle)
+        d.addCallback(self.populate_from_data)
+        return d
+
+    def populate_from_data(self, data):
+        self.child_spec = bencode.bdecode(data)
+        return self
+
+class QueenOrLocalFileRedirection(object):
+    stype = "QueenOrLocalFileRedirection"
+
+    def populate_from_specification(self, spec, parent_is_mutable, downloader):
+        # there is a local file which contains a bencoded serialized
+        # subtree specification. The queen also has a copy. Whomever has
+        # the higher version number wins.
+        (stype, filename, handle) = spec
+
+        f = open(filename, "rb")
+        #local_version, local_data = bencode.bdecode(f.read())
+        local_version_and_data = f.read()
+        f.close()
+
+        # TODO: queen?
+        # TODO: pubsub so we can cache the queen's results
+        d = self._queen.callRemote("lookup_handle", handle)
+        d.addCallback(self._choose_winner, local_version_and_data)
+        return d
+
+    def _choose_winner(self, queen_version_and_data, local_version_and_data):
+        queen_version, queen_data = bencode.bdecode(queen_version_and_data)
+        local_version, local_data = bencode.bdecode(local_version_and_data)
+        if queen_version > local_version:
+            data = queen_data
+        else:
+            data = local_data
+        return self.populate_from_data(data)
+
+    def populate_from_data(self, data):
+        # NOTE: two layers of bencoding here, TODO
+        self.child_spec = bencode.bdecode(data)
+        return self
+
+class HTTPRedirection(object):
+    stype = "HTTPRedirection"
+
+    def populate_from_specification(self, spec, parent_is_mutable, downloader):
+        # this specifies a URL at which there is a bencoded serialized
+        # subtree specification.
+        (stype, url) = spec
+        from twisted.web import client
+        d = client.getPage(url)
+        d.addCallback(self.populate_from_data)
+        return d
+
+    def populate_from_data(self, data):
+        self.child_spec = bencode.bdecode(data)
+        return self
index d9d2a7e92f3ae33ca1619160f33a2a8d1ae9bfed..a09f9b3e2c3f9e9daa31d1244c2c05ec37e78e93 100644 (file)
@@ -1,4 +1,5 @@
 
+"""
 from zope.interface import implements
 from allmydata.filetree.interfaces import ISubTreeSpecification
 
@@ -40,87 +41,24 @@ class MutableSSKFileSpecification(ImmutableSSKFileSpecification):
         self.read_cap = data[1]
         self.write_cap = data[2]
 
-class CHKDirectorySpecification(object):
-    implements(ISubTreeSpecification)
-    stype = "CHK-Directory"
-    def set_uri(self, uri):
-        self.uri = uri
-    def serialize(self):
-        return (self.stype, self.uri)
-    def unserialize(self, data):
-        assert data[0] == self.stype
-        self.uri = data[1]
-
-class ImmutableSSKDirectorySpecification(object):
-    implements(ISubTreeSpecification)
-    stype = "SSK-Readonly-Directory"
-    def set_read_capability(self, read_cap):
-        self.read_cap = read_cap
-    def get_read_capability(self):
-        return self.read_cap
-    def serialize(self):
-        return (self.stype, self.read_cap)
-    def unserialize(self, data):
-        assert data[0] == self.stype
-        self.read_cap = data[1]
-
-class MutableSSKDirectorySpecification(ImmutableSSKDirectorySpecification):
-    implements(ISubTreeSpecification)
-    stype = "SSK-ReadWrite-Directory"
-    def set_write_capability(self, write_cap):
-        self.write_cap = write_cap
-    def get_write_capability(self):
-        return self.write_cap
-    def serialize(self):
-        return (self.stype, self.read_cap, self.write_cap)
-    def unserialize(self, data):
-        assert data[0] == self.stype
-        self.read_cap = data[1]
-        self.write_cap = data[2]
 
 
 
-class LocalFileRedirection(object):
-    implements(ISubTreeSpecification)
-    stype = "LocalFile"
-    def set_filename(self, filename):
-        self.filename = filename
-    def get_filename(self):
-        return self.filename
-    def serialize(self):
-        return (self.stype, self.filename)
-
-class QueenRedirection(object):
-    implements(ISubTreeSpecification)
-    stype = "QueenRedirection"
-    def set_handle(self, handle):
-        self.handle = handle
-    def get_handle(self):
-        return self.handle
-    def serialize(self):
-        return (self.stype, self.handle)
-
-class HTTPRedirection(object):
-    implements(ISubTreeSpecification)
-    stype = "HTTPRedirection"
-    def set_url(self, url):
-        self.url = url
-    def get_url(self):
-        return self.url
-    def serialize(self):
-        return (self.stype, self.url)
-
-class QueenOrLocalFileRedirection(object):
-    implements(ISubTreeSpecification)
-    stype = "QueenOrLocalFile"
-    def set_filename(self, filename):
-        self.filename = filename
-    def get_filename(self):
-        return self.filename
-    def set_handle(self, handle):
-        self.handle = handle
-    def get_handle(self):
-        return self.handle
-    def serialize(self):
-        return (self.stype, self.handle, self.filename)
+def unserialize_subtree_specification(serialized_spec):
+    assert isinstance(serialized_spec, tuple)
+    for stype in [CHKDirectorySpecification,
+                  ImmutableSSKDirectorySpecification,
+                  MutableSSKDirectorySpecification,
 
+                  LocalFileRedirection,
+                  QueenRedirection,
+                  HTTPRedirection,
+                  QueenOrLocalFileRedirection,
+                  ]:
+        if tuple[0] == stype:
+            spec = stype()
+            spec.unserialize(serialized_spec)
+            return spec
+    raise RuntimeError("unable to unserialize subtree specification '%s'" %
+                       (serialized_spec,))
+"""
diff --git a/src/allmydata/filetree/vdrive.py b/src/allmydata/filetree/vdrive.py
new file mode 100644 (file)
index 0000000..1ada7cd
--- /dev/null
@@ -0,0 +1,176 @@
+
+from allmydata.filetree import interfaces, opener
+
+class VirtualDrive(object):
+    implements(interfaces.IVirtualDrive)
+
+    def __init__(self, workqueue, downloader, root_specification):
+        self.workqueue = workqueue
+        workqueue.set_vdrive(self)
+        # TODO: queen?
+        self.opener = Opener(queen, downloader)
+        self.root_specification = root_specification
+
+    # these methods are used to walk through our subtrees
+
+    def _get_root(self):
+        return self.opener.open(self.root_specification, False)
+
+    def _get_node(self, path):
+        d = self._get_closest_node(path)
+        def _got_node((node, remaining_path)):
+            if remaining_path:
+                return None
+            return node
+        d.addCallback(_got_node)
+        return d
+
+    def _get_closest_node(self, path):
+        """Find the closest directory node parent for the desired path.
+        Return a Deferred that fires with (node, remaining_path).
+        """
+        d = self._get_root()
+        d.addCallback(self._get_closest_node_1, path)
+        return d
+
+    def _get_closest_node_1(self, subtree, path):
+        d = subtree.get_node_for_path(path)
+        d.addCallback(self._get_closest_node_2, subtree.is_mutable())
+        return d
+
+    def _get_closest_node_2(self, res, parent_is_mutable):
+        (found_path, node, remaining_path) = res
+        if node.is_directory():
+            # traversal done
+            return (node, remaining_path)
+        # otherwise, we must open and recurse into a new subtree
+        d = self.opener.open(node, parent_is_mutable)
+        def _opened(next_subtree):
+            next_subtree = ISubTree(next_subtree)
+            return self._get_closest_node_1(next_subtree, remaining_path)
+        d.addCallback(_opened)
+        return d
+
+    def _get_directory(self, path):
+        """Return a Deferred that fires with the IDirectoryNode at the given
+        path, or raise NoSuchDirectoryError if there is no such node. This
+        will traverse subtrees as necessary."""
+        d = self._get_node(path)
+        def _got_directory(node):
+            if not node:
+                raise NoSuchDirectoryError
+            assert interfaces.IDirectoryNode(node)
+            return node
+        d.addCallback(_got_directory)
+        return d
+
+    def _get_file(self, path):
+        """Return a Deferred that files with an IFileNode at the given path,
+        or raises a NoSuchDirectoryError or NoSuchChildError, or some other
+        error if the path refers to something other than a file."""
+        d = self._get_node(path)
+        def _got_node(node):
+            if not node:
+                raise NoSuchChildError
+            return IFileNode(node)
+        d.addCallback(_got_node)
+        return d
+
+    def _get_file_uri(self, path):
+        d = self._get_file(path)
+        d.addCallback(lambda filenode: filenode.get_uri())
+        return d
+
+    def _child_should_not_exist(self, path):
+        d = self._get_node(path)
+        def _got_node(node):
+            if node is not None:
+                raise PathAlreadyExistsError
+        d.addCallback(_got_node)
+        return d
+
+    def _child_should_exist(self, path):
+        d = self._get_node(path)
+        def _got_node(node):
+            if node is None:
+                raise PathDoesNotExistError
+        d.addCallback(_got_node)
+        return d
+
+    def _get_closest_node_and_prepath(self, path):
+        d = self._get_closest_node(path)
+        def _got_closest((node, remaining_path)):
+            prepath_len = len(path) - len(remaining_path)
+            prepath = path[:prepath_len]
+            assert path[prepath_len:] == remaining_path
+            return (prepath, node, remaining_path)
+        d.addCallback(_got_closest)
+        return d
+
+    # these are called by the workqueue
+
+    def add(self, path, new_node):
+        parent_path = path[:-1]
+        new_node_path = path[-1]
+        d = self._get_closest_node_and_prepath(parent_path)
+        def _got_closest((prepath, node, remaining_path)):
+            # now tell it to create any necessary parent directories
+            while remaining_path:
+                node = node.add_subdir(remaining_path.pop(0))
+            # 'node' is now the directory where the child wants to go
+            return node, prepath
+        d.addCallback(_got_closest)
+        def _add_new_node((node, prepath)):
+            node.add(new_node_path, new_node)
+            subtree = node.get_subtree()
+            # now, tell the subtree to serialize and upload itself, using the
+            # workqueue. The subtree will also queue a step to notify its
+            # parent (using 'prepath'), if necessary.
+            return subtree.update(prepath, self.workqueue)
+        d.addCallback(_add_new_node)
+        return d
+
+    # these are user-visible
+
+    def list(self, path):
+        d = self._get_directory(path)
+        d.addCallback(lambda node: node.list())
+        return d
+
+    def download(self, path, target):
+        d = self._get_file_uri(path)
+        d.addCallback(lambda uri: self.downloader.download(uri, target))
+        return d
+
+    def upload_now(self, path, uploadable):
+        # note: the first few steps of this do not use the workqueue, but I
+        # think things should remain consistent anyways. If the node is shut
+        # down before the file has finished uploading, then we forget all
+        # abou the file.
+        uploadable = IUploadable(uploadable)
+        d = self._child_should_not_exist(path)
+        # then we upload the file
+        d.addCallback(lambda ignored: self.uploader.upload(uploadable))
+        d.addCallback(lambda uri: self.workqueue.create_boxname(uri))
+        d.addCallback(lambda boxname:
+                      self.workqueue.add_addpath(boxname, path))
+        return d
+
+    def upload_later(self, path, filename):
+        boxname = self.workqueue.create_boxname()
+        self.workqueue.add_upload_chk(filename, boxname)
+        self.workqueue.add_addpath(boxname, path)
+
+    def delete(self, path):
+        parent_path = path[:-1]
+        orphan_path = path[-1]
+        d = self._get_closest_node_and_prepath(parent_path)
+        def _got_parent((prepath, node, remaining_path)):
+            assert not remaining_path
+            node.delete(orphan_path)
+            # now serialize and upload
+            subtree = node.get_subtree()
+            return subtree.update(prepath, self.workqueue)
+        d.addCallback(_got_parent)
+        return d
+
index a7434376272e6ed2fae9afcf2bfe5f1aa2a92123..65bfee3b59b4d33394cc6aa2e293d3e059c717ab 100644 (file)
@@ -5,7 +5,7 @@ from twisted.internet import defer
 from allmydata.filetree.interfaces import IOpener, IDirectoryNode
 from allmydata.filetree.directory import (ImmutableDirectorySubTree,
                                           SubTreeNode,
-                                          MutableCHKDirectorySubTree)
+                                          CHKDirectorySubTree)
 from allmydata.filetree.specification import (CHKFileSpecification,
                                               CHKDirectorySpecification)
 from allmydata import workqueue
@@ -303,3 +303,9 @@ class MultipleSubTrees(unittest.TestCase):
 
 
         return d
+
+del OneSubTree
+del MultipleSubTrees
+
+class Redirect(unittest.TestCase):
+    pass
index 29938e098fae184b5977962ca9fd4092cde087f7..9607fc93aeec18c97c05a6b5fd7be482d6918993 100644 (file)
@@ -279,6 +279,11 @@ class FileHandle:
         # the originator of the filehandle reserves the right to close it
         pass
 
+class IUploader(Interface):
+    def upload(uploadable):
+        """Upload the file. 'uploadable' must impement IUploadable. This
+        returns a Deferred which fires with the URI of the file."""
+
 class Uploader(service.MultiService):
     """I am a service that allows file uploading.
     """
@@ -296,7 +301,7 @@ class Uploader(service.MultiService):
         return hasher.digest()
 
     def upload(self, f):
-        # this returns (verifierid, encoding_params)
+        # this returns the URI
         assert self.parent
         assert self.running
         f = IUploadable(f)
index e1c06b1caf9731cbddaf8a6a757fb2172b669d22..14e83dfc6d077a3fb7328be1970bebbd3e26cfdf 100644 (file)
@@ -26,7 +26,7 @@ class IWorkQueue(Interface):
 
     def create_tempfile(suffix=""):
         """Return (f, filename)."""
-    def create_boxname():
+    def create_boxname(contents=None):
         """Return a unique box name (as a string)."""
 
     def add_upload_chk(source_filename, stash_uri_in_boxname):
@@ -156,6 +156,9 @@ class WorkQueue(object):
         # line specifies what kind of step it is
         assert self.seqnum < 1000 # TODO: don't let this grow unboundedly
 
+    def set_vdrive(self, vdrive):
+        self.vdrive = vdrive
+
     def create_tempfile(self, suffix=""):
         randomname = b2a(os.urandom(10))
         filename = randomname + suffix
@@ -342,8 +345,8 @@ class WorkQueue(object):
 
     def step_addpath(self, boxname, *path):
         data = self.read_from_box(boxname)
-        child_spec = unserialize(data)
-        return self.root.add_subpath(path, child_spec, self)
+        child_node = unserialize(data) # TODO: unserialize ?
+        return self.vdrive.add(path, node)
 
     def step_retain_ssk(self, index_a, read_key_a):
         pass