1 | """ |
---|
2 | Ported to Python 3. |
---|
3 | """ |
---|
4 | |
---|
5 | import os.path, re, fnmatch |
---|
6 | |
---|
7 | from allmydata.scripts.types_ import SubCommands, Parameters |
---|
8 | |
---|
9 | from twisted.python import usage |
---|
10 | from allmydata.scripts.common import get_aliases, get_default_nodedir, \ |
---|
11 | DEFAULT_ALIAS, BaseOptions |
---|
12 | from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_local_unicode_path |
---|
13 | from .tahoe_status import TahoeStatusCommand |
---|
14 | |
---|
15 | NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?") |
---|
16 | |
---|
17 | _default_nodedir = get_default_nodedir() |
---|
18 | |
---|
19 | class FileStoreOptions(BaseOptions): |
---|
20 | optParameters : Parameters = [ |
---|
21 | ["node-url", "u", None, |
---|
22 | "Specify the URL of the Tahoe gateway node, such as " |
---|
23 | "'http://127.0.0.1:3456'. " |
---|
24 | "This overrides the URL found in the --node-directory ."], |
---|
25 | ["dir-cap", None, None, |
---|
26 | "Specify which dirnode URI should be used as the 'tahoe' alias."] |
---|
27 | ] |
---|
28 | |
---|
29 | def postOptions(self): |
---|
30 | self["quiet"] = self.parent["quiet"] |
---|
31 | if self.parent['node-directory']: |
---|
32 | self['node-directory'] = argv_to_abspath(self.parent['node-directory']) |
---|
33 | else: |
---|
34 | self['node-directory'] = _default_nodedir |
---|
35 | |
---|
36 | # compute a node-url from the existing options, put in self['node-url'] |
---|
37 | if self['node-url']: |
---|
38 | if (not isinstance(self['node-url'], (bytes, str)) |
---|
39 | or not NODEURL_RE.match(self['node-url'])): |
---|
40 | msg = ("--node-url is required to be a string and look like " |
---|
41 | "\"http://HOSTNAMEORADDR:PORT\", not: %r" % |
---|
42 | (self['node-url'],)) |
---|
43 | raise usage.UsageError(msg) |
---|
44 | else: |
---|
45 | node_url_file = os.path.join(self['node-directory'], "node.url") |
---|
46 | with open(node_url_file, "r") as f: |
---|
47 | self['node-url'] = f.read().strip() |
---|
48 | if self['node-url'][-1] != "/": |
---|
49 | self['node-url'] += "/" |
---|
50 | |
---|
51 | aliases = get_aliases(self['node-directory']) |
---|
52 | if self['dir-cap']: |
---|
53 | aliases[DEFAULT_ALIAS] = self['dir-cap'] |
---|
54 | self.aliases = aliases # maps alias name to dircap |
---|
55 | |
---|
56 | |
---|
57 | class MakeDirectoryOptions(FileStoreOptions): |
---|
58 | optParameters = [ |
---|
59 | ("format", None, None, "Create a directory with the given format: SDMF or MDMF (case-insensitive)"), |
---|
60 | ] |
---|
61 | |
---|
62 | def parseArgs(self, where=""): |
---|
63 | self.where = argv_to_unicode(where) |
---|
64 | |
---|
65 | if self['format']: |
---|
66 | if self['format'].upper() not in ("SDMF", "MDMF"): |
---|
67 | raise usage.UsageError("%s is an invalid format" % self['format']) |
---|
68 | |
---|
69 | synopsis = "[options] [REMOTE_DIR]" |
---|
70 | description = """Create a new directory, either unlinked or as a subdirectory.""" |
---|
71 | |
---|
72 | class AddAliasOptions(FileStoreOptions): |
---|
73 | def parseArgs(self, alias, cap): |
---|
74 | self.alias = argv_to_unicode(alias) |
---|
75 | if self.alias.endswith(u':'): |
---|
76 | self.alias = self.alias[:-1] |
---|
77 | self.cap = cap |
---|
78 | |
---|
79 | synopsis = "[options] ALIAS[:] DIRCAP" |
---|
80 | description = """Add a new alias for an existing directory.""" |
---|
81 | |
---|
82 | class CreateAliasOptions(FileStoreOptions): |
---|
83 | def parseArgs(self, alias): |
---|
84 | self.alias = argv_to_unicode(alias) |
---|
85 | if self.alias.endswith(u':'): |
---|
86 | self.alias = self.alias[:-1] |
---|
87 | |
---|
88 | synopsis = "[options] ALIAS[:]" |
---|
89 | description = """Create a new directory and add an alias for it.""" |
---|
90 | |
---|
91 | class ListAliasesOptions(FileStoreOptions): |
---|
92 | synopsis = "[options]" |
---|
93 | description = """Display a table of all configured aliases.""" |
---|
94 | optFlags = [ |
---|
95 | ("readonly-uri", None, "Show read-only dircaps instead of readwrite"), |
---|
96 | ("json", None, "Show JSON output"), |
---|
97 | ] |
---|
98 | |
---|
99 | class ListOptions(FileStoreOptions): |
---|
100 | optFlags = [ |
---|
101 | ("long", "l", "Use long format: show file sizes, and timestamps."), |
---|
102 | ("uri", None, "Show file/directory URIs."), |
---|
103 | ("readonly-uri", None, "Show read-only file/directory URIs."), |
---|
104 | ("classify", "F", "Append '/' to directory names, and '*' to mutable."), |
---|
105 | ("json", None, "Show the raw JSON output."), |
---|
106 | ] |
---|
107 | def parseArgs(self, where=""): |
---|
108 | self.where = argv_to_unicode(where) |
---|
109 | |
---|
110 | synopsis = "[options] [PATH]" |
---|
111 | |
---|
112 | description = """ |
---|
113 | List the contents of some portion of the grid. |
---|
114 | |
---|
115 | If PATH is omitted, "tahoe:" is assumed. |
---|
116 | |
---|
117 | When the -l or --long option is used, each line is shown in the |
---|
118 | following format: |
---|
119 | |
---|
120 | drwx <size> <date/time> <name in this directory> |
---|
121 | |
---|
122 | where each of the letters on the left may be replaced by '-'. |
---|
123 | If 'd' is present, it indicates that the object is a directory. |
---|
124 | If the 'd' is replaced by a '?', the object type is unknown. |
---|
125 | 'rwx' is a Unix-like permissions mask: if the mask includes 'w', |
---|
126 | then the object is writeable through its link in this directory |
---|
127 | (note that the link might be replaceable even if the object is |
---|
128 | not writeable through the current link). |
---|
129 | The 'x' is a legacy of Unix filesystems. In Tahoe it is used |
---|
130 | only to indicate that the contents of a directory can be listed. |
---|
131 | |
---|
132 | Directories have no size, so their size field is shown as '-'. |
---|
133 | Otherwise the size of the file, when known, is given in bytes. |
---|
134 | The size of mutable files or unknown objects is shown as '?'. |
---|
135 | |
---|
136 | The date/time shows when this link in the Tahoe grid was last |
---|
137 | modified. |
---|
138 | """ |
---|
139 | |
---|
140 | class GetOptions(FileStoreOptions): |
---|
141 | def parseArgs(self, arg1, arg2=None): |
---|
142 | # tahoe get FOO |less # write to stdout |
---|
143 | # tahoe get tahoe:FOO |less # same |
---|
144 | # tahoe get FOO bar # write to local file |
---|
145 | # tahoe get tahoe:FOO bar # same |
---|
146 | |
---|
147 | if arg2 == "-": |
---|
148 | arg2 = None |
---|
149 | |
---|
150 | self.from_file = argv_to_unicode(arg1) |
---|
151 | self.to_file = None if arg2 is None else argv_to_abspath(arg2) |
---|
152 | |
---|
153 | synopsis = "[options] REMOTE_FILE LOCAL_FILE" |
---|
154 | |
---|
155 | description = """ |
---|
156 | Retrieve a file from the grid and write it to the local filesystem. If |
---|
157 | LOCAL_FILE is omitted or '-', the contents of the file will be written to |
---|
158 | stdout.""" |
---|
159 | |
---|
160 | description_unwrapped = """ |
---|
161 | Examples: |
---|
162 | % tahoe get FOO |less # write to stdout |
---|
163 | % tahoe get tahoe:FOO |less # same |
---|
164 | % tahoe get FOO bar # write to local file |
---|
165 | % tahoe get tahoe:FOO bar # same |
---|
166 | """ |
---|
167 | |
---|
168 | class PutOptions(FileStoreOptions): |
---|
169 | optFlags = [ |
---|
170 | ("mutable", "m", "Create a mutable file instead of an immutable one (like --format=SDMF)"), |
---|
171 | ] |
---|
172 | |
---|
173 | optParameters = [ |
---|
174 | ("format", None, None, "Create a file with the given format: SDMF and MDMF for mutable, CHK (default) for immutable. (case-insensitive)"), |
---|
175 | |
---|
176 | ("private-key-path", None, None, |
---|
177 | "***Warning*** " |
---|
178 | "It is possible to use this option to spoil the normal security properties of mutable objects. " |
---|
179 | "It is also possible to corrupt or destroy data with this option. " |
---|
180 | "Most users will not need this option and can ignore it. " |
---|
181 | "For mutables only, " |
---|
182 | "this gives a file containing a PEM-encoded 2048 bit RSA private key to use as the signature key for the mutable. " |
---|
183 | "The private key must be handled at least as strictly as the resulting capability string. " |
---|
184 | "A single private key must not be used for more than one mutable." |
---|
185 | ), |
---|
186 | ] |
---|
187 | |
---|
188 | def parseArgs(self, arg1=None, arg2=None): |
---|
189 | # see Examples below |
---|
190 | |
---|
191 | if arg1 == "-": |
---|
192 | arg1 = None |
---|
193 | |
---|
194 | self.from_file = None if arg1 is None else argv_to_abspath(arg1) |
---|
195 | self.to_file = None if arg2 is None else argv_to_unicode(arg2) |
---|
196 | |
---|
197 | if self['format']: |
---|
198 | if self['format'].upper() not in ("SDMF", "MDMF", "CHK"): |
---|
199 | raise usage.UsageError("%s is an invalid format" % self['format']) |
---|
200 | |
---|
201 | synopsis = "[options] LOCAL_FILE REMOTE_FILE" |
---|
202 | |
---|
203 | description = """ |
---|
204 | Put a file into the grid, copying its contents from the local filesystem. |
---|
205 | If REMOTE_FILE is missing, upload the file but do not link it into a |
---|
206 | directory; also print the new filecap to stdout. If LOCAL_FILE is missing |
---|
207 | or '-', data will be copied from stdin. REMOTE_FILE is assumed to start |
---|
208 | with tahoe: unless otherwise specified. |
---|
209 | |
---|
210 | If the destination file already exists and is mutable, it will be |
---|
211 | modified in-place, whether or not --mutable is specified. (--mutable only |
---|
212 | affects creation of new files.) |
---|
213 | """ |
---|
214 | |
---|
215 | description_unwrapped = """ |
---|
216 | Examples: |
---|
217 | % cat FILE | tahoe put # create unlinked file from stdin |
---|
218 | % cat FILE | tahoe put - # same |
---|
219 | % tahoe put bar # create unlinked file from local 'bar' |
---|
220 | % cat FILE | tahoe put - FOO # create tahoe:FOO from stdin |
---|
221 | % tahoe put bar FOO # copy local 'bar' to tahoe:FOO |
---|
222 | % tahoe put bar tahoe:FOO # same |
---|
223 | % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place |
---|
224 | """ |
---|
225 | |
---|
226 | class CpOptions(FileStoreOptions): |
---|
227 | optFlags = [ |
---|
228 | ("recursive", "r", "Copy source directory recursively."), |
---|
229 | ("verbose", "v", "Be noisy about what is happening."), |
---|
230 | ("caps-only", None, |
---|
231 | "When copying to local files, write out filecaps instead of actual " |
---|
232 | "data (only useful for debugging and tree-comparison purposes)."), |
---|
233 | ] |
---|
234 | |
---|
235 | def parseArgs(self, *args): |
---|
236 | if len(args) < 2: |
---|
237 | raise usage.UsageError("cp requires at least two arguments") |
---|
238 | self.sources = [argv_to_unicode(arg) for arg in args[:-1]] |
---|
239 | self.destination = argv_to_unicode(args[-1]) |
---|
240 | |
---|
241 | synopsis = "[options] FROM.. TO" |
---|
242 | |
---|
243 | description = """ |
---|
244 | Use 'tahoe cp' to copy files between a local filesystem and a Tahoe grid. |
---|
245 | Any FROM/TO arguments that begin with an alias indicate Tahoe-side |
---|
246 | files or non-file arguments. Directories will be copied recursively. |
---|
247 | New Tahoe-side directories will be created when necessary. Assuming that |
---|
248 | you have previously set up an alias 'home' with 'tahoe create-alias home', |
---|
249 | here are some examples: |
---|
250 | |
---|
251 | tahoe cp ~/foo.txt home: # creates tahoe-side home:foo.txt |
---|
252 | |
---|
253 | tahoe cp ~/foo.txt /tmp/bar.txt home: # copies two files to home: |
---|
254 | |
---|
255 | tahoe cp ~/Pictures home:stuff/my-pictures # copies directory recursively |
---|
256 | |
---|
257 | You can also use a dircap as either FROM or TO target: |
---|
258 | |
---|
259 | tahoe cp URI:DIR2-RO:ixqhc4kdbjxc7o65xjnveoewym:5x6lwoxghrd5rxhwunzavft2qygfkt27oj3fbxlq4c6p45z5uneq/blog.html ./ # copy Zooko's wiki page to a local file |
---|
260 | |
---|
261 | This command still has some limitations: symlinks and special files |
---|
262 | (device nodes, named pipes) are not handled very well. Arguments should |
---|
263 | not have trailing slashes (they are ignored for directory arguments, but |
---|
264 | trigger errors for file arguments). When copying directories, it can be |
---|
265 | unclear whether you mean to copy the contents of a source directory, or |
---|
266 | the source directory itself (i.e. whether the output goes under the |
---|
267 | target directory, or one directory lower). Tahoe's rule is that source |
---|
268 | directories with names are referring to the directory as a whole, and |
---|
269 | source directories without names (e.g. a raw dircap) are referring to the |
---|
270 | contents. |
---|
271 | """ |
---|
272 | |
---|
273 | class UnlinkOptions(FileStoreOptions): |
---|
274 | def parseArgs(self, where): |
---|
275 | self.where = argv_to_unicode(where) |
---|
276 | |
---|
277 | synopsis = "[options] REMOTE_FILE" |
---|
278 | description = "Remove a named file from its parent directory." |
---|
279 | |
---|
280 | class MvOptions(FileStoreOptions): |
---|
281 | def parseArgs(self, frompath, topath): |
---|
282 | self.from_file = argv_to_unicode(frompath) |
---|
283 | self.to_file = argv_to_unicode(topath) |
---|
284 | |
---|
285 | synopsis = "[options] FROM TO" |
---|
286 | |
---|
287 | description = """ |
---|
288 | Use 'tahoe mv' to move files that are already on the grid elsewhere on |
---|
289 | the grid, e.g., 'tahoe mv alias:some_file alias:new_file'. |
---|
290 | |
---|
291 | If moving a remote file into a remote directory, you'll need to append a |
---|
292 | '/' to the name of the remote directory, e.g., 'tahoe mv tahoe:file1 |
---|
293 | tahoe:dir/', not 'tahoe mv tahoe:file1 tahoe:dir'. |
---|
294 | |
---|
295 | Note that it is not possible to use this command to move local files to |
---|
296 | the grid -- use 'tahoe cp' for that. |
---|
297 | """ |
---|
298 | |
---|
299 | class LnOptions(FileStoreOptions): |
---|
300 | def parseArgs(self, frompath, topath): |
---|
301 | self.from_file = argv_to_unicode(frompath) |
---|
302 | self.to_file = argv_to_unicode(topath) |
---|
303 | |
---|
304 | synopsis = "[options] FROM_LINK TO_LINK" |
---|
305 | |
---|
306 | description = """ |
---|
307 | Use 'tahoe ln' to duplicate a link (directory entry) already on the grid |
---|
308 | to elsewhere on the grid. For example 'tahoe ln alias:some_file |
---|
309 | alias:new_file'. causes 'alias:new_file' to point to the same object that |
---|
310 | 'alias:some_file' points to. |
---|
311 | |
---|
312 | (The argument order is the same as Unix ln. To remember the order, you |
---|
313 | can think of this command as copying a link, rather than copying a file |
---|
314 | as 'tahoe cp' does. Then the argument order is consistent with that of |
---|
315 | 'tahoe cp'.) |
---|
316 | |
---|
317 | When linking a remote file into a remote directory, you'll need to append |
---|
318 | a '/' to the name of the remote directory, e.g. 'tahoe ln tahoe:file1 |
---|
319 | tahoe:dir/' (which is shorthand for 'tahoe ln tahoe:file1 |
---|
320 | tahoe:dir/file1'). If you forget the '/', e.g. 'tahoe ln tahoe:file1 |
---|
321 | tahoe:dir', the 'ln' command will refuse to overwrite the 'tahoe:dir' |
---|
322 | directory, and will exit with an error. |
---|
323 | |
---|
324 | Note that it is not possible to use this command to create links between |
---|
325 | local and remote files. |
---|
326 | """ |
---|
327 | |
---|
328 | class BackupConfigurationError(Exception): |
---|
329 | pass |
---|
330 | |
---|
331 | class BackupOptions(FileStoreOptions): |
---|
332 | optFlags = [ |
---|
333 | ("verbose", "v", "Be noisy about what is happening."), |
---|
334 | ("ignore-timestamps", None, "Do not use backupdb timestamps to decide whether a local file is unchanged."), |
---|
335 | ] |
---|
336 | |
---|
337 | vcs_patterns = ('CVS', 'RCS', 'SCCS', '.git', '.gitignore', '.cvsignore', |
---|
338 | '.svn', '.arch-ids','{arch}', '=RELEASE-ID', |
---|
339 | '=meta-update', '=update', '.bzr', '.bzrignore', |
---|
340 | '.bzrtags', '.hg', '.hgignore', '_darcs') |
---|
341 | |
---|
342 | def __init__(self): |
---|
343 | super(BackupOptions, self).__init__() |
---|
344 | self['exclude'] = set() |
---|
345 | |
---|
346 | def parseArgs(self, localdir, topath): |
---|
347 | self.from_dir = argv_to_abspath(localdir) |
---|
348 | self.to_dir = argv_to_unicode(topath) |
---|
349 | |
---|
350 | synopsis = "[options] FROM ALIAS:TO" |
---|
351 | |
---|
352 | def opt_exclude(self, pattern): |
---|
353 | """Ignore files matching a glob pattern. You may give multiple |
---|
354 | '--exclude' options.""" |
---|
355 | g = argv_to_unicode(pattern).strip() |
---|
356 | if g: |
---|
357 | exclude = self['exclude'] |
---|
358 | exclude.add(g) |
---|
359 | |
---|
360 | def opt_exclude_from_utf_8(self, filepath): |
---|
361 | """Ignore file matching glob patterns listed in file, one per |
---|
362 | line. The file is assumed to be in the argv encoding.""" |
---|
363 | abs_filepath = argv_to_abspath(filepath) |
---|
364 | try: |
---|
365 | exclude_file = open(abs_filepath, "r", encoding="utf-8") |
---|
366 | except Exception as e: |
---|
367 | raise BackupConfigurationError('Error opening exclude file %s. (Error: %s)' % ( |
---|
368 | quote_local_unicode_path(abs_filepath), e)) |
---|
369 | try: |
---|
370 | for line in exclude_file: |
---|
371 | self.opt_exclude(line) |
---|
372 | finally: |
---|
373 | exclude_file.close() |
---|
374 | |
---|
375 | def opt_exclude_vcs(self): |
---|
376 | """Exclude files and directories used by following version control |
---|
377 | systems: CVS, RCS, SCCS, Git, SVN, Arch, Bazaar(bzr), Mercurial, |
---|
378 | Darcs.""" |
---|
379 | for pattern in self.vcs_patterns: |
---|
380 | self.opt_exclude(pattern) |
---|
381 | |
---|
382 | def filter_listdir(self, listdir): |
---|
383 | """Yields non-excluded childpaths in path.""" |
---|
384 | exclude = self['exclude'] |
---|
385 | exclude_regexps = [re.compile(fnmatch.translate(pat)) for pat in exclude] |
---|
386 | for filename in listdir: |
---|
387 | for regexp in exclude_regexps: |
---|
388 | if regexp.match(filename): |
---|
389 | break |
---|
390 | else: |
---|
391 | yield filename |
---|
392 | |
---|
393 | description = """ |
---|
394 | Add a versioned backup of the local FROM directory to a timestamped |
---|
395 | subdirectory of the TO/Archives directory on the grid, sharing as many |
---|
396 | files and directories as possible with earlier backups. Create TO/Latest |
---|
397 | as a reference to the latest backup. Behaves somewhat like 'rsync -a |
---|
398 | --link-dest=TO/Archives/(previous) FROM TO/Archives/(new); ln -sf |
---|
399 | TO/Archives/(new) TO/Latest'.""" |
---|
400 | |
---|
401 | class WebopenOptions(FileStoreOptions): |
---|
402 | optFlags = [ |
---|
403 | ("info", "i", "Open the t=info page for the file"), |
---|
404 | ] |
---|
405 | def parseArgs(self, where=''): |
---|
406 | self.where = argv_to_unicode(where) |
---|
407 | |
---|
408 | synopsis = "[options] [ALIAS:PATH]" |
---|
409 | |
---|
410 | description = """ |
---|
411 | Open a web browser to the contents of some file or |
---|
412 | directory on the grid. When run without arguments, open the Welcome |
---|
413 | page.""" |
---|
414 | |
---|
415 | class ManifestOptions(FileStoreOptions): |
---|
416 | optFlags = [ |
---|
417 | ("storage-index", "s", "Only print storage index strings, not pathname+cap."), |
---|
418 | ("verify-cap", None, "Only print verifycap, not pathname+cap."), |
---|
419 | ("repair-cap", None, "Only print repaircap, not pathname+cap."), |
---|
420 | ("raw", "r", "Display raw JSON data instead of parsed."), |
---|
421 | ] |
---|
422 | def parseArgs(self, where=''): |
---|
423 | self.where = argv_to_unicode(where) |
---|
424 | |
---|
425 | synopsis = "[options] [ALIAS:PATH]" |
---|
426 | description = """ |
---|
427 | Print a list of all files and directories reachable from the given |
---|
428 | starting point.""" |
---|
429 | |
---|
430 | class StatsOptions(FileStoreOptions): |
---|
431 | optFlags = [ |
---|
432 | ("raw", "r", "Display raw JSON data instead of parsed"), |
---|
433 | ] |
---|
434 | def parseArgs(self, where=''): |
---|
435 | self.where = argv_to_unicode(where) |
---|
436 | |
---|
437 | synopsis = "[options] [ALIAS:PATH]" |
---|
438 | description = """ |
---|
439 | Print statistics about of all files and directories reachable from the |
---|
440 | given starting point.""" |
---|
441 | |
---|
442 | class CheckOptions(FileStoreOptions): |
---|
443 | optFlags = [ |
---|
444 | ("raw", None, "Display raw JSON data instead of parsed."), |
---|
445 | ("verify", None, "Verify all hashes, instead of merely querying share presence."), |
---|
446 | ("repair", None, "Automatically repair any problems found."), |
---|
447 | ("add-lease", None, "Add/renew lease on all shares."), |
---|
448 | ] |
---|
449 | def parseArgs(self, *locations): |
---|
450 | self.locations = list(map(argv_to_unicode, locations)) |
---|
451 | |
---|
452 | synopsis = "[options] [ALIAS:PATH]" |
---|
453 | description = """ |
---|
454 | Check a single file or directory: count how many shares are available and |
---|
455 | verify their hashes. Optionally repair the file if any problems were |
---|
456 | found.""" |
---|
457 | |
---|
458 | class DeepCheckOptions(FileStoreOptions): |
---|
459 | optFlags = [ |
---|
460 | ("raw", None, "Display raw JSON data instead of parsed."), |
---|
461 | ("verify", None, "Verify all hashes, instead of merely querying share presence."), |
---|
462 | ("repair", None, "Automatically repair any problems found."), |
---|
463 | ("add-lease", None, "Add/renew lease on all shares."), |
---|
464 | ("verbose", "v", "Be noisy about what is happening."), |
---|
465 | ] |
---|
466 | def parseArgs(self, *locations): |
---|
467 | self.locations = list(map(argv_to_unicode, locations)) |
---|
468 | |
---|
469 | synopsis = "[options] [ALIAS:PATH]" |
---|
470 | description = """ |
---|
471 | Check all files and directories reachable from the given starting point |
---|
472 | (which must be a directory), like 'tahoe check' but for multiple files. |
---|
473 | Optionally repair any problems found.""" |
---|
474 | |
---|
475 | subCommands : SubCommands = [ |
---|
476 | ("mkdir", None, MakeDirectoryOptions, "Create a new directory."), |
---|
477 | ("add-alias", None, AddAliasOptions, "Add a new alias cap."), |
---|
478 | ("create-alias", None, CreateAliasOptions, "Create a new alias cap."), |
---|
479 | ("list-aliases", None, ListAliasesOptions, "List all alias caps."), |
---|
480 | ("ls", None, ListOptions, "List a directory."), |
---|
481 | ("get", None, GetOptions, "Retrieve a file from the grid."), |
---|
482 | ("put", None, PutOptions, "Upload a file into the grid."), |
---|
483 | ("cp", None, CpOptions, "Copy one or more files or directories."), |
---|
484 | ("unlink", None, UnlinkOptions, "Unlink a file or directory on the grid."), |
---|
485 | ("mv", None, MvOptions, "Move a file within the grid."), |
---|
486 | ("ln", None, LnOptions, "Make an additional link to an existing file or directory."), |
---|
487 | ("backup", None, BackupOptions, "Make target dir look like local dir."), |
---|
488 | ("webopen", None, WebopenOptions, "Open a web browser to a grid file or directory."), |
---|
489 | ("manifest", None, ManifestOptions, "List all files/directories in a subtree."), |
---|
490 | ("stats", None, StatsOptions, "Print statistics about all files/directories in a subtree."), |
---|
491 | ("check", None, CheckOptions, "Check a single file or directory."), |
---|
492 | ("deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point."), |
---|
493 | ("status", None, TahoeStatusCommand, "Various status information."), |
---|
494 | ] |
---|
495 | |
---|
496 | def mkdir(options): |
---|
497 | from allmydata.scripts import tahoe_mkdir |
---|
498 | rc = tahoe_mkdir.mkdir(options) |
---|
499 | return rc |
---|
500 | |
---|
501 | def add_alias(options): |
---|
502 | from allmydata.scripts import tahoe_add_alias |
---|
503 | rc = tahoe_add_alias.add_alias(options) |
---|
504 | return rc |
---|
505 | |
---|
506 | def create_alias(options): |
---|
507 | from allmydata.scripts import tahoe_add_alias |
---|
508 | rc = tahoe_add_alias.create_alias(options) |
---|
509 | return rc |
---|
510 | |
---|
511 | def list_aliases(options): |
---|
512 | from allmydata.scripts import tahoe_add_alias |
---|
513 | rc = tahoe_add_alias.list_aliases(options) |
---|
514 | return rc |
---|
515 | |
---|
516 | def list_(options): |
---|
517 | from allmydata.scripts import tahoe_ls |
---|
518 | rc = tahoe_ls.ls(options) |
---|
519 | return rc |
---|
520 | |
---|
521 | def get(options): |
---|
522 | from allmydata.scripts import tahoe_get |
---|
523 | rc = tahoe_get.get(options) |
---|
524 | if rc == 0: |
---|
525 | if options.to_file is None: |
---|
526 | # be quiet, since the file being written to stdout should be |
---|
527 | # proof enough that it worked, unless the user is unlucky |
---|
528 | # enough to have picked an empty file |
---|
529 | pass |
---|
530 | else: |
---|
531 | print("%s retrieved and written to %s" % \ |
---|
532 | (options.from_file, options.to_file), file=options.stderr) |
---|
533 | return rc |
---|
534 | |
---|
535 | def put(options): |
---|
536 | from allmydata.scripts import tahoe_put |
---|
537 | rc = tahoe_put.put(options) |
---|
538 | return rc |
---|
539 | |
---|
540 | def cp(options): |
---|
541 | from allmydata.scripts import tahoe_cp |
---|
542 | rc = tahoe_cp.copy(options) |
---|
543 | return rc |
---|
544 | |
---|
545 | def unlink(options, command="unlink"): |
---|
546 | from allmydata.scripts import tahoe_unlink |
---|
547 | rc = tahoe_unlink.unlink(options, command=command) |
---|
548 | return rc |
---|
549 | |
---|
550 | def rm(options): |
---|
551 | return unlink(options, command="rm") |
---|
552 | |
---|
553 | def mv(options): |
---|
554 | from allmydata.scripts import tahoe_mv |
---|
555 | rc = tahoe_mv.mv(options, mode="move") |
---|
556 | return rc |
---|
557 | |
---|
558 | def ln(options): |
---|
559 | from allmydata.scripts import tahoe_mv |
---|
560 | rc = tahoe_mv.mv(options, mode="link") |
---|
561 | return rc |
---|
562 | |
---|
563 | def backup(options): |
---|
564 | from allmydata.scripts import tahoe_backup |
---|
565 | rc = tahoe_backup.backup(options) |
---|
566 | return rc |
---|
567 | |
---|
568 | def webopen(options, opener=None): |
---|
569 | from allmydata.scripts import tahoe_webopen |
---|
570 | rc = tahoe_webopen.webopen(options, opener=opener) |
---|
571 | return rc |
---|
572 | |
---|
573 | def manifest(options): |
---|
574 | from allmydata.scripts import tahoe_manifest |
---|
575 | rc = tahoe_manifest.manifest(options) |
---|
576 | return rc |
---|
577 | |
---|
578 | def stats(options): |
---|
579 | from allmydata.scripts import tahoe_manifest |
---|
580 | rc = tahoe_manifest.stats(options) |
---|
581 | return rc |
---|
582 | |
---|
583 | def check(options): |
---|
584 | from allmydata.scripts import tahoe_check |
---|
585 | rc = tahoe_check.check(options) |
---|
586 | return rc |
---|
587 | |
---|
588 | def deepcheck(options): |
---|
589 | from allmydata.scripts import tahoe_check |
---|
590 | rc = tahoe_check.deepcheck(options) |
---|
591 | return rc |
---|
592 | |
---|
593 | def status(options): |
---|
594 | from allmydata.scripts import tahoe_status |
---|
595 | return tahoe_status.do_status(options) |
---|
596 | |
---|
597 | dispatch = { |
---|
598 | "mkdir": mkdir, |
---|
599 | "add-alias": add_alias, |
---|
600 | "create-alias": create_alias, |
---|
601 | "list-aliases": list_aliases, |
---|
602 | "ls": list_, |
---|
603 | "get": get, |
---|
604 | "put": put, |
---|
605 | "cp": cp, |
---|
606 | "unlink": unlink, |
---|
607 | "rm": rm, |
---|
608 | "mv": mv, |
---|
609 | "ln": ln, |
---|
610 | "backup": backup, |
---|
611 | "webopen": webopen, |
---|
612 | "manifest": manifest, |
---|
613 | "stats": stats, |
---|
614 | "check": check, |
---|
615 | "deep-check": deepcheck, |
---|
616 | "status": status, |
---|
617 | } |
---|