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.
This commit is contained in:
Brian Warner 2009-10-12 20:12:32 -07:00
parent b30041c5ec
commit c2520e4ec7
5 changed files with 45 additions and 7 deletions

View File

@ -463,7 +463,7 @@ class Client(node.Node, pollmixin.PollMixin):
d.addCallback(lambda n: n.set_children(initial_children)) d.addCallback(lambda n: n.set_children(initial_children))
return d 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) return self.nodemaker.create_mutable_file(contents, keysize)
def upload(self, uploadable): def upload(self, uploadable):

View File

@ -2006,8 +2006,20 @@ class IClient(Interface):
""" """
def create_mutable_file(contents=""): def create_mutable_file(contents=""):
"""Create a new mutable file with contents, get back the URI string. """Create a new mutable file (with initial) contents, get back the
@param contents: the initial contents to place in the file. 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 @return: a Deferred that fires with tne (string) SSK URI for the new
file. file.
""" """

View File

@ -102,10 +102,11 @@ class MutableFileNode:
self._encprivkey = None self._encprivkey = None
return self 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 """Call this to create a brand-new mutable file. It will create the
shares, find homes for them, and upload the initial contents. Returns shares, find homes for them, and upload the initial contents (created
a Deferred that fires (with the MutableFileNode instance you should with the same rules as IClient.create_mutable_file() ). Returns a
Deferred that fires (with the MutableFileNode instance you should
use) when it completes. use) when it completes.
""" """
self._pubkey, self._privkey = pubkey, privkey self._pubkey, self._privkey = pubkey, privkey
@ -117,8 +118,18 @@ class MutableFileNode:
self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint) self._uri = WriteableSSKFileURI(self._writekey, self._fingerprint)
self._readkey = self._uri.readkey self._readkey = self._uri.readkey
self._storage_index = self._uri.storage_index self._storage_index = self._uri.storage_index
initial_contents = self._get_initial_contents(contents)
return self._upload(initial_contents, None) 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): def _encrypt_privkey(self, writekey, privkey):
enc = AES(writekey) enc = AES(writekey)
crypttext = enc.process(privkey) crypttext = enc.process(privkey)

View File

@ -80,7 +80,7 @@ class NodeMaker:
return node 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, n = MutableFileNode(self.storage_broker, self.secret_holder,
self.default_encoding_parameters, self.history) self.default_encoding_parameters, self.history)
d = self.key_generator.generate(keysize) d = self.key_generator.generate(keysize)

View File

@ -299,6 +299,21 @@ class Filenode(unittest.TestCase, testutil.ShouldFailMixin):
d.addCallback(_created) d.addCallback(_created)
return d 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): def test_create_with_too_large_contents(self):
BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1) BIG = "a" * (self.OLD_MAX_SEGMENT_SIZE + 1)
d = self.nodemaker.create_mutable_file(BIG) d = self.nodemaker.create_mutable_file(BIG)