mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-20 05:28:04 +00:00
dirnode.py: fix a bug in the no-write change for Adder, and improve test coverage. Add a 'metadata' argument to create_subdirectory, with documentation. Also update some comments in test_dirnode.py made stale by the ctime/mtime change.
This commit is contained in:
parent
db394671e9
commit
29a06457d2
@ -415,9 +415,11 @@ POST /uri?t=mkdir-with-children
|
|||||||
it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The
|
it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The
|
||||||
client must not put a write cap into a "ro_uri" field.
|
client must not put a write cap into a "ro_uri" field.
|
||||||
|
|
||||||
A file may have a "no-write" metadata field that affects how writes to
|
The metadata may have a "no-write" field. If this is set to true in the
|
||||||
it are handled via the SFTP frontend; see docs/frontends/FTP-and-SFTP.txt
|
metadata of a link, it will not be possible to open that link for writing
|
||||||
for details.
|
via the SFTP frontend; see docs/frontends/FTP-and-SFTP.txt for details.
|
||||||
|
Also, if the "no-write" field is set to true in the metadata of a link to
|
||||||
|
a mutable child, it will cause the link to be diminished to read-only.
|
||||||
|
|
||||||
Note that the webapi-using client application must not provide the
|
Note that the webapi-using client application must not provide the
|
||||||
"Content-Type: multipart/form-data" header that usually accompanies HTML
|
"Content-Type: multipart/form-data" header that usually accompanies HTML
|
||||||
@ -669,7 +671,9 @@ GET /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=json
|
|||||||
|
|
||||||
In Tahoe earlier than v1.4.0, only the 'mtime'/'ctime' keys were populated.
|
In Tahoe earlier than v1.4.0, only the 'mtime'/'ctime' keys were populated.
|
||||||
Starting in Tahoe v1.4.0, the 'linkmotime'/'linkcrtime' keys in the 'tahoe'
|
Starting in Tahoe v1.4.0, the 'linkmotime'/'linkcrtime' keys in the 'tahoe'
|
||||||
sub-dict are also populated.
|
sub-dict are also populated. However, prior to v1.7.0, a bug caused the
|
||||||
|
'tahoe' sub-dict to be deleted by webapi requests in which new metadata
|
||||||
|
is specified, and not to be added to existing child links that lack it.
|
||||||
|
|
||||||
The reason we added the new values in Tahoe v1.4.0 is that there is a
|
The reason we added the new values in Tahoe v1.4.0 is that there is a
|
||||||
"set_children" API (described below) which you can use to overwrite the
|
"set_children" API (described below) which you can use to overwrite the
|
||||||
|
@ -122,9 +122,10 @@ class MetadataSetter:
|
|||||||
raise NoSuchChildError(name)
|
raise NoSuchChildError(name)
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
metadata = update_metadata(children[name][1].copy(), self.metadata, now)
|
|
||||||
child = children[name][0]
|
child = children[name][0]
|
||||||
if self.create_readonly_node and metadata and metadata.get('no-write', False):
|
|
||||||
|
metadata = update_metadata(children[name][1].copy(), self.metadata, now)
|
||||||
|
if self.create_readonly_node and metadata.get('no-write', False):
|
||||||
child = self.create_readonly_node(child, name)
|
child = self.create_readonly_node(child, name)
|
||||||
|
|
||||||
children[name] = (child, metadata)
|
children[name] = (child, metadata)
|
||||||
@ -167,10 +168,11 @@ class Adder:
|
|||||||
raise ExistingChildError("child '%s' already exists" % name)
|
raise ExistingChildError("child '%s' already exists" % name)
|
||||||
metadata = children[name][1].copy()
|
metadata = children[name][1].copy()
|
||||||
|
|
||||||
if self.create_readonly_node and metadata and metadata.get('no-write', False):
|
metadata = update_metadata(metadata, new_metadata, now)
|
||||||
|
if self.create_readonly_node and metadata.get('no-write', False):
|
||||||
child = self.create_readonly_node(child, name)
|
child = self.create_readonly_node(child, name)
|
||||||
|
|
||||||
children[name] = (child, update_metadata(metadata, new_metadata, now))
|
children[name] = (child, metadata)
|
||||||
new_contents = self.node._pack_contents(children)
|
new_contents = self.node._pack_contents(children)
|
||||||
return new_contents
|
return new_contents
|
||||||
|
|
||||||
@ -591,7 +593,7 @@ class DirectoryNode:
|
|||||||
return d
|
return d
|
||||||
|
|
||||||
def create_subdirectory(self, name, initial_children={}, overwrite=True,
|
def create_subdirectory(self, name, initial_children={}, overwrite=True,
|
||||||
mutable=True):
|
mutable=True, metadata=None):
|
||||||
assert isinstance(name, unicode)
|
assert isinstance(name, unicode)
|
||||||
if self.is_readonly():
|
if self.is_readonly():
|
||||||
return defer.fail(NotWriteableError())
|
return defer.fail(NotWriteableError())
|
||||||
@ -600,7 +602,7 @@ class DirectoryNode:
|
|||||||
else:
|
else:
|
||||||
d = self._nodemaker.create_immutable_directory(initial_children)
|
d = self._nodemaker.create_immutable_directory(initial_children)
|
||||||
def _created(child):
|
def _created(child):
|
||||||
entries = {name: (child, None)}
|
entries = {name: (child, metadata)}
|
||||||
a = Adder(self, entries, overwrite=overwrite,
|
a = Adder(self, entries, overwrite=overwrite,
|
||||||
create_readonly_node=self._create_readonly_node)
|
create_readonly_node=self._create_readonly_node)
|
||||||
d = self._node.modify(a.modify)
|
d = self._node.modify(a.modify)
|
||||||
|
@ -985,7 +985,7 @@ class IDirectoryNode(IFilesystemNode):
|
|||||||
is a file, or if must_be_file is True and the child is a directory,
|
is a file, or if must_be_file is True and the child is a directory,
|
||||||
I raise ChildOfWrongTypeError."""
|
I raise ChildOfWrongTypeError."""
|
||||||
|
|
||||||
def create_subdirectory(name, initial_children={}, overwrite=True):
|
def create_subdirectory(name, initial_children={}, overwrite=True, metadata=None):
|
||||||
"""I create and attach a directory at the given name. The new
|
"""I create and attach a directory at the given name. The new
|
||||||
directory can be empty, or it can be populated with children
|
directory can be empty, or it can be populated with children
|
||||||
according to 'initial_children', which takes a dictionary in the same
|
according to 'initial_children', which takes a dictionary in the same
|
||||||
|
@ -775,8 +775,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
d.addCallback(lambda metadata:
|
d.addCallback(lambda metadata:
|
||||||
self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
|
self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
|
||||||
|
|
||||||
# or we can add specific metadata at set_uri() time, which
|
# we can also add specific metadata at set_uri() time
|
||||||
# overrides the timestamps
|
|
||||||
d.addCallback(lambda res: n.set_uri(u"c4",
|
d.addCallback(lambda res: n.set_uri(u"c4",
|
||||||
fake_file_uri, fake_file_uri,
|
fake_file_uri, fake_file_uri,
|
||||||
{"key": "value"}))
|
{"key": "value"}))
|
||||||
@ -790,7 +789,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
d.addCallback(lambda res: n.delete(u"c4"))
|
d.addCallback(lambda res: n.delete(u"c4"))
|
||||||
|
|
||||||
# set_node + metadata
|
# set_node + metadata
|
||||||
# it should be possible to add a child without any metadata
|
# it should be possible to add a child without any metadata except for timestamps
|
||||||
d.addCallback(lambda res: n.set_node(u"d2", n, {}))
|
d.addCallback(lambda res: n.set_node(u"d2", n, {}))
|
||||||
d.addCallback(lambda res: c.create_dirnode())
|
d.addCallback(lambda res: c.create_dirnode())
|
||||||
d.addCallback(lambda n2:
|
d.addCallback(lambda n2:
|
||||||
@ -808,8 +807,7 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
d.addCallback(lambda metadata:
|
d.addCallback(lambda metadata:
|
||||||
self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
|
self.failUnlessEqual(set(metadata.keys()), set(["tahoe", "ctime", "mtime"])))
|
||||||
|
|
||||||
# or we can add specific metadata at set_node() time, which
|
# we can also add specific metadata at set_node() time
|
||||||
# overrides the timestamps
|
|
||||||
d.addCallback(lambda res: n.set_node(u"d4", n,
|
d.addCallback(lambda res: n.set_node(u"d4", n,
|
||||||
{"key": "value"}))
|
{"key": "value"}))
|
||||||
d.addCallback(lambda res: n.get_metadata_for(u"d4"))
|
d.addCallback(lambda res: n.get_metadata_for(u"d4"))
|
||||||
@ -899,6 +897,11 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
metadata["tags"] == ["web2.0-compatible"] and
|
metadata["tags"] == ["web2.0-compatible"] and
|
||||||
"bad" not in metadata["tahoe"], metadata))
|
"bad" not in metadata["tahoe"], metadata))
|
||||||
|
|
||||||
|
d.addCallback(lambda res:
|
||||||
|
self.shouldFail(NoSuchChildError, "set_metadata_for-nosuch", "",
|
||||||
|
n.set_metadata_for, u"nosuch", {}))
|
||||||
|
|
||||||
|
|
||||||
def _start(res):
|
def _start(res):
|
||||||
self._start_timestamp = time.time()
|
self._start_timestamp = time.time()
|
||||||
d.addCallback(_start)
|
d.addCallback(_start)
|
||||||
@ -1047,6 +1050,35 @@ class Dirnode(GridTestMixin, unittest.TestCase,
|
|||||||
self.failUnlessEqual(child.get_uri(),
|
self.failUnlessEqual(child.get_uri(),
|
||||||
other_file_uri))
|
other_file_uri))
|
||||||
|
|
||||||
|
|
||||||
|
# Setting the no-write field should diminish a mutable cap to read-only
|
||||||
|
# (for both files and directories).
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: n.set_uri(u"mutable", other_file_uri, other_file_uri))
|
||||||
|
d.addCallback(lambda ign: n.get(u"mutable"))
|
||||||
|
d.addCallback(lambda mutable: self.failIf(mutable.is_readonly(), mutable))
|
||||||
|
d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
|
||||||
|
d.addCallback(lambda ign: n.get(u"mutable"))
|
||||||
|
d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
|
||||||
|
d.addCallback(lambda ign: n.set_metadata_for(u"mutable", {"no-write": True}))
|
||||||
|
d.addCallback(lambda ign: n.get(u"mutable"))
|
||||||
|
d.addCallback(lambda mutable: self.failUnless(mutable.is_readonly(), mutable))
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: n.get(u"subdir2"))
|
||||||
|
d.addCallback(lambda subdir2: self.failIf(subdir2.is_readonly()))
|
||||||
|
d.addCallback(lambda ign: n.set_metadata_for(u"subdir2", {"no-write": True}))
|
||||||
|
d.addCallback(lambda ign: n.get(u"subdir2"))
|
||||||
|
d.addCallback(lambda subdir2: self.failUnless(subdir2.is_readonly(), subdir2))
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: n.set_uri(u"mutable_ro", other_file_uri, other_file_uri,
|
||||||
|
metadata={"no-write": True}))
|
||||||
|
d.addCallback(lambda ign: n.get(u"mutable_ro"))
|
||||||
|
d.addCallback(lambda mutable_ro: self.failUnless(mutable_ro.is_readonly(), mutable_ro))
|
||||||
|
|
||||||
|
d.addCallback(lambda ign: n.create_subdirectory(u"subdir_ro", metadata={"no-write": True}))
|
||||||
|
d.addCallback(lambda ign: n.get(u"subdir_ro"))
|
||||||
|
d.addCallback(lambda subdir_ro: self.failUnless(subdir_ro.is_readonly(), subdir_ro))
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
d.addCallback(_then)
|
d.addCallback(_then)
|
||||||
|
Loading…
Reference in New Issue
Block a user