From d772ea850d5fa692807b62a9afea01ecf478a61f Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Thu, 1 Nov 2007 23:46:47 -0700
Subject: [PATCH] mutable: split dirnode stuff out to dirnode2.py, will be
 renamed later

---
 src/allmydata/client.py            |   4 +-
 src/allmydata/dirnode2.py          | 351 +++++++++++++++++++++++++++++
 src/allmydata/mutable.py           | 345 +---------------------------
 src/allmydata/test/test_mutable.py |   6 +-
 4 files changed, 358 insertions(+), 348 deletions(-)
 create mode 100644 src/allmydata/dirnode2.py

diff --git a/src/allmydata/client.py b/src/allmydata/client.py
index 6dbb8598..5fde8980 100644
--- a/src/allmydata/client.py
+++ b/src/allmydata/client.py
@@ -210,14 +210,14 @@ class Client(node.Node, Referenceable, testutil.PollMixin):
 
 
     def create_empty_dirnode(self):
-        from allmydata.mutable import NewDirectoryNode
+        from allmydata.dirnode2 import NewDirectoryNode
         n = NewDirectoryNode(self)
         d = n.create()
         d.addCallback(lambda res: n)
         return d
 
     def create_dirnode_from_uri(self, u):
-        from allmydata.mutable import NewDirectoryNode
+        from allmydata.dirnode2 import NewDirectoryNode
         return NewDirectoryNode(self).init_from_uri(u)
 
     def create_mutable_file(self, contents=""):
