mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-23 14:52:26 +00:00
dirnode: add overwrite= to most API calls, defaulting to True. When False, this raises ExistingChildError rather than overwriting an existing child
This commit is contained in:
parent
1121df6664
commit
87c1e8e066
@ -7,7 +7,8 @@ import simplejson
|
||||
from allmydata.mutable.common import NotMutableError
|
||||
from allmydata.mutable.node import MutableFileNode
|
||||
from allmydata.interfaces import IMutableFileNode, IDirectoryNode,\
|
||||
IURI, IFileNode, IMutableFileURI, IVerifierURI, IFilesystemNode
|
||||
IURI, IFileNode, IMutableFileURI, IVerifierURI, IFilesystemNode, \
|
||||
ExistingChildError
|
||||
from allmydata.util import hashutil, mathutil
|
||||
from allmydata.util.hashutil import netstring
|
||||
from allmydata.util.limiter import ConcurrencyLimiter
|
||||
@ -70,11 +71,12 @@ class MetadataSetter:
|
||||
|
||||
|
||||
class Adder:
|
||||
def __init__(self, node, entries=None):
|
||||
def __init__(self, node, entries=None, overwrite=True):
|
||||
self.node = node
|
||||
if entries is None:
|
||||
entries = []
|
||||
self.entries = entries
|
||||
self.overwrite = overwrite
|
||||
|
||||
def set_node(self, name, node, metadata):
|
||||
self.entries.append( [name, node, metadata] )
|
||||
@ -91,6 +93,8 @@ class Adder:
|
||||
name, child, new_metadata = e
|
||||
assert isinstance(name, unicode)
|
||||
if name in children:
|
||||
if not self.overwrite:
|
||||
raise ExistingChildError("child '%s' already exists" % name)
|
||||
metadata = children[name][1].copy()
|
||||
else:
|
||||
metadata = {"ctime": now,
|
||||
@ -313,7 +317,7 @@ class NewDirectoryNode:
|
||||
d.addCallback(_got)
|
||||
return d
|
||||
|
||||
def set_uri(self, name, child_uri, metadata=None):
|
||||
def set_uri(self, name, child_uri, metadata=None, overwrite=True):
|
||||
"""I add a child (by URI) at the specific name. I return a Deferred
|
||||
that fires with the child node when the operation finishes. I will
|
||||
replace any existing child of the same name.
|
||||
@ -325,13 +329,13 @@ class NewDirectoryNode:
|
||||
NotMutableError."""
|
||||
assert isinstance(name, unicode)
|
||||
child_node = self._create_node(child_uri)
|
||||
d = self.set_node(name, child_node, metadata)
|
||||
d = self.set_node(name, child_node, metadata, overwrite)
|
||||
d.addCallback(lambda res: child_node)
|
||||
return d
|
||||
|
||||
def set_children(self, entries):
|
||||
def set_children(self, entries, overwrite=True):
|
||||
# this takes URIs
|
||||
a = Adder(self)
|
||||
a = Adder(self, overwrite=overwrite)
|
||||
node_entries = []
|
||||
for e in entries:
|
||||
if len(e) == 2:
|
||||
@ -344,7 +348,7 @@ class NewDirectoryNode:
|
||||
a.set_node(name, self._create_node(child_uri), metadata)
|
||||
return self._node.modify(a.modify)
|
||||
|
||||
def set_node(self, name, child, metadata=None):
|
||||
def set_node(self, name, child, metadata=None, overwrite=True):
|
||||
"""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
|
||||
@ -357,22 +361,22 @@ class NewDirectoryNode:
|
||||
return defer.fail(NotMutableError())
|
||||
assert isinstance(name, unicode)
|
||||
assert IFilesystemNode.providedBy(child), child
|
||||
a = Adder(self)
|
||||
a = Adder(self, overwrite=overwrite)
|
||||
a.set_node(name, child, metadata)
|
||||
d = self._node.modify(a.modify)
|
||||
d.addCallback(lambda res: child)
|
||||
return d
|
||||
|
||||
def set_nodes(self, entries):
|
||||
def set_nodes(self, entries, overwrite=True):
|
||||
if self.is_readonly():
|
||||
return defer.fail(NotMutableError())
|
||||
a = Adder(self, entries)
|
||||
a = Adder(self, entries, overwrite=overwrite)
|
||||
d = self._node.modify(a.modify)
|
||||
d.addCallback(lambda res: None)
|
||||
return d
|
||||
|
||||
|
||||
def add_file(self, name, uploadable, metadata=None):
|
||||
def add_file(self, name, uploadable, metadata=None, overwrite=True):
|
||||
"""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
|
||||
@ -383,7 +387,8 @@ class NewDirectoryNode:
|
||||
d = self._client.upload(uploadable)
|
||||
d.addCallback(lambda results: results.uri)
|
||||
d.addCallback(self._client.create_node_from_uri)
|
||||
d.addCallback(lambda node: self.set_node(name, node, metadata))
|
||||
d.addCallback(lambda node:
|
||||
self.set_node(name, node, metadata, overwrite))
|
||||
return d
|
||||
|
||||
def delete(self, name):
|
||||
@ -397,7 +402,7 @@ class NewDirectoryNode:
|
||||
d.addCallback(lambda res: deleter.old_child)
|
||||
return d
|
||||
|
||||
def create_empty_directory(self, name):
|
||||
def create_empty_directory(self, name, overwrite=True):
|
||||
"""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."""
|
||||
@ -407,7 +412,7 @@ class NewDirectoryNode:
|
||||
d = self._client.create_empty_dirnode()
|
||||
def _created(child):
|
||||
entries = [(name, child, None)]
|
||||
a = Adder(self, entries)
|
||||
a = Adder(self, entries, overwrite=overwrite)
|
||||
d = self._node.modify(a.modify)
|
||||
d.addCallback(lambda res: child)
|
||||
return d
|
||||
@ -415,7 +420,7 @@ class NewDirectoryNode:
|
||||
return d
|
||||
|
||||
def move_child_to(self, current_child_name, new_parent,
|
||||
new_child_name=None):
|
||||
new_child_name=None, overwrite=True):
|
||||
"""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
|
||||
@ -428,7 +433,8 @@ class NewDirectoryNode:
|
||||
assert isinstance(new_child_name, unicode)
|
||||
d = self.get(current_child_name)
|
||||
def sn(child):
|
||||
return new_parent.set_node(new_child_name, child)
|
||||
return new_parent.set_node(new_child_name, child,
|
||||
overwrite=overwrite)
|
||||
d.addCallback(sn)
|
||||
d.addCallback(lambda child: self.delete(current_child_name))
|
||||
return d
|
||||
|
@ -725,6 +725,10 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode):
|
||||
writer-visible data using this writekey.
|
||||
"""
|
||||
|
||||
class ExistingChildError(Exception):
|
||||
"""A directory node was asked to add or replace a child that already
|
||||
exists, and overwrite= was set to False."""
|
||||
|
||||
class IDirectoryNode(IMutableFilesystemNode):
|
||||
"""I represent a name-to-child mapping, holding the tahoe equivalent of a
|
||||
directory. All child names are unicode strings, and all children are some
|
||||
@ -790,10 +794,12 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
path-name elements. All elements must be unicode strings.
|
||||
"""
|
||||
|
||||
def set_uri(name, child_uri, metadata=None):
|
||||
def set_uri(name, child_uri, metadata=None, overwrite=True):
|
||||
"""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 name must be a unicode string.
|
||||
that fires when the operation finishes. If overwrite= is True, I will
|
||||
replace any existing child of the same name, otherwise an existing
|
||||
child will cause me to return ExistingChildError. The child name must
|
||||
be a unicode string.
|
||||
|
||||
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() ).
|
||||
@ -808,7 +814,7 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
If this directory node is read-only, the Deferred will errback with a
|
||||
NotMutableError."""
|
||||
|
||||
def set_children(entries):
|
||||
def set_children(entries, overwrite=True):
|
||||
"""Add multiple (name, child_uri) pairs (or (name, child_uri,
|
||||
metadata) triples) to a directory node. Returns a Deferred that fires
|
||||
(with None) when the operation finishes. This is equivalent to
|
||||
@ -816,7 +822,7 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
child names must be unicode strings.
|
||||
"""
|
||||
|
||||
def set_node(name, child, metadata=None):
|
||||
def set_node(name, child, metadata=None, overwrite=True):
|
||||
"""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
|
||||
@ -832,7 +838,7 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
If this directory node is read-only, the Deferred will errback with a
|
||||
NotMutableError."""
|
||||
|
||||
def set_nodes(entries):
|
||||
def set_nodes(entries, overwrite=True):
|
||||
"""Add multiple (name, child_node) pairs (or (name, child_node,
|
||||
metadata) triples) to a directory node. Returns a Deferred that fires
|
||||
(with None) when the operation finishes. This is equivalent to
|
||||
@ -840,7 +846,7 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
child names must be unicode strings."""
|
||||
|
||||
|
||||
def add_file(name, uploadable, metadata=None):
|
||||
def add_file(name, uploadable, metadata=None, overwrite=True):
|
||||
"""I upload a file (using the given IUploadable), then attach the
|
||||
resulting FileNode to the directory at the given name. I set metadata
|
||||
the same way as set_uri and set_node. The child name must be a
|
||||
@ -854,12 +860,13 @@ class IDirectoryNode(IMutableFilesystemNode):
|
||||
fires when the operation finishes. The child name must be a unicode
|
||||
string."""
|
||||
|
||||
def create_empty_directory(name):
|
||||
def create_empty_directory(name, overwrite=True):
|
||||
"""I create and attach an empty directory at the given name. The
|
||||
child name must be a unicode string. I return a Deferred that fires
|
||||
when the operation finishes."""
|
||||
|
||||
def move_child_to(current_child_name, new_parent, new_child_name=None):
|
||||
def move_child_to(current_child_name, new_parent, new_child_name=None,
|
||||
overwrite=True):
|
||||
"""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'. TODO: what
|
||||
|
@ -4,7 +4,7 @@ from zope.interface import implements
|
||||
from twisted.trial import unittest
|
||||
from allmydata import uri, dirnode, upload
|
||||
from allmydata.interfaces import IURI, IClient, IMutableFileNode, \
|
||||
INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode
|
||||
INewDirectoryURI, IReadonlyNewDirectoryURI, IFileNode, ExistingChildError
|
||||
from allmydata.util import hashutil, testutil
|
||||
from allmydata.test.common import make_chk_file_uri, make_mutable_file_uri, \
|
||||
FakeDirectoryNode, create_chk_filenode
|
||||
@ -186,15 +186,22 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
d.addCallback(lambda res: n.has_child(u"missing"))
|
||||
d.addCallback(lambda res: self.failIf(res))
|
||||
fake_file_uri = make_mutable_file_uri()
|
||||
other_file_uri = make_mutable_file_uri()
|
||||
m = Marker(fake_file_uri)
|
||||
ffu_v = m.get_verifier()
|
||||
assert isinstance(ffu_v, str)
|
||||
self.expected_manifest.append(ffu_v)
|
||||
d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri))
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "set_uri-no",
|
||||
"child 'child' already exists",
|
||||
n.set_uri, u"child", other_file_uri,
|
||||
overwrite=False))
|
||||
# /
|
||||
# /child = mutable
|
||||
|
||||
d.addCallback(lambda res: n.create_empty_directory(u"subdir"))
|
||||
|
||||
# /
|
||||
# /child = mutable
|
||||
# /subdir = directory
|
||||
@ -206,6 +213,12 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
self.expected_manifest.append(new_v)
|
||||
d.addCallback(_created)
|
||||
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "mkdir-no",
|
||||
"child 'subdir' already exists",
|
||||
n.create_empty_directory, u"subdir",
|
||||
overwrite=False))
|
||||
|
||||
d.addCallback(lambda res: n.list())
|
||||
d.addCallback(lambda children:
|
||||
self.failUnlessEqual(sorted(children.keys()),
|
||||
@ -287,6 +300,12 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
# set_node + metadata
|
||||
# it should be possible to add a child without any metadata
|
||||
d.addCallback(lambda res: n.set_node(u"d2", n, {}))
|
||||
d.addCallback(lambda res: self.client.create_empty_dirnode())
|
||||
d.addCallback(lambda n2:
|
||||
self.shouldFail(ExistingChildError, "set_node-no",
|
||||
"child 'd2' already exists",
|
||||
n.set_node, u"d2", n2,
|
||||
overwrite=False))
|
||||
d.addCallback(lambda res: n.get_metadata_for(u"d2"))
|
||||
d.addCallback(lambda metadata: self.failUnlessEqual(metadata, {}))
|
||||
|
||||
@ -315,6 +334,16 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
(u"e3", fake_file_uri,
|
||||
{"key": "value"}),
|
||||
]))
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "set_children-no",
|
||||
"child 'e1' already exists",
|
||||
n.set_children,
|
||||
[ (u"e1", other_file_uri),
|
||||
(u"new", other_file_uri), ],
|
||||
overwrite=False))
|
||||
# and 'new' should not have been created
|
||||
d.addCallback(lambda res: n.list())
|
||||
d.addCallback(lambda children: self.failIf(u"new" in children))
|
||||
d.addCallback(lambda res: n.get_metadata_for(u"e1"))
|
||||
d.addCallback(lambda metadata:
|
||||
self.failUnlessEqual(sorted(metadata.keys()),
|
||||
@ -335,6 +364,16 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
(u"f3", n,
|
||||
{"key": "value"}),
|
||||
]))
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "set_nodes-no",
|
||||
"child 'f1' already exists",
|
||||
n.set_nodes,
|
||||
[ (u"f1", n),
|
||||
(u"new", n), ],
|
||||
overwrite=False))
|
||||
# and 'new' should not have been created
|
||||
d.addCallback(lambda res: n.list())
|
||||
d.addCallback(lambda children: self.failIf(u"new" in children))
|
||||
d.addCallback(lambda res: n.get_metadata_for(u"f1"))
|
||||
d.addCallback(lambda metadata:
|
||||
self.failUnlessEqual(sorted(metadata.keys()),
|
||||
@ -427,6 +466,13 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
d.addCallback(lambda res: n.add_file(u"newfile", uploadable))
|
||||
d.addCallback(lambda newnode:
|
||||
self.failUnless(IFileNode.providedBy(newnode)))
|
||||
other_uploadable = upload.Data("some data", convergence="stuff")
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "add_file-no",
|
||||
"child 'newfile' already exists",
|
||||
n.add_file, u"newfile",
|
||||
other_uploadable,
|
||||
overwrite=False))
|
||||
d.addCallback(lambda res: n.list())
|
||||
d.addCallback(lambda children:
|
||||
self.failUnlessEqual(sorted(children.keys()),
|
||||
@ -436,7 +482,6 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
self.failUnlessEqual(sorted(metadata.keys()),
|
||||
["ctime", "mtime"]))
|
||||
|
||||
uploadable = upload.Data("some data", convergence="some convergence string")
|
||||
d.addCallback(lambda res: n.add_file(u"newfile-metadata",
|
||||
uploadable,
|
||||
{"key": "value"}))
|
||||
@ -450,6 +495,9 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
d.addCallback(lambda res: n.create_empty_directory(u"subdir2"))
|
||||
def _created2(subdir2):
|
||||
self.subdir2 = subdir2
|
||||
# put something in the way, to make sure it gets overwritten
|
||||
return subdir2.add_file(u"child", upload.Data("overwrite me",
|
||||
"converge"))
|
||||
d.addCallback(_created2)
|
||||
|
||||
d.addCallback(lambda res:
|
||||
@ -462,6 +510,37 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
|
||||
d.addCallback(lambda children:
|
||||
self.failUnlessEqual(sorted(children.keys()),
|
||||
sorted([u"child"])))
|
||||
d.addCallback(lambda res: self.subdir2.get(u"child"))
|
||||
d.addCallback(lambda child:
|
||||
self.failUnlessEqual(child.get_uri(),
|
||||
fake_file_uri.to_string()))
|
||||
|
||||
# move it back, using new_child_name=
|
||||
d.addCallback(lambda res:
|
||||
self.subdir2.move_child_to(u"child", n, u"newchild"))
|
||||
d.addCallback(lambda res: n.list())
|
||||
d.addCallback(lambda children:
|
||||
self.failUnlessEqual(sorted(children.keys()),
|
||||
sorted([u"newchild", u"newfile",
|
||||
u"subdir2"])))
|
||||
d.addCallback(lambda res: self.subdir2.list())
|
||||
d.addCallback(lambda children:
|
||||
self.failUnlessEqual(sorted(children.keys()), []))
|
||||
|
||||
# now make sure that we honor overwrite=False
|
||||
d.addCallback(lambda res:
|
||||
self.subdir2.set_uri(u"newchild", other_file_uri))
|
||||
|
||||
d.addCallback(lambda res:
|
||||
self.shouldFail(ExistingChildError, "move_child_to-no",
|
||||
"child 'newchild' already exists",
|
||||
n.move_child_to, u"newchild",
|
||||
self.subdir2,
|
||||
overwrite=False))
|
||||
d.addCallback(lambda res: self.subdir2.get(u"newchild"))
|
||||
d.addCallback(lambda child:
|
||||
self.failUnlessEqual(child.get_uri(),
|
||||
other_file_uri.to_string()))
|
||||
|
||||
return d
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user