From c2520e4ec76195fb559834cd1f7509a7ca20d9d7 Mon Sep 17 00:00:00 2001 From: Brian Warner 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.37.2