diff --git a/src/allmydata/dirnode2.py b/src/allmydata/dirnode2.py
new file mode 100644
index 00000000..21eeeddc
--- /dev/null
+++ b/src/allmydata/dirnode2.py
@@ -0,0 +1,351 @@
+
+import os
+
+from zope.interface import implements
+from twisted.internet import defer
+import simplejson
+from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\
+     IMutableFileURI, INewDirectoryURI, IURI, IFileNode, NotMutableError, \
+     IVerifierURI
+from allmydata.util import hashutil
+from allmydata.util.hashutil import netstring
+from allmydata.dirnode import IntegrityCheckError, FileNode
+from allmydata.uri import NewDirectoryURI
+from allmydata.Crypto.Cipher import AES
+
+from allmydata.mutable import MutableFileNode
+
+def split_netstring(data, numstrings, allow_leftover=False):
+    """like string.split(), but extracts netstrings. If allow_leftover=False,
+    returns numstrings elements, and throws ValueError if there was leftover
+    data. If allow_leftover=True, returns numstrings+1 elements, in which the
+    last element is the leftover data (possibly an empty string)"""
+    elements = []
+    assert numstrings >= 0
+    while data:
+        colon = data.index(":")
+        length = int(data[:colon])
+        string = data[colon+1:colon+1+length]
+        assert len(string) == length
+        elements.append(string)
+        assert data[colon+1+length] == ","
+        data = data[colon+1+length+1:]
+        if len(elements) == numstrings:
+            break
+    if len(elements) < numstrings:
+        raise ValueError("ran out of netstrings")
+    if allow_leftover:
+        return tuple(elements + [data])
+    if data:
+        raise ValueError("leftover data in netstrings")
+    return tuple(elements)
+
+class NewDirectoryNode:
+    implements(IDirectoryNode)
+    filenode_class = MutableFileNode
+
+    def __init__(self, client):
+        self._client = client
+    def init_from_uri(self, myuri):
+        u = INewDirectoryURI(myuri)
+        self._uri = u
+        self._node = self.filenode_class(self._client)
+        self._node.init_from_uri(u.get_filenode_uri())
+        return self
+
+    def create(self):
+        # first we create a MutableFileNode with empty_contents, then use its
+        # URI to create our own.
+        self._node = self.filenode_class(self._client)
+        empty_contents = self._pack_contents({})
+        d = self._node.create(empty_contents)
+        d.addCallback(self._filenode_created)
+        return d
+    def _filenode_created(self, res):
+        self._uri = NewDirectoryURI(self._node._uri)
+        return None
+
+    def _read(self):
+        d = self._node.download_to_data()
+        d.addCallback(self._unpack_contents)
+        return d
+
+    def _encrypt_rwcap(self, rwcap):
+        assert isinstance(rwcap, str)
+        IV = os.urandom(16)
+        key = hashutil.mutable_rwcap_key_hash(IV, self._node.writekey)
+        counterstart = "\x00"*16
+        cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
+        crypttext = cryptor.encrypt(rwcap)
+        mac = hashutil.hmac(key, IV + crypttext)
+        assert len(mac) == 32
+        return IV + crypttext + mac
+
+    def _decrypt_rwcapdata(self, encwrcap):
+        IV = encwrcap[:16]
+        crypttext = encwrcap[16:-32]
+        mac = encwrcap[-32:]
+        key = hashutil.mutable_rwcap_key_hash(IV, self._node.writekey)
+        if mac != hashutil.hmac(key, IV+crypttext):
+            raise IntegrityCheckError("HMAC does not match, crypttext is corrupted")
+        counterstart = "\x00"*16
+        cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
+        plaintext = cryptor.decrypt(crypttext)
+        return plaintext
+
+    def _create_node(self, child_uri):
+        u = IURI(child_uri)
+        if INewDirectoryURI.providedBy(u):
+            return self._client.create_dirnode_from_uri(u)
+        if IFileNode.providedBy(u):
+            return self._client.create_file_from_uri(u)
+        if IMutableFileURI.providedBy(u):
+            return self._client.create_mutable_file_from_uri(u)
+        raise TypeError("cannot handle '%s' URI" % (u.__class__,))
+
+    def _unpack_contents(self, data):
+        # the directory is serialized as a list of netstrings, one per child.
+        # Each child is serialized as a list of four netstrings: (name,
+        # rocap, rwcap, metadata), in which the name,rocap,metadata are in
+        # cleartext. The rwcap is formatted as:
+        #  pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac)
+        assert isinstance(data, str)
+        # an empty directory is serialized as an empty string
+        if data == "":
+            return {}
+        mutable = self.is_mutable()
+        children = {}
+        while len(data) > 0:
+            entry, data = split_netstring(data, 1, True)
+            name, rocap, rwcapdata, metadata_s = split_netstring(entry, 4)
+            if mutable:
+                rwcap = self._decrypt_rwcapdata(rwcapdata)
+                child = self._create_node(rwcap)
+            else:
+                child = self._create_node(rocap)
+            metadata = simplejson.loads(metadata_s)
+            assert isinstance(metadata, dict)
+            children[name] = (child, metadata)
+        return children
+
+    def _pack_contents(self, children):
+        # expects children in the same format as _unpack_contents
+        assert isinstance(children, dict)
+        entries = []
+        for name in sorted(children.keys()):
+            child, metadata = children[name]
+            assert (IFileNode.providedBy(child)
+                    or IMutableFileNode.providedBy(child)
+                    or IDirectoryNode.providedBy(child))
+            assert isinstance(metadata, dict)
+            rwcap = child.get_uri() # might be RO if the child is not mutable
+            rocap = child.get_readonly()
+            entry = "".join([netstring(name),
+                             netstring(rocap),
+                             netstring(self._encrypt_rwcap(rwcap)),
+                             netstring(simplejson.dumps(metadata))])
+            entries.append(netstring(entry))
+        return "".join(entries)
+
+    def is_readonly(self):
+        return self._node.is_readonly()
+    def is_mutable(self):
+        return self._node.is_mutable()
+
+    def get_uri(self):
+        return self._uri.to_string()
+
+    def get_readonly(self):
+        return self._uri.get_readonly().to_string()
+
+    def get_immutable_uri(self):
+        return self._uri.get_readonly().to_string()
+
+    def get_verifier(self):
+        return self._uri.get_verifier().to_string()
+
+    def check(self):
+        """Perform a file check. See IChecker.check for details."""
+        pass # TODO
+
+    def list(self):
+        """I return a Deferred that fires with a dictionary mapping child
+        name to an IFileNode or IDirectoryNode."""
+        return self._read()
+
+    def has_child(self, name):
+        """I return a Deferred that fires with a boolean, True if there
+        exists a child of the given name, False if not."""
+        d = self._read()
+        d.addCallback(lambda children: children.has_key(name))
+        return d
+
+    def get(self, name):
+        """I return a Deferred that fires with a specific named child node,
+        either an IFileNode or an IDirectoryNode."""
+        d = self._read()
+        d.addCallback(lambda children: children[name][0])
+        return d
+
+    def get_metadata_for(self, name):
+        d = self._read()
+        d.addCallback(lambda children: children[name][1])
+        return d
+
+    def get_child_at_path(self, path):
+        """Transform a child path into an IDirectoryNode or IFileNode.
+
+        I perform a recursive series of 'get' operations to find the named
+        descendant node. I return a Deferred that fires with the node, or
+        errbacks with IndexError if the node could not be found.
+
+        The path can be either a single string (slash-separated) or a list of
+        path-name elements.
+        """
+
+        if not path:
+            return defer.succeed(self)
+        if isinstance(path, (str, unicode)):
+            path = path.split("/")
+        childname = path[0]
+        remaining_path = path[1:]
+        d = self.get(childname)
+        if remaining_path:
+            def _got(node):
+                return node.get_child_at_path(remaining_path)
+            d.addCallback(_got)
+        return d
+
+    def set_uri(self, name, child_uri, metadata={}):
+        """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 this directory node is read-only, the Deferred will errback with a
+        NotMutableError."""
+        return self.set_node(name, self._create_node(child_uri), metadata)
+
+    def set_node(self, name, child, metadata={}):
+        """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 this directory node is read-only, the Deferred will errback with a
+        NotMutableError."""
+        if self.is_readonly():
+            return defer.fail(NotMutableError())
+        d = self._read()
+        def _add(children):
+            children[name] = (child, metadata)
+            new_contents = self._pack_contents(children)
+            return self._node.replace(new_contents)
+        d.addCallback(_add)
+        d.addCallback(lambda res: None)
+        return d
+
+    def add_file(self, name, uploadable):
+        """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."""
+        if self.is_readonly():
+            return defer.fail(NotMutableError())
+        uploader = self._client.getServiceNamed("uploader")
+        d = uploader.upload(uploadable)
+        d.addCallback(lambda uri: self.set_node(name,
+                                                FileNode(uri, self._client)))
+        return d
+
+    def delete(self, name):
+        """I remove the child at the specific name. I return a Deferred that
+        fires (with the node just removed) when the operation finishes."""
+        if self.is_readonly():
+            return defer.fail(NotMutableError())
+        d = self._read()
+        def _delete(children):
+            old_child, metadata = children[name]
+            del children[name]
+            new_contents = self._pack_contents(children)
+            d = self._node.replace(new_contents)
+            def _done(res):
+                return old_child
+            d.addCallback(_done)
+            return d
+        d.addCallback(_delete)
+        return d
+
+    def create_empty_directory(self, name):
+        """I create and attach an empty directory at the given name. I return
+        a Deferred that fires (with the new directory node) when the
+        operation finishes."""
+        if self.is_readonly():
+            return defer.fail(NotMutableError())
+        d = self._client.create_empty_dirnode()
+        def _created(child):
+            d = self.set_node(name, child)
+            d.addCallback(lambda res: child)
+            return d
+        d.addCallback(_created)
+        return d
+
+    def move_child_to(self, 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."""
+        if self.is_readonly() or new_parent.is_readonly():
+            return defer.fail(NotMutableError())
+        if new_child_name is None:
+            new_child_name = current_child_name
+        d = self.get(current_child_name)
+        d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
+        d.addCallback(lambda child: self.delete(current_child_name))
+        return d
+
+    def build_manifest(self):
+        """Return a frozenset of verifier-capability strings for all nodes
+        (directories and files) reachable from this one."""
+
+        # this is just a tree-walker, except that following each edge
+        # requires a Deferred.
+
+        manifest = set()
+        manifest.add(self.get_verifier())
+
+        d = self._build_manifest_from_node(self, manifest)
+        def _done(res):
+            # LIT nodes have no verifier-capability: their data is stored
+            # inside the URI itself, so there is no need to refresh anything.
+            # They indicate this by returning None from their get_verifier
+            # method. We need to remove any such Nones from our set. We also
+            # want to convert all these caps into strings.
+            return frozenset([IVerifierURI(cap).to_string()
+                              for cap in manifest
+                              if cap is not None])
+        d.addCallback(_done)
+        return d
+
+    def _build_manifest_from_node(self, node, manifest):
+        d = node.list()
+        def _got_list(res):
+            dl = []
+            for name, (child, metadata) in res.iteritems():
+                verifier = child.get_verifier()
+                if verifier not in manifest:
+                    manifest.add(verifier)
+                    if IDirectoryNode.providedBy(child):
+                        dl.append(self._build_manifest_from_node(child,
+                                                                 manifest))
+            if dl:
+                return defer.DeferredList(dl)
+        d.addCallback(_got_list)
+        return d
+
+# use client.create_dirnode() to make one of these
+
+
diff --git a/src/allmydata/mutable.py b/src/allmydata/mutable.py
index d6757c81..b4b86035 100644
--- a/src/allmydata/mutable.py
+++ b/src/allmydata/mutable.py
@@ -1,16 +1,9 @@
 
-import os
 from zope.interface import implements
 from twisted.internet import defer
-import simplejson
-from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\
-     IMutableFileURI, INewDirectoryURI, IURI, IFileNode, NotMutableError, \
-     IVerifierURI
+from allmydata.interfaces import IMutableFileNode, IMutableFileURI
 from allmydata.util import hashutil
-from allmydata.util.hashutil import netstring
-from allmydata.dirnode import IntegrityCheckError, FileNode
-from allmydata.uri import WriteableSSKFileURI, NewDirectoryURI
-from allmydata.Crypto.Cipher import AES
+from allmydata.uri import WriteableSSKFileURI
 
 class MutableFileNode:
     implements(IMutableFileNode)
@@ -76,337 +69,3 @@ class MutableFileNode:
         return defer.succeed(None)
 
 # use client.create_mutable_file() to make one of these
-
-def split_netstring(data, numstrings, allow_leftover=False):
-    """like string.split(), but extracts netstrings. If allow_leftover=False,
-    returns numstrings elements, and throws ValueError if there was leftover
-    data. If allow_leftover=True, returns numstrings+1 elements, in which the
-    last element is the leftover data (possibly an empty string)"""
-    elements = []
-    assert numstrings >= 0
-    while data:
-        colon = data.index(":")
-        length = int(data[:colon])
-        string = data[colon+1:colon+1+length]
-        assert len(string) == length
-        elements.append(string)
-        assert data[colon+1+length] == ","
-        data = data[colon+1+length+1:]
-        if len(elements) == numstrings:
-            break
-    if len(elements) < numstrings:
-        raise ValueError("ran out of netstrings")
-    if allow_leftover:
-        return tuple(elements + [data])
-    if data:
-        raise ValueError("leftover data in netstrings")
-    return tuple(elements)
-
-class NewDirectoryNode:
-    implements(IDirectoryNode)
-    filenode_class = MutableFileNode
-
-    def __init__(self, client):
-        self._client = client
-    def init_from_uri(self, myuri):
-        u = INewDirectoryURI(myuri)
-        self._uri = u
-        self._node = self.filenode_class(self._client)
-        self._node.init_from_uri(u.get_filenode_uri())
-        return self
-
-    def create(self):
-        # first we create a MutableFileNode with empty_contents, then use its
-        # URI to create our own.
-        self._node = self.filenode_class(self._client)
-        empty_contents = self._pack_contents({})
-        d = self._node.create(empty_contents)
-        d.addCallback(self._filenode_created)
-        return d
-    def _filenode_created(self, res):
-        self._uri = NewDirectoryURI(self._node._uri)
-        return None
-
-    def _read(self):
-        d = self._node.download_to_data()
-        d.addCallback(self._unpack_contents)
-        return d
-
-    def _encrypt_rwcap(self, rwcap):
-        assert isinstance(rwcap, str)
-        IV = os.urandom(16)
-        key = hashutil.mutable_rwcap_key_hash(IV, self._node.writekey)
-        counterstart = "\x00"*16
-        cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
-        crypttext = cryptor.encrypt(rwcap)
-        mac = hashutil.hmac(key, IV + crypttext)
-        assert len(mac) == 32
-        return IV + crypttext + mac
-
-    def _decrypt_rwcapdata(self, encwrcap):
-        IV = encwrcap[:16]
-        crypttext = encwrcap[16:-32]
-        mac = encwrcap[-32:]
-        key = hashutil.mutable_rwcap_key_hash(IV, self._node.writekey)
-        if mac != hashutil.hmac(key, IV+crypttext):
-            raise IntegrityCheckError("HMAC does not match, crypttext is corrupted")
-        counterstart = "\x00"*16
-        cryptor = AES.new(key=key, mode=AES.MODE_CTR, counterstart=counterstart)
-        plaintext = cryptor.decrypt(crypttext)
-        return plaintext
-
-    def _create_node(self, child_uri):
-        u = IURI(child_uri)
-        if INewDirectoryURI.providedBy(u):
-            return self._client.create_dirnode_from_uri(u)
-        if IFileNode.providedBy(u):
-            return self._client.create_file_from_uri(u)
-        if IMutableFileURI.providedBy(u):
-            return self._client.create_mutable_file_from_uri(u)
-        raise TypeError("cannot handle '%s' URI" % (u.__class__,))
-
-    def _unpack_contents(self, data):
-        # the directory is serialized as a list of netstrings, one per child.
-        # Each child is serialized as a list of four netstrings: (name,
-        # rocap, rwcap, metadata), in which the name,rocap,metadata are in
-        # cleartext. The rwcap is formatted as:
-        #  pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac)
-        assert isinstance(data, str)
-        # an empty directory is serialized as an empty string
-        if data == "":
-            return {}
-        mutable = self.is_mutable()
-        children = {}
-        while len(data) > 0:
-            entry, data = split_netstring(data, 1, True)
-            name, rocap, rwcapdata, metadata_s = split_netstring(entry, 4)
-            if mutable:
-                rwcap = self._decrypt_rwcapdata(rwcapdata)
-                child = self._create_node(rwcap)
-            else:
-                child = self._create_node(rocap)
-            metadata = simplejson.loads(metadata_s)
-            assert isinstance(metadata, dict)
-            children[name] = (child, metadata)
-        return children
-
-    def _pack_contents(self, children):
-        # expects children in the same format as _unpack_contents
-        assert isinstance(children, dict)
-        entries = []
-        for name in sorted(children.keys()):
-            child, metadata = children[name]
-            assert (IFileNode.providedBy(child)
-                    or IMutableFileNode.providedBy(child)
-                    or IDirectoryNode.providedBy(child))
-            assert isinstance(metadata, dict)
-            rwcap = child.get_uri() # might be RO if the child is not mutable
-            rocap = child.get_readonly()
-            entry = "".join([netstring(name),
-                             netstring(rocap),
-                             netstring(self._encrypt_rwcap(rwcap)),
-                             netstring(simplejson.dumps(metadata))])
-            entries.append(netstring(entry))
-        return "".join(entries)
-
-    def is_readonly(self):
-        return self._node.is_readonly()
-    def is_mutable(self):
-        return self._node.is_mutable()
-
-    def get_uri(self):
-        return self._uri.to_string()
-
-    def get_readonly(self):
-        return self._uri.get_readonly().to_string()
-
-    def get_immutable_uri(self):
-        return self._uri.get_readonly().to_string()
-
-    def get_verifier(self):
-        return self._uri.get_verifier().to_string()
-
-    def check(self):
-        """Perform a file check. See IChecker.check for details."""
-        pass # TODO
-
-    def list(self):
-        """I return a Deferred that fires with a dictionary mapping child
-        name to an IFileNode or IDirectoryNode."""
-        return self._read()
-
-    def has_child(self, name):
-        """I return a Deferred that fires with a boolean, True if there
-        exists a child of the given name, False if not."""
-        d = self._read()
-        d.addCallback(lambda children: children.has_key(name))
-        return d
-
-    def get(self, name):
-        """I return a Deferred that fires with a specific named child node,
-        either an IFileNode or an IDirectoryNode."""
-        d = self._read()
-        d.addCallback(lambda children: children[name][0])
-        return d
-
-    def get_metadata_for(self, name):
-        d = self._read()
-        d.addCallback(lambda children: children[name][1])
-        return d
-
-    def get_child_at_path(self, path):
-        """Transform a child path into an IDirectoryNode or IFileNode.
-
-        I perform a recursive series of 'get' operations to find the named
-        descendant node. I return a Deferred that fires with the node, or
-        errbacks with IndexError if the node could not be found.
-
-        The path can be either a single string (slash-separated) or a list of
-        path-name elements.
-        """
-
-        if not path:
-            return defer.succeed(self)
-        if isinstance(path, (str, unicode)):
-            path = path.split("/")
-        childname = path[0]
-        remaining_path = path[1:]
-        d = self.get(childname)
-        if remaining_path:
-            def _got(node):
-                return node.get_child_at_path(remaining_path)
-            d.addCallback(_got)
-        return d
-
-    def set_uri(self, name, child_uri, metadata={}):
-        """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 this directory node is read-only, the Deferred will errback with a
-        NotMutableError."""
-        return self.set_node(name, self._create_node(child_uri), metadata)
-
-    def set_node(self, name, child, metadata={}):
-        """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 this directory node is read-only, the Deferred will errback with a
-        NotMutableError."""
-        if self.is_readonly():
-            return defer.fail(NotMutableError())
-        d = self._read()
-        def _add(children):
-            children[name] = (child, metadata)
-            new_contents = self._pack_contents(children)
-            return self._node.replace(new_contents)
-        d.addCallback(_add)
-        d.addCallback(lambda res: None)
-        return d
-
-    def add_file(self, name, uploadable):
-        """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."""
-        if self.is_readonly():
-            return defer.fail(NotMutableError())
-        uploader = self._client.getServiceNamed("uploader")
-        d = uploader.upload(uploadable)
-        d.addCallback(lambda uri: self.set_node(name,
-                                                FileNode(uri, self._client)))
-        return d
-
-    def delete(self, name):
-        """I remove the child at the specific name. I return a Deferred that
-        fires (with the node just removed) when the operation finishes."""
-        if self.is_readonly():
-            return defer.fail(NotMutableError())
-        d = self._read()
-        def _delete(children):
-            old_child, metadata = children[name]
-            del children[name]
-            new_contents = self._pack_contents(children)
-            d = self._node.replace(new_contents)
-            def _done(res):
-                return old_child
-            d.addCallback(_done)
-            return d
-        d.addCallback(_delete)
-        return d
-
-    def create_empty_directory(self, name):
-        """I create and attach an empty directory at the given name. I return
-        a Deferred that fires (with the new directory node) when the
-        operation finishes."""
-        if self.is_readonly():
-            return defer.fail(NotMutableError())
-        d = self._client.create_empty_dirnode()
-        def _created(child):
-            d = self.set_node(name, child)
-            d.addCallback(lambda res: child)
-            return d
-        d.addCallback(_created)
-        return d
-
-    def move_child_to(self, 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."""
-        if self.is_readonly() or new_parent.is_readonly():
-            return defer.fail(NotMutableError())
-        if new_child_name is None:
-            new_child_name = current_child_name
-        d = self.get(current_child_name)
-        d.addCallback(lambda child: new_parent.set_node(new_child_name, child))
-        d.addCallback(lambda child: self.delete(current_child_name))
-        return d
-
-    def build_manifest(self):
-        """Return a frozenset of verifier-capability strings for all nodes
-        (directories and files) reachable from this one."""
-
-        # this is just a tree-walker, except that following each edge
-        # requires a Deferred.
-
-        manifest = set()
-        manifest.add(self.get_verifier())
-
-        d = self._build_manifest_from_node(self, manifest)
-        def _done(res):
-            # LIT nodes have no verifier-capability: their data is stored
-            # inside the URI itself, so there is no need to refresh anything.
-            # They indicate this by returning None from their get_verifier
-            # method. We need to remove any such Nones from our set. We also
-            # want to convert all these caps into strings.
-            return frozenset([IVerifierURI(cap).to_string()
-                              for cap in manifest
-                              if cap is not None])
-        d.addCallback(_done)
-        return d
-
-    def _build_manifest_from_node(self, node, manifest):
-        d = node.list()
-        def _got_list(res):
-            dl = []
-            for name, (child, metadata) in res.iteritems():
-                verifier = child.get_verifier()
-                if verifier not in manifest:
-                    manifest.add(verifier)
-                    if IDirectoryNode.providedBy(child):
-                        dl.append(self._build_manifest_from_node(child,
-                                                                 manifest))
-            if dl:
-                return defer.DeferredList(dl)
-        d.addCallback(_got_list)
-        return d
-
-# use client.create_dirnode() to make one of these
-
diff --git a/src/allmydata/test/test_mutable.py b/src/allmydata/test/test_mutable.py
index d7d78f4c..186112af 100644
--- a/src/allmydata/test/test_mutable.py
+++ b/src/allmydata/test/test_mutable.py
@@ -3,8 +3,8 @@ import itertools
 from twisted.trial import unittest
 from twisted.internet import defer
 
-from allmydata import mutable, uri
-from allmydata.mutable import split_netstring
+from allmydata import mutable, uri, dirnode2
+from allmydata.dirnode2 import split_netstring
 from allmydata.util.hashutil import netstring
 
 class Netstring(unittest.TestCase):
@@ -62,7 +62,7 @@ class FakeFilenode(mutable.MutableFileNode):
     def get_readonly(self):
         return "fake readonly"
 
-class FakeNewDirectoryNode(mutable.NewDirectoryNode):
+class FakeNewDirectoryNode(dirnode2.NewDirectoryNode):
     filenode_class = FakeFilenode
 
 class MyClient:
-- 
2.45.2