X-Git-Url: https://git.rkrishnan.org/?a=blobdiff_plain;f=src%2Fallmydata%2Fscripts%2Fcli.py;h=722c8d983b5b55694841f62eb0904ee7b4ea6f46;hb=0d6fcf445e21f58024ed869f5e672eab2d4a2903;hp=1ac6ad0e1fd60f31ce48bb5291648af239b6751b;hpb=52185053c3ad99c3cd71b26f0d32ea8f3cb24aae;p=tahoe-lafs%2Ftahoe-lafs.git diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 1ac6ad0e..722c8d98 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -1,33 +1,31 @@ -import os.path, re, sys, fnmatch +import os.path, re, fnmatch from twisted.python import usage -from allmydata.scripts.common import BaseOptions, get_aliases +from allmydata.scripts.common import get_aliases, get_default_nodedir, \ + DEFAULT_ALIAS, BaseOptions +from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_local_unicode_path -NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?") +NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?") -class VDriveOptions(BaseOptions, usage.Options): +_default_nodedir = get_default_nodedir() + +class FilesystemOptions(BaseOptions): optParameters = [ - ["node-directory", "d", "~/.tahoe", - "Look here to find out which Tahoe node should be used for all " - "operations. The directory should either contain a full Tahoe node, " - "or a file named node.url which points to some other Tahoe node. " - "It should also contain a file named private/aliases which contains " - "the mapping from alias name to root dirnode URI." - ], ["node-url", "u", None, - "URL of the tahoe node to use, a URL like \"http://127.0.0.1:3456\". " + "Specify the URL of the Tahoe gateway node, such as " + "'http://127.0.0.1:3456'. " "This overrides the URL found in the --node-directory ."], ["dir-cap", None, None, - "Which dirnode URI should be used as the 'tahoe' alias."] + "Specify which dirnode URI should be used as the 'tahoe' alias."] ] def postOptions(self): + self["quiet"] = self.parent["quiet"] + if self.parent['node-directory']: + self['node-directory'] = argv_to_abspath(self.parent['node-directory']) + else: + self['node-directory'] = _default_nodedir + # compute a node-url from the existing options, put in self['node-url'] - if self['node-directory']: - if sys.platform == 'win32' and self['node-directory'] == '~/.tahoe': - from allmydata.windows import registry - self['node-directory'] = registry.get_base_dir_path() - else: - self['node-directory'] = os.path.expanduser(self['node-directory']) if self['node-url']: if (not isinstance(self['node-url'], basestring) or not NODEURL_RE.match(self['node-url'])): @@ -43,126 +41,164 @@ class VDriveOptions(BaseOptions, usage.Options): aliases = get_aliases(self['node-directory']) if self['dir-cap']: - aliases["tahoe"] = self['dir-cap'] + aliases[DEFAULT_ALIAS] = self['dir-cap'] self.aliases = aliases # maps alias name to dircap -class MakeDirectoryOptions(VDriveOptions): +class MakeDirectoryOptions(FilesystemOptions): + optParameters = [ + ("format", None, None, "Create a directory with the given format: SDMF or MDMF (case-insensitive)"), + ] + def parseArgs(self, where=""): - self.where = where - longdesc = """Create a new directory, either unlinked or as a subdirectory.""" + self.where = argv_to_unicode(where) -class AddAliasOptions(VDriveOptions): + if self['format']: + if self['format'].upper() not in ("SDMF", "MDMF"): + raise usage.UsageError("%s is an invalid format" % self['format']) + + synopsis = "[options] [REMOTE_DIR]" + description = """Create a new directory, either unlinked or as a subdirectory.""" + +class AddAliasOptions(FilesystemOptions): def parseArgs(self, alias, cap): - self.alias = alias + self.alias = argv_to_unicode(alias) + if self.alias.endswith(u':'): + self.alias = self.alias[:-1] self.cap = cap - def getSynopsis(self): - return "%s add-alias ALIAS DIRCAP" % (os.path.basename(sys.argv[0]),) - - longdesc = """Add a new alias for an existing directory.""" + synopsis = "[options] ALIAS[:] DIRCAP" + description = """Add a new alias for an existing directory.""" -class CreateAliasOptions(VDriveOptions): +class CreateAliasOptions(FilesystemOptions): def parseArgs(self, alias): - self.alias = alias + self.alias = argv_to_unicode(alias) + if self.alias.endswith(u':'): + self.alias = self.alias[:-1] - def getSynopsis(self): - return "%s create-alias ALIAS" % (os.path.basename(sys.argv[0]),) + synopsis = "[options] ALIAS[:]" + description = """Create a new directory and add an alias for it.""" - longdesc = """Create a new directory and add an alias for it.""" +class ListAliasesOptions(FilesystemOptions): + synopsis = "[options]" + description = """Display a table of all configured aliases.""" -class ListAliasOptions(VDriveOptions): - longdesc = """Display a table of all configured aliases.""" - -class ListOptions(VDriveOptions): +class ListOptions(FilesystemOptions): optFlags = [ - ("long", "l", "Use long format: show file sizes, and timestamps"), - ("uri", "u", "Show file/directory URIs"), - ("readonly-uri", None, "Show readonly file/directory URIs"), - ("classify", "F", "Append '/' to directory names, and '*' to mutable"), - ("json", None, "Show the raw JSON output"), + ("long", "l", "Use long format: show file sizes, and timestamps."), + ("uri", None, "Show file/directory URIs."), + ("readonly-uri", None, "Show read-only file/directory URIs."), + ("classify", "F", "Append '/' to directory names, and '*' to mutable."), + ("json", None, "Show the raw JSON output."), ] def parseArgs(self, where=""): - self.where = where + self.where = argv_to_unicode(where) + + synopsis = "[options] [PATH]" + + description = """ + List the contents of some portion of the grid. - longdesc = """List the contents of some portion of the grid.""" + If PATH is omitted, "tahoe:" is assumed. -class GetOptions(VDriveOptions): + When the -l or --long option is used, each line is shown in the + following format: + + drwx + + where each of the letters on the left may be replaced by '-'. + If 'd' is present, it indicates that the object is a directory. + If the 'd' is replaced by a '?', the object type is unknown. + 'rwx' is a Unix-like permissions mask: if the mask includes 'w', + then the object is writeable through its link in this directory + (note that the link might be replaceable even if the object is + not writeable through the current link). + The 'x' is a legacy of Unix filesystems. In Tahoe it is used + only to indicate that the contents of a directory can be listed. + + Directories have no size, so their size field is shown as '-'. + Otherwise the size of the file, when known, is given in bytes. + The size of mutable files or unknown objects is shown as '?'. + + The date/time shows when this link in the Tahoe filesystem was + last modified. + """ + +class GetOptions(FilesystemOptions): def parseArgs(self, arg1, arg2=None): # tahoe get FOO |less # write to stdout # tahoe get tahoe:FOO |less # same # tahoe get FOO bar # write to local file # tahoe get tahoe:FOO bar # same - self.from_file = arg1 - self.to_file = arg2 - if self.to_file == "-": - self.to_file = None + if arg2 == "-": + arg2 = None + + self.from_file = argv_to_unicode(arg1) + self.to_file = None if arg2 is None else argv_to_abspath(arg2) - def getSynopsis(self): - return "%s get REMOTE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),) + synopsis = "[options] REMOTE_FILE LOCAL_FILE" - longdesc = """ + description = """ Retrieve a file from the grid and write it to the local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file will be written to stdout.""" - def getUsage(self, width=None): - t = VDriveOptions.getUsage(self, width) - t += """ -Examples: - % tahoe get FOO |less # write to stdout - % tahoe get tahoe:FOO |less # same - % tahoe get FOO bar # write to local file - % tahoe get tahoe:FOO bar # same -""" - return t - -class PutOptions(VDriveOptions): + description_unwrapped = """ + Examples: + % tahoe get FOO |less # write to stdout + % tahoe get tahoe:FOO |less # same + % tahoe get FOO bar # write to local file + % tahoe get tahoe:FOO bar # same + """ + +class PutOptions(FilesystemOptions): optFlags = [ - ("mutable", "m", "Create a mutable file instead of an immutable one."), + ("mutable", "m", "Create a mutable file instead of an immutable one (like --format=SDMF)"), + ] + optParameters = [ + ("format", None, None, "Create a file with the given format: SDMF and MDMF for mutable, CHK (default) for immutable. (case-insensitive)"), ] def parseArgs(self, arg1=None, arg2=None): # see Examples below - if arg1 is not None and arg2 is not None: - self.from_file = arg1 - self.to_file = arg2 - elif arg1 is not None and arg2 is None: - self.from_file = arg1 # might be "-" - self.to_file = None - else: - self.from_file = None - self.to_file = None - if self.from_file == "-": - self.from_file = None + if arg1 == "-": + arg1 = None + + self.from_file = None if arg1 is None else argv_to_abspath(arg1) + self.to_file = None if arg2 is None else argv_to_unicode(arg2) - def getSynopsis(self): - return "%s put LOCAL_FILE REMOTE_FILE" % (os.path.basename(sys.argv[0]),) + if self['format']: + if self['format'].upper() not in ("SDMF", "MDMF", "CHK"): + raise usage.UsageError("%s is an invalid format" % self['format']) - longdesc = """ + synopsis = "[options] LOCAL_FILE REMOTE_FILE" + + description = """ Put a file into the grid, copying its contents from the local filesystem. If REMOTE_FILE is missing, upload the file but do not link it into a directory; also print the new filecap to stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin. REMOTE_FILE is assumed to start - with tahoe: unless otherwise specified.""" - - def getUsage(self, width=None): - t = VDriveOptions.getUsage(self, width) - t += """ -Examples: - % cat FILE | tahoe put # create unlinked file from stdin - % cat FILE | tahoe - # same - % tahoe put bar # create unlinked file from local 'bar' - % cat FILE | tahoe put - FOO # create tahoe:FOO from stdin - % tahoe put bar FOO # copy local 'bar' to tahoe:FOO - % tahoe put bar tahoe:FOO # same - % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place -""" - return t - -class CpOptions(VDriveOptions): + with tahoe: unless otherwise specified. + + If the destination file already exists and is mutable, it will be + modified in-place, whether or not --mutable is specified. (--mutable only + affects creation of new files.) + """ + + description_unwrapped = """ + Examples: + % cat FILE | tahoe put # create unlinked file from stdin + % cat FILE | tahoe put - # same + % tahoe put bar # create unlinked file from local 'bar' + % cat FILE | tahoe put - FOO # create tahoe:FOO from stdin + % tahoe put bar FOO # copy local 'bar' to tahoe:FOO + % tahoe put bar tahoe:FOO # same + % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place + """ + +class CpOptions(FilesystemOptions): optFlags = [ ("recursive", "r", "Copy source directory recursively."), ("verbose", "v", "Be noisy about what is happening."), @@ -170,14 +206,16 @@ class CpOptions(VDriveOptions): "When copying to local files, write out filecaps instead of actual " "data (only useful for debugging and tree-comparison purposes)."), ] + def parseArgs(self, *args): if len(args) < 2: raise usage.UsageError("cp requires at least two arguments") - self.sources = args[:-1] - self.destination = args[-1] - def getSynopsis(self): - return "Usage: tahoe [options] cp FROM.. TO" - longdesc = """ + self.sources = map(argv_to_unicode, args[:-1]) + self.destination = argv_to_unicode(args[-1]) + + synopsis = "[options] FROM.. TO" + + description = """ Use 'tahoe cp' to copy files between a local filesystem and a Tahoe grid. Any FROM/TO arguments that begin with an alias indicate Tahoe-side files or non-file arguments. Directories will be copied recursively. @@ -185,38 +223,47 @@ class CpOptions(VDriveOptions): you have previously set up an alias 'home' with 'tahoe create-alias home', here are some examples: - tahoe cp ~/foo.txt home: # creates tahoe-side home:foo.txt + tahoe cp ~/foo.txt home: # creates tahoe-side home:foo.txt - tahoe cp ~/foo.txt /tmp/bar.txt home: # copies two files to home: + tahoe cp ~/foo.txt /tmp/bar.txt home: # copies two files to home: - tahoe cp ~/Pictures home:stuff/my-pictures # copies directory recursively + tahoe cp ~/Pictures home:stuff/my-pictures # copies directory recursively You can also use a dircap as either FROM or TO target: - tahoe cp URI:DIR2-RO:j74uhg25nwdpjpacl6rkat2yhm:kav7ijeft5h7r7rxdp5bgtlt3viv32yabqajkrdykozia5544jqa/wiki.html ./ # copy Zooko's wiki page to a local file - - This command still has some limitations: symlinks, special files (device - nodes, named pipes), and non-ASCII filenames are not handled very well. - Arguments should probably not have trailing slashes. 'tahoe cp' does not - behave as much like /bin/cp as you would wish, especially with respect to - trailing slashes. + tahoe cp URI:DIR2-RO:ixqhc4kdbjxc7o65xjnveoewym:5x6lwoxghrd5rxhwunzavft2qygfkt27oj3fbxlq4c6p45z5uneq/blog.html ./ # copy Zooko's wiki page to a local file + + This command still has some limitations: symlinks and special files + (device nodes, named pipes) are not handled very well. Arguments should + not have trailing slashes (they are ignored for directory arguments, but + trigger errors for file arguments). When copying directories, it can be + unclear whether you mean to copy the contents of a source directory, or + the source directory itself (i.e. whether the output goes under the + target directory, or one directory lower). Tahoe's rule is that source + directories with names are referring to the directory as a whole, and + source directories without names (e.g. a raw dircap) are referring to the + contents. """ -class RmOptions(VDriveOptions): +class UnlinkOptions(FilesystemOptions): def parseArgs(self, where): - self.where = where + self.where = argv_to_unicode(where) - def getSynopsis(self): - return "%s rm REMOTE_FILE" % (os.path.basename(sys.argv[0]),) + synopsis = "[options] REMOTE_FILE" + description = "Remove a named file from its parent directory." -class MvOptions(VDriveOptions): +class RmOptions(UnlinkOptions): + synopsis = "[options] REMOTE_FILE" + description = "Remove a named file from its parent directory." + +class MvOptions(FilesystemOptions): def parseArgs(self, frompath, topath): - self.from_file = frompath - self.to_file = topath + self.from_file = argv_to_unicode(frompath) + self.to_file = argv_to_unicode(topath) + + synopsis = "[options] FROM TO" - def getSynopsis(self): - return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),) - longdesc = """ + description = """ Use 'tahoe mv' to move files that are already on the grid elsewhere on the grid, e.g., 'tahoe mv alias:some_file alias:new_file'. @@ -228,18 +275,39 @@ class MvOptions(VDriveOptions): the grid -- use 'tahoe cp' for that. """ -class LnOptions(VDriveOptions): +class LnOptions(FilesystemOptions): def parseArgs(self, frompath, topath): - self.from_file = frompath - self.to_file = topath - - def getSynopsis(self): - return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),) + self.from_file = argv_to_unicode(frompath) + self.to_file = argv_to_unicode(topath) + + synopsis = "[options] FROM_LINK TO_LINK" + + description = """ + Use 'tahoe ln' to duplicate a link (directory entry) already on the grid + to elsewhere on the grid. For example 'tahoe ln alias:some_file + alias:new_file'. causes 'alias:new_file' to point to the same object that + 'alias:some_file' points to. + + (The argument order is the same as Unix ln. To remember the order, you + can think of this command as copying a link, rather than copying a file + as 'tahoe cp' does. Then the argument order is consistent with that of + 'tahoe cp'.) + + When linking a remote file into a remote directory, you'll need to append + a '/' to the name of the remote directory, e.g. 'tahoe ln tahoe:file1 + tahoe:dir/' (which is shorthand for 'tahoe ln tahoe:file1 + tahoe:dir/file1'). If you forget the '/', e.g. 'tahoe ln tahoe:file1 + tahoe:dir', the 'ln' command will refuse to overwrite the 'tahoe:dir' + directory, and will exit with an error. + + Note that it is not possible to use this command to create links between + local and remote files. + """ class BackupConfigurationError(Exception): pass -class BackupOptions(VDriveOptions): +class BackupOptions(FilesystemOptions): optFlags = [ ("verbose", "v", "Be noisy about what is happening."), ("ignore-timestamps", None, "Do not use backupdb timestamps to decide whether a local file is unchanged."), @@ -255,27 +323,27 @@ class BackupOptions(VDriveOptions): self['exclude'] = set() def parseArgs(self, localdir, topath): - self.from_dir = localdir - self.to_dir = topath + self.from_dir = argv_to_abspath(localdir) + self.to_dir = argv_to_unicode(topath) - def getSynopsis(Self): - return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0]) + synopsis = "[options] FROM ALIAS:TO" def opt_exclude(self, pattern): """Ignore files matching a glob pattern. You may give multiple '--exclude' options.""" - g = pattern.strip() + g = argv_to_unicode(pattern).strip() if g: exclude = self['exclude'] exclude.add(g) def opt_exclude_from(self, filepath): """Ignore file matching glob patterns listed in file, one per - line.""" + line. The file is assumed to be in the argv encoding.""" + abs_filepath = argv_to_abspath(filepath) try: - exclude_file = file(filepath) + exclude_file = file(abs_filepath) except: - raise BackupConfigurationError('Error opening exclude file %r.' % filepath) + raise BackupConfigurationError('Error opening exclude file %s.' % quote_local_unicode_path(abs_filepath)) try: for line in exclude_file: self.opt_exclude(line) @@ -300,7 +368,7 @@ class BackupOptions(VDriveOptions): else: yield filename - longdesc = """ + description = """ Add a versioned backup of the local FROM directory to a timestamped subdirectory of the TO/Archives directory on the grid, sharing as many files and directories as possible with earlier backups. Create TO/Latest @@ -308,100 +376,99 @@ class BackupOptions(VDriveOptions): --link-dest=TO/Archives/(previous) FROM TO/Archives/(new); ln -sf TO/Archives/(new) TO/Latest'.""" -class WebopenOptions(VDriveOptions): +class WebopenOptions(FilesystemOptions): + optFlags = [ + ("info", "i", "Open the t=info page for the file"), + ] def parseArgs(self, where=''): - self.where = where + self.where = argv_to_unicode(where) - def getSynopsis(self): - return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) + synopsis = "[options] [ALIAS:PATH]" - longdesc = """Open a web browser to the contents of some file or - directory on the grid.""" + description = """ + Open a web browser to the contents of some file or + directory on the grid. When run without arguments, open the Welcome + page.""" -class ManifestOptions(VDriveOptions): +class ManifestOptions(FilesystemOptions): optFlags = [ - ("storage-index", "s", "Only print storage index strings, not pathname+cap"), - ("verify-cap", None, "Only print verifycap, not pathname+cap"), - ("repair-cap", None, "Only print repaircap, not pathname+cap"), - ("raw", "r", "Display raw JSON data instead of parsed"), + ("storage-index", "s", "Only print storage index strings, not pathname+cap."), + ("verify-cap", None, "Only print verifycap, not pathname+cap."), + ("repair-cap", None, "Only print repaircap, not pathname+cap."), + ("raw", "r", "Display raw JSON data instead of parsed."), ] def parseArgs(self, where=''): - self.where = where + self.where = argv_to_unicode(where) - def getSynopsis(self): - return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) + synopsis = "[options] [ALIAS:PATH]" + description = """ + Print a list of all files and directories reachable from the given + starting point.""" - longdesc = """Print a list of all files and directories reachable from - the given starting point.""" - -class StatsOptions(VDriveOptions): +class StatsOptions(FilesystemOptions): optFlags = [ ("raw", "r", "Display raw JSON data instead of parsed"), ] def parseArgs(self, where=''): - self.where = where - - def getSynopsis(self): - return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) + self.where = argv_to_unicode(where) - longdesc = """Print statistics about of all files and directories - reachable from the given starting point.""" + synopsis = "[options] [ALIAS:PATH]" + description = """ + Print statistics about of all files and directories reachable from the + given starting point.""" -class CheckOptions(VDriveOptions): +class CheckOptions(FilesystemOptions): optFlags = [ - ("raw", None, "Display raw JSON data instead of parsed"), - ("verify", None, "Verify all hashes, instead of merely querying share presence"), - ("repair", None, "Automatically repair any problems found"), - ("add-lease", None, "Add/renew lease on all shares"), + ("raw", None, "Display raw JSON data instead of parsed."), + ("verify", None, "Verify all hashes, instead of merely querying share presence."), + ("repair", None, "Automatically repair any problems found."), + ("add-lease", None, "Add/renew lease on all shares."), ] - def parseArgs(self, where=''): - self.where = where + def parseArgs(self, *locations): + self.locations = map(argv_to_unicode, locations) - def getSynopsis(self): - return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) - - longdesc = """ + synopsis = "[options] [ALIAS:PATH]" + description = """ Check a single file or directory: count how many shares are available and verify their hashes. Optionally repair the file if any problems were found.""" -class DeepCheckOptions(VDriveOptions): +class DeepCheckOptions(FilesystemOptions): optFlags = [ - ("raw", None, "Display raw JSON data instead of parsed"), - ("verify", None, "Verify all hashes, instead of merely querying share presence"), - ("repair", None, "Automatically repair any problems found"), - ("add-lease", None, "Add/renew lease on all shares"), + ("raw", None, "Display raw JSON data instead of parsed."), + ("verify", None, "Verify all hashes, instead of merely querying share presence."), + ("repair", None, "Automatically repair any problems found."), + ("add-lease", None, "Add/renew lease on all shares."), ("verbose", "v", "Be noisy about what is happening."), ] - def parseArgs(self, where=''): - self.where = where - - def getSynopsis(self): - return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),) + def parseArgs(self, *locations): + self.locations = map(argv_to_unicode, locations) - longdesc = """ + synopsis = "[options] [ALIAS:PATH]" + description = """ Check all files and directories reachable from the given starting point (which must be a directory), like 'tahoe check' but for multiple files. Optionally repair any problems found.""" subCommands = [ - ["mkdir", None, MakeDirectoryOptions, "Create a new directory"], - ["add-alias", None, AddAliasOptions, "Add a new alias cap"], - ["create-alias", None, CreateAliasOptions, "Create a new alias cap"], - ["list-aliases", None, ListAliasOptions, "List all alias caps"], - ["ls", None, ListOptions, "List a directory"], + ["mkdir", None, MakeDirectoryOptions, "Create a new directory."], + ["add-alias", None, AddAliasOptions, "Add a new alias cap."], + ["create-alias", None, CreateAliasOptions, "Create a new alias cap."], + ["list-aliases", None, ListAliasesOptions, "List all alias caps."], + ["ls", None, ListOptions, "List a directory."], ["get", None, GetOptions, "Retrieve a file from the grid."], ["put", None, PutOptions, "Upload a file into the grid."], - ["cp", None, CpOptions, "Copy one or more files."], - ["rm", None, RmOptions, "Unlink a file or directory on the grid."], + ["cp", None, CpOptions, "Copy one or more files or directories."], + ["unlink", None, UnlinkOptions, "Unlink a file or directory on the grid."], + ["rm", None, RmOptions, "Unlink a file or directory on the grid (same as unlink)."], ["mv", None, MvOptions, "Move a file within the grid."], - ["ln", None, LnOptions, "Make an additional link to an existing file."], + ["ln", None, LnOptions, "Make an additional link to an existing file or directory."], ["backup", None, BackupOptions, "Make target dir look like local dir."], ["webopen", None, WebopenOptions, "Open a web browser to a grid file or directory."], - ["manifest", None, ManifestOptions, "List all files/directories in a subtree"], - ["stats", None, StatsOptions, "Print statistics about all files/directories in a subtree"], - ["check", None, CheckOptions, "Check a single file or directory"], - ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"], + ["manifest", None, ManifestOptions, "List all files/directories in a subtree."], + ["stats", None, StatsOptions, "Print statistics about all files/directories in a subtree."], + ["check", None, CheckOptions, "Check a single file or directory."], + ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point."], ] def mkdir(options): @@ -453,11 +520,14 @@ def cp(options): rc = tahoe_cp.copy(options) return rc -def rm(options): - from allmydata.scripts import tahoe_rm - rc = tahoe_rm.rm(options) +def unlink(options, command="unlink"): + from allmydata.scripts import tahoe_unlink + rc = tahoe_unlink.unlink(options, command=command) return rc +def rm(options): + return unlink(options, command="rm") + def mv(options): from allmydata.scripts import tahoe_mv rc = tahoe_mv.mv(options, mode="move") @@ -507,6 +577,7 @@ dispatch = { "get": get, "put": put, "cp": cp, + "unlink": unlink, "rm": rm, "mv": mv, "ln": ln,