From c2520e4ec76195fb559834cd1f7509a7ca20d9d7 Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@lothar.com>
Date: Mon, 12 Oct 2009 20:12:32 -0700
Subject: [PATCH] client.create_mutable_file(contents=) now accepts a callable,
 which is 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            |  2 +-
 src/allmydata/interfaces.py        | 16 ++++++++++++++--
 src/allmydata/mutable/filenode.py  | 17 ++++++++++++++---
 src/allmydata/nodemaker.py         |  2 +-
 src/allmydata/test/test_mutable.py | 15 +++++++++++++++
 5 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/src/allmydata/client.py b/src/allmydata/client.py
index 1187291a..51ab2464 100644
--- a/src/allmydata/client.py
+++ b/src/allmydata/client.py
@@ -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):
diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py
index 66d742ee..9b960167 100644
--- a/src/allmydata/interfaces.py
+++ b/src/allmydata/interfaces.py
@@ -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.
         """
diff --git a/src/allmydata/mutable/filenode.py b/src/allmydata/mutable/filenode.py
index 12b052a0..afd71ffd 100644
--- a/src/allmydata/mutable/filenode.py
+++ b/src/allmydata/mutable/filenode.py
@@ -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)
diff --git a/src/allmydata/nodemaker.py b/src/allmydata/nodemaker.py
index a31163d6..01fdd72a 100644
--- a/src/allmydata/nodemaker.py
+++ b/src/allmydata/nodemaker.py
@@ -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)
diff --git a/src/allmydata/test/test_mutable.py b/src/allmydata/test/test_mutable.py
index ce36d12c..20d5f1cc 100644
--- a/src/allmydata/test/test_mutable.py
+++ b/src/allmydata/test/test_mutable.py
@@ -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)
-- 
2.45.2