client.create_mutable_file(contents=) now accepts a callable, which is
authorBrian Warner <warner@lothar.com>
Tue, 13 Oct 2009 03:12:32 +0000 (20:12 -0700)
committerBrian Warner <warner@lothar.com>
Tue, 13 Oct 2009 03:12:32 +0000 (20:12 -0700)
invoked with the new MutableFileNode and is supposed to return the initial
contents. This can be used by e.g. a new dirnode which needs the filenode's
writekey to encrypt its initial children.

create_mutable_file() still accepts a bytestring too, or None for an empty
file.

src/allmydata/client.py
src/allmydata/interfaces.py
src/allmydata/mutable/filenode.py
src/allmydata/nodemaker.py
src/allmydata/test/test_mutable.py

index 1187291a9ea6c273318b17a195471c48a945074c..51ab2464f7ba9f3ece8e2434bc454c84cc0c9c18 100644 (file)
@@ -463,7 +463,7 @@ class Client(node.Node, pollmixin.PollMixin):
             d.addCallback(lambda n: n.set_children(initial_children))
         return d
 
-    def create_mutable_file(self, contents="", keysize=None):
+    def create_mutable_file(self, contents=None, keysize=None):
         return self.nodemaker.create_mutable_file(contents, keysize)
 
     def upload(self, uploadable):
index 66d742ee9d9213450974ca5176f6cd781c4f4f84..9b96016728d8639fa239110723e24225f755d2f1 100644 (file)
@@ -2006,8 +2006,20 @@ class IClient(Interface):
         """
 
     def create_mutable_file(contents=""):
-        """Create a new mutable file with contents, get back the URI string.
-        @param contents: the initial contents to place in the file.
+        """Create a new mutable file (with initial) contents, get back the
+        URI string.
+
+        @param contents: (bytestring, callable, or None): this provides the
+        initial contents of the mutable file. If 'contents' is a bytestring,
+        it will be used as-is. If 'contents' is a callable, it will be
+        invoked with the new MutableFileNode instance and is expected to
+        return a bytestring with the initial contents of the file (the
+        callable can use node.get_writekey() to decide how to encrypt the
+        initial contents, e.g. for a brand new dirnode with initial
+        children). contents=None is equivalent to an empty string. Using
+        content_maker= is more efficient than creating a mutable file and
+        setting its contents in two separate operations.
+
         @return: a Deferred that fires with tne (string) SSK URI for the new
                  file.
         """
index 12b052a0a57100c3e46ba4885aae95339fc121bf..afd71ffd0429c205c55842cba69d6e1a30f9fde9 100644 (file)
@@ -102,10 +102,11 @@ class MutableFileNode:
         self._encprivkey = None
         return self
 
-    def create_with_keys(self, (pubkey, privkey), initial_contents):
+    def create_with_keys(self, (pubkey, privkey), contents):
         """Call this to create a brand-new mutable file. It will create the
-        shares, find homes for them, and upload the initial contents. Returns
-        a Deferred that fires (with the MutableFileNode instance you should
+        shares, find homes for them, and upload the initial contents (created
+        with the same rules as IClient.create_mutable_file() ). Returns a
+        Deferred that fires (with the MutableFileNode instance you should
         use) when it completes.
         """
         self._pubkey, self._privkey = pubkey, privkey
@@ -117,8 +118,18 @@ class MutableFileNode:
         self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
         self._readkey = self._uri.readkey
         self._storage_index = self._uri.storage_index
+        initial_contents = self._get_initial_contents(contents)
         return self._upload(initial_contents, None)
 
+    def _get_initial_contents(self, contents):
+        if isinstance(contents, str):
+            return contents
+        if contents is None:
+            return ""
+        assert callable(contents), "%s should be callable, not %s" % \
+               (contents, type(contents))
+        return contents(self)
+
     def _encrypt_privkey(self, writekey, privkey):
         enc = AES(writekey)
         crypttext = enc.process(privkey)
index a31163d692e8a709c449f93f518e4d1ead2b8b77..01fdd72ab9f7a4565447358222e3873b753a9ac1 100644 (file)
@@ -80,7 +80,7 @@ class NodeMaker:
         return node
 
 
-    def create_mutable_file(self, contents="", keysize=None):
+    def create_mutable_file(self, contents=None, keysize=None):
         n = MutableFileNode(self.storage_broker, self.secret_holder,
                             self.default_encoding_parameters, self.history)
         d = self.key_generator.generate(keysize)
index ce36d12cd74616dfbd65cc106fdcfc4b31867cba..20d5f1cc5fd04827de0b6e261484db6e23dd8e72 100644 (file)
@@ -299,6 +299,21 @@ class Filenode(unittest.TestCase, testutil.ShouldFailMixin):
         d.addCallback(_created)
         return d
 
+    def test_create_with_initial_contents_function(self):
+        data = "initial contents"
+        def _make_contents(n):
+            self.failUnless(isinstance(n, MutableFileNode))
+            key = n.get_writekey()
+            self.failUnless(isinstance(key, str), key)
+            self.failUnlessEqual(len(key), 16) # AES key size
+            return data
+        d = self.nodemaker.create_mutable_file(_make_contents)
+        def _created(n):
+            return n.download_best_version()
+        d.addCallback(_created)
+        d.addCallback(lambda data2: self.failUnlessEqual(data2, data))
+        return d
+
     def test_create_with_too_large_contents(self):
         BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
         d = self.nodemaker.create_mutable_file(BIG)