6. `Attaching An Existing File Or Directory (by URI)`_
7. `Unlinking A Child`_
8. `Renaming A Child`_
- 9. `Moving A Child`_
+ 9. `Relinking a Child`_
10. `Other Utilities`_
11. `Debugging and Testing Features`_
same child-cap under the new name, except that it preserves metadata. This
operation cannot move the child to a different directory.
- By default, this operation will replace any existing child of the new name,
- making it behave like the UNIX "``mv -f``" command. Adding a "replace=false"
- argument causes the command to throw an HTTP 409 Conflict error if there is
- already a child with the new name.
+ The default behavior is to overwrite any existing link at the destination
+ (replace=true). To prevent this (and make the operation return an error
+ instead of overwriting), add a "replace=false" argument. With replace=false,
+ this operation will return an HTTP 409 "Conflict" error if the destination
+ is not the same link as the source and there is already a link at the
+ destination, rather than overwriting the existing link. To allow the
+ operation to overwrite a link to a file, but return an HTTP 409 error when
+ trying to overwrite a link to a directory, use "replace=only-files" (this
+ behavior is closer to the traditional UNIX "mv" command). Note that "true",
+ "t", and "1" are all synonyms for "True"; "false", "f", and "0" are synonyms
+ for "False"; and the parameter is case-insensitive.
-Moving A Child
-----------------
-``POST /uri/$DIRCAP/[SUBDIRS../]?t=move&from_name=OLD&to_dir=TARGETNAME[&target_type=name][&to_name=NEWNAME]``
-``POST /uri/$DIRCAP/[SUBDIRS../]?t=move&from_name=OLD&to_dir=TARGETURI&target_type=uri[&to_name=NEWNAME]``
-
- This instructs the node to move a child of the given directory to a
- different directory, both of which must be mutable. If target_type=name
- or is omitted, the to_dir= parameter should contain the name of a
- subdirectory of the child's current parent directory (multiple levels of
- descent are supported). If target_uri=, then to_dir= will be treated as
- a dircap, allowing the child to be moved to an unrelated directory.
-
- The child can also be renamed in the process, by providing a new name in
- the to_name= parameter. If omitted, the child will retain its existing
- name.
-
- By default, this operation will replace any existing child of the new name,
- making it behave like the UNIX "``mv -f``" command. Adding a "replace=false"
- argument causes the command to throw an HTTP 409 Conflict error if there is
- already a child with the new name. For safety, the child is not unlinked
- from the old directory until its has been successfully added to the new
- directory.
+Relinking a Child
+-----------------
+
+``POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_dir=$NEWDIRCAP/[NEWSUBDIRS../]&to_name=NEW``
+ ``[&replace=true|false|only-files]`` (Tahoe >= v1.10)
+
+ This instructs the node to relink a child of the given source directory,
+ into a different directory and/or to a different name. The source and
+ destination directories must be writeable. If {{{to_dir}}} is not present,
+ the child link is renamed within the same directory. If {{{to_name}}} is
+ not present then it defaults to {{{from_name}}}. If the destination link
+ (directory and name) is the same as the source link, the operation has no
+ effect.
+
+ Metadata from the source directory entry is preserved. Multiple levels of
+ descent in the source and destination paths are supported.
+
+ This operation will return an HTTP 404 "Not Found" error if
+ ``$DIRCAP/[SUBDIRS../]``, the child being moved, or the destination
+ directory does not exist. It will return an HTTP 400 "Bad Request" error
+ if any entry in the source or destination paths is not a directory.
+
+ The default behavior is to overwrite any existing link at the destination
+ (replace=true). To prevent this (and make the operation return an error
+ instead of overwriting), add a "replace=false" argument. With replace=false,
+ this operation will return an HTTP 409 "Conflict" error if the destination
+ is not the same link as the source and there is already a link at the
+ destination, rather than overwriting the existing link. To allow the
+ operation to overwrite a link to a file, but return an HTTP 409 error when
+ trying to overwrite a link to a directory, use "replace=only-files" (this
+ behavior is closer to the traditional UNIX "mv" command). Note that "true",
+ "t", and "1" are all synonyms for "True"; "false", "f", and "0" are synonyms
+ for "False"; and the parameter is case-insensitive.
+
+ When relinking into a different directory, for safety, the child link is
+ not removed from the old directory until it has been successfully added to
+ the new directory. This implies that in case of a crash or failure, the
+ link to the child will not be lost, but it could be linked at both the old
+ and new locations.
+
+ The source link should not be the same as any link (directory and child name)
+ in the ``to_dir`` path. This restriction is not enforced, but it may be
+ enforced in a future version. If it were violated then the result would be
+ to create a cycle in the directory structure that is not necessarily reachable
+ from the root of the destination path (``$NEWDIRCAP``), which could result in
+ data loss, as described in ticket `#943`_.
+
+.. _`#943`: https://tahoe-lafs.org/trac/tahoe-lafs/ticket/943
+
Other Utilities
---------------
res.trap(expected_failure)
if substring:
self.failUnlessIn(substring, str(res),
- "'%s' not in '%s' for test '%s'" % \
- (substring, str(res), which))
+ "'%s' not in '%s' (response is '%s') for test '%s'" % \
+ (substring, str(res),
+ getattr(res.value, "response", ""),
+ which))
if response_substring:
self.failUnlessIn(response_substring, res.value.response,
"'%s' not in '%s' for test '%s'" % \
r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
])
self.failUnless(re.search(get_bar, res), res)
- for label in ['unlink', 'rename/move']:
+ for label in ['unlink', 'rename/relink']:
for line in res.split("\n"):
# find the line that contains the relevant button for bar.txt
if ("form action" in line and
d.addCallback(self.failUnlessIsEmptyJSON)
return d
+ def test_POST_rename_file_no_replace_same_link(self):
+ d = self.POST(self.public_url + "/foo", t="rename",
+ replace="false", from_name="bar.txt", to_name="bar.txt")
+ d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_rename_file_replace_only_files(self):
+ d = self.POST(self.public_url + "/foo", t="rename",
+ replace="only-files", from_name="bar.txt",
+ to_name="baz.txt")
+ d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_rename_file_replace_only_files_conflict(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict",
+ "409 Conflict",
+ "There was already a child by that name, and you asked me to not replace it.",
+ self.POST, self.public_url + "/foo", t="relink",
+ replace="only-files", from_name="bar.txt",
+ to_name="empty")
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
def failUnlessIsEmptyJSON(self, res):
data = simplejson.loads(res)
self.failUnlessEqual(data[0], "dirnode", data)
self.failUnlessReallyEqual(len(data[1]["children"]), 0)
- def test_POST_rename_file_slash_fail(self):
+ def test_POST_rename_file_to_slash_fail(self):
d = self.POST(self.public_url + "/foo", t="rename",
from_name="bar.txt", to_name='kirk/spock.txt')
d.addBoth(self.shouldFail, error.Error,
- "test_POST_rename_file_slash_fail",
+ "test_POST_rename_file_to_slash_fail",
"400 Bad Request",
"to_name= may not contain a slash",
)
self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
return d
+ def test_POST_rename_file_from_slash_fail(self):
+ d = self.POST(self.public_url + "/foo", t="rename",
+ from_name="sub/bar.txt", to_name='spock.txt')
+ d.addBoth(self.shouldFail, error.Error,
+ "test_POST_rename_from_file_slash_fail",
+ "400 Bad Request",
+ "from_name= may not contain a slash",
+ )
+ d.addCallback(lambda res:
+ self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
+ return d
+
def test_POST_rename_dir(self):
d = self.POST(self.public_url, t="rename",
from_name="foo", to_name='plunk')
d.addCallback(self.failUnlessIsFooJSON)
return d
- def test_POST_move_file(self):
- d = self.POST(self.public_url + "/foo", t="move",
- from_name="bar.txt", to_dir="sub")
+ def test_POST_relink_file(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_dir=self.public_root.get_uri() + "/foo/sub")
d.addCallback(lambda res:
self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res:
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_new_name(self):
- d = self.POST(self.public_url + "/foo", t="move",
- from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
+ def test_POST_relink_file_new_name(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_name="wibble.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
d.addCallback(lambda res:
self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res:
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_replace(self):
- d = self.POST(self.public_url + "/foo", t="move",
- from_name="bar.txt", to_name="baz.txt", to_dir="sub")
+ def test_POST_relink_file_replace(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
d.addCallback(lambda res:
self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_no_replace(self):
- d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
+ def test_POST_relink_file_no_replace(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_no_replace",
"409 Conflict",
"There was already a child by that name, and you asked me to not replace it",
- self.POST, self.public_url + "/foo", t="move",
+ self.POST, self.public_url + "/foo", t="relink",
replace="false", from_name="bar.txt",
- to_name="baz.txt", to_dir="sub")
+ to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
d.addCallback(self.failUnlessIsSubBazDotTxt)
return d
- def test_POST_move_file_slash_fail(self):
+ def test_POST_relink_file_no_replace_explicitly_same_link(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ replace="false", from_name="bar.txt",
+ to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo")
+ d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_relink_file_replace_only_files(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ replace="only-files", from_name="bar.txt",
+ to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
+ d.addCallback(lambda res:
+ self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_relink_file_replace_only_files_conflict(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_replace_only_files_conflict",
+ "409 Conflict",
+ "There was already a child by that name, and you asked me to not replace it.",
+ self.POST, self.public_url + "/foo", t="relink",
+ replace="only-files", from_name="bar.txt",
+ to_name="sub", to_dir=self.public_root.get_uri() + "/foo")
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_relink_file_to_slash_fail(self):
d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
"400 Bad Request",
"to_name= may not contain a slash",
- self.POST, self.public_url + "/foo", t="move",
+ self.POST, self.public_url + "/foo", t="relink",
from_name="bar.txt",
- to_name="slash/fail.txt", to_dir="sub")
+ to_name="slash/fail.txt", to_dir=self.public_root.get_uri() + "/foo/sub")
d.addCallback(lambda res:
self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res:
"400 Bad Request",
"from_name= may not contain a slash",
self.POST, self.public_url + "/foo",
- t="move",
+ t="relink",
from_name="nope/bar.txt",
- to_name="fail.txt", to_dir="sub"))
+ to_name="fail.txt",
+ to_dir=self.public_root.get_uri() + "/foo/sub"))
return d
- def test_POST_move_file_no_target(self):
- d = self.shouldFail2(error.Error, "POST_move_file_no_target",
- "400 Bad Request",
- "move requires from_name and to_dir",
- self.POST, self.public_url + "/foo", t="move",
- from_name="bar.txt", to_name="baz.txt")
+ def test_POST_relink_file_explicitly_same_link(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo")
+ d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_relink_file_implicitly_same_link(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt")
+ d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
d.addCallback(self.failUnlessIsBarJSON)
+ return d
+
+ def test_POST_relink_file_same_dir(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_name="baz.txt", to_dir=self.public_root.get_uri() + "/foo")
+ d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
+ d.addCallback(lambda res: self.failUnlessNodeHasChild(self._sub_node, u"baz.txt"))
d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
- d.addCallback(self.failUnlessIsBazDotTxt)
+ d.addCallback(self.failUnlessIsBarDotTxt)
+ d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt?t=json"))
+ d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_bad_target_type(self):
- d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
- "400 Bad Request", "invalid target_type parameter",
+ def test_POST_relink_file_bad_replace(self):
+ d = self.shouldFail2(error.Error, "test_POST_relink_file_bad_replace",
+ "400 Bad Request", "invalid replace= argument: 'boogabooga'",
self.POST,
- self.public_url + "/foo", t="move",
- target_type="*D", from_name="bar.txt",
- to_dir="sub")
+ self.public_url + "/foo", t="relink",
+ replace="boogabooga", from_name="bar.txt",
+ to_dir=self.public_root.get_uri() + "/foo/sub")
return d
- def test_POST_move_file_multi_level(self):
+ def test_POST_relink_file_multi_level(self):
d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
- d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
- from_name="bar.txt", to_dir="sub/level2"))
+ d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt", to_dir=self.public_root.get_uri() + "/foo/sub/level2"))
d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_to_uri(self):
- d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
+ def test_POST_relink_file_to_uri(self):
+ d = self.POST(self.public_url + "/foo", t="relink", target_type="uri",
from_name="bar.txt", to_dir=self._sub_uri)
d.addCallback(lambda res:
self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_to_nonexist_dir(self):
- d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
+ def test_POST_relink_file_to_nonexistent_dir(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_to_nonexistent_dir",
"404 Not Found", "No such child: nopechucktesta",
- self.POST, self.public_url + "/foo", t="move",
- from_name="bar.txt", to_dir="nopechucktesta")
+ self.POST, self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_dir=self.public_root.get_uri() + "/nopechucktesta")
return d
- def test_POST_move_file_into_file(self):
- d = self.shouldFail2(error.Error, "POST_move_file_into_file",
+ def test_POST_relink_file_into_file(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_into_file",
"400 Bad Request", "to_dir is not a directory",
- self.POST, self.public_url + "/foo", t="move",
- from_name="bar.txt", to_dir="baz.txt")
+ self.POST, self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_dir=self.public_root.get_uri() + "/foo/baz.txt")
d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
d.addCallback(self.failUnlessIsBazDotTxt)
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_file_to_bad_uri(self):
- d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
+ def test_POST_relink_file_to_bad_uri(self):
+ d = self.shouldFail2(error.Error, "POST_relink_file_to_bad_uri",
"400 Bad Request", "to_dir is not a directory",
- self.POST, self.public_url + "/foo", t="move",
- from_name="bar.txt", target_type="uri",
+ self.POST, self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
d.addCallback(self.failUnlessIsBarDotTxt)
d.addCallback(self.failUnlessIsBarJSON)
return d
- def test_POST_move_dir(self):
- d = self.POST(self.public_url + "/foo", t="move",
- from_name="bar.txt", to_dir="empty")
+ def test_POST_relink_dir(self):
+ d = self.POST(self.public_url + "/foo", t="relink",
+ from_name="bar.txt",
+ to_dir=self.public_root.get_uri() + "/foo/empty")
d.addCallback(lambda res: self.POST(self.public_url + "/foo",
- t="move", from_name="empty", to_dir="sub"))
+ t="relink", from_name="empty",
+ to_dir=self.public_root.get_uri() + "/foo/sub"))
d.addCallback(lambda res:
self.failIfNodeHasChild(self._foo_node, u"empty"))
d.addCallback(lambda res:
self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
"only-files")
- self.shouldFail(AssertionError, "test_parse_replace_arg", "",
- common.parse_replace_arg, "only_fles")
+ self.failUnlessRaises(common.WebError, common.parse_replace_arg, "only_fles")
def test_abbreviate_time(self):
self.failUnlessReallyEqual(common.abbreviate_time(None), "")
from foolscap.api import fireEventually
from allmydata.util import base32, time_format
+from allmydata.util.encodingutil import to_str
from allmydata.uri import from_string_dirnode
from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
IImmutableFileNode, IMutableFileNode, ExistingChildError, \
d = self._POST_unlink(req)
elif t == "rename":
d = self._POST_rename(req)
- elif t == "move":
- d = self._POST_move(req)
+ elif t == "relink":
+ d = self._POST_relink(req)
elif t == "check":
d = self._POST_check(req)
elif t == "start-deep-check":
raise WebError("set-uri requires a name")
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
# We mustn't pass childcap for the readcap argument because we don't
# know whether it is a read cap. Passing a read cap as the writecap
return d
def _POST_rename(self, req):
+ # rename is identical to relink, but to_dir is not allowed
+ # and to_name is required.
+ if get_arg(req, "to_dir") is not None:
+ raise WebError("to_dir= is not valid for rename")
+ if get_arg(req, "to_name") is None:
+ raise WebError("to_name= is required for rename")
+ return self._POST_relink(req)
+
+ def _POST_relink(self, req):
charset = get_arg(req, "_charset", "utf-8")
- from_name = get_arg(req, "from_name")
- if from_name is not None:
- from_name = from_name.strip()
- from_name = from_name.decode(charset)
- assert isinstance(from_name, unicode)
- to_name = get_arg(req, "to_name")
- if to_name is not None:
- to_name = to_name.strip()
- to_name = to_name.decode(charset)
- assert isinstance(to_name, unicode)
- if not from_name or not to_name:
- raise WebError("rename requires from_name and to_name")
- if from_name == to_name:
- return defer.succeed("redundant rename")
-
- # allow from_name to contain slashes, so they can fix names that were
- # accidentally created with them. But disallow them in to_name, to
- # discourage the practice.
- if "/" in to_name:
- raise WebError("to_name= may not contain a slash", http.BAD_REQUEST)
-
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.move_child_to(from_name, self.node, to_name, replace)
- d.addCallback(lambda res: "thing renamed")
- return d
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
- def _POST_move(self, req):
- charset = get_arg(req, "_charset", "utf-8")
from_name = get_arg(req, "from_name")
if from_name is not None:
from_name = from_name.strip()
from_name = from_name.decode(charset)
assert isinstance(from_name, unicode)
+ else:
+ raise WebError("from_name= is required")
+
to_name = get_arg(req, "to_name")
if to_name is not None:
to_name = to_name.strip()
to_name = to_name.decode(charset)
assert isinstance(to_name, unicode)
- if not to_name:
+ else:
to_name = from_name
- to_dir = get_arg(req, "to_dir")
- if to_dir is not None:
- to_dir = to_dir.strip()
- to_dir = to_dir.decode(charset)
- assert isinstance(to_dir, unicode)
- if not from_name or not to_dir:
- raise WebError("move requires from_name and to_dir")
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
# Disallow slashes in both from_name and to_name, that would only
- # cause confusion. t=move is only for moving things from the
- # *current* directory into a second directory named by to_dir=
+ # cause confusion.
if "/" in from_name:
raise WebError("from_name= may not contain a slash",
http.BAD_REQUEST)
raise WebError("to_name= may not contain a slash",
http.BAD_REQUEST)
- target_type = get_arg(req, "target_type", "name")
- if target_type == "name":
- d = self.node.get_child_at_path(to_dir)
- elif target_type == "uri":
- d = defer.succeed(self.client.create_node_from_uri(str(to_dir)))
+ to_dir = get_arg(req, "to_dir")
+ if to_dir is not None and to_dir != self.node.get_write_uri():
+ to_dir = to_dir.strip()
+ to_dir = to_dir.decode(charset)
+ assert isinstance(to_dir, unicode)
+ to_path = to_dir.split(u"/")
+ to_root = self.client.nodemaker.create_from_cap(to_str(to_path[0]))
+ if not IDirectoryNode.providedBy(to_root):
+ raise WebError("to_dir is not a directory", http.BAD_REQUEST)
+ d = to_root.get_child_at_path(to_path[1:])
else:
- raise WebError("invalid target_type parameter", http.BAD_REQUEST)
+ d = defer.succeed(self.node)
- def is_target_node_usable(target_node):
- if not IDirectoryNode.providedBy(target_node):
+ def _got_new_parent(new_parent):
+ if not IDirectoryNode.providedBy(new_parent):
raise WebError("to_dir is not a directory", http.BAD_REQUEST)
- return target_node
- d.addCallback(is_target_node_usable)
- d.addCallback(lambda new_parent:
- self.node.move_child_to(from_name, new_parent,
- to_name, replace))
+
+ return self.node.move_child_to(from_name, new_parent,
+ to_name, replace)
+ d.addCallback(_got_new_parent)
d.addCallback(lambda res: "thing moved")
return d
return d
def _POST_set_children(self, req):
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
req.content.seek(0)
body = req.content.read()
try:
T.input(type='hidden', name='t', value='rename-form'),
T.input(type='hidden', name='name', value=name),
T.input(type='hidden', name='when_done', value="."),
- T.input(type='submit', value='rename/move', name="rename"),
+ T.input(type='submit', value='rename/relink', name="rename"),
]
ctx.fillSlots("unlink", unlink)