]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/cli.py
Update tahoe mv help text.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / cli.py
1 import os.path, re, sys, fnmatch
2 from twisted.python import usage
3 from allmydata.scripts.common import BaseOptions, get_aliases
4
5 NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
6
7 class VDriveOptions(BaseOptions, usage.Options):
8     optParameters = [
9         ["node-directory", "d", "~/.tahoe",
10          "Look here to find out which Tahoe node should be used for all "
11          "operations. The directory should either contain a full Tahoe node, "
12          "or a file named node.url which points to some other Tahoe node. "
13          "It should also contain a file named private/aliases which contains "
14          "the mapping from alias name to root dirnode URI."
15          ],
16         ["node-url", "u", None,
17          "URL of the tahoe node to use, a URL like \"http://127.0.0.1:3456\". "
18          "This overrides the URL found in the --node-directory ."],
19         ["dir-cap", None, None,
20          "Which dirnode URI should be used as the 'tahoe' alias."]
21         ]
22
23     def postOptions(self):
24         # compute a node-url from the existing options, put in self['node-url']
25         if self['node-directory']:
26             if sys.platform == 'win32' and self['node-directory'] == '~/.tahoe':
27                 from allmydata.windows import registry
28                 self['node-directory'] = registry.get_base_dir_path()
29             else:
30                 self['node-directory'] = os.path.expanduser(self['node-directory'])
31         if self['node-url']:
32             if (not isinstance(self['node-url'], basestring)
33                 or not NODEURL_RE.match(self['node-url'])):
34                 msg = ("--node-url is required to be a string and look like "
35                        "\"http://HOSTNAMEORADDR:PORT\", not: %r" %
36                        (self['node-url'],))
37                 raise usage.UsageError(msg)
38         else:
39             node_url_file = os.path.join(self['node-directory'], "node.url")
40             self['node-url'] = open(node_url_file, "r").read().strip()
41         if self['node-url'][-1] != "/":
42             self['node-url'] += "/"
43
44         aliases = get_aliases(self['node-directory'])
45         if self['dir-cap']:
46             aliases["tahoe"] = self['dir-cap']
47         self.aliases = aliases # maps alias name to dircap
48
49
50 class MakeDirectoryOptions(VDriveOptions):
51     def parseArgs(self, where=""):
52         self.where = where
53     longdesc = """Create a new directory, either unlinked or as a subdirectory."""
54
55 class AddAliasOptions(VDriveOptions):
56     def parseArgs(self, alias, cap):
57         self.alias = alias
58         self.cap = cap
59
60     def getSynopsis(self):
61         return "%s add-alias ALIAS DIRCAP" % (os.path.basename(sys.argv[0]),)
62
63     longdesc = """Add a new alias for an existing directory."""
64
65 class CreateAliasOptions(VDriveOptions):
66     def parseArgs(self, alias):
67         self.alias = alias
68
69     def getSynopsis(self):
70         return "%s create-alias ALIAS" % (os.path.basename(sys.argv[0]),)
71
72     longdesc = """Creates a new directory and adds an alias for it."""
73
74 class ListAliasOptions(VDriveOptions):
75     longdesc = """Displays a table of all configured aliases."""
76
77 class ListOptions(VDriveOptions):
78     optFlags = [
79         ("long", "l", "Use long format: show file sizes, and timestamps"),
80         ("uri", "u", "Show file/directory URIs"),
81         ("readonly-uri", None, "Show readonly file/directory URIs"),
82         ("classify", "F", "Append '/' to directory names, and '*' to mutable"),
83         ("json", None, "Show the raw JSON output"),
84         ]
85     def parseArgs(self, where=""):
86         self.where = where
87
88     longdesc = """List the contents of some portion of the virtual drive."""
89
90 class GetOptions(VDriveOptions):
91     def parseArgs(self, arg1, arg2=None):
92         # tahoe get FOO |less            # write to stdout
93         # tahoe get tahoe:FOO |less      # same
94         # tahoe get FOO bar              # write to local file
95         # tahoe get tahoe:FOO bar        # same
96
97         self.from_file = arg1
98         self.to_file = arg2
99         if self.to_file == "-":
100             self.to_file = None
101
102     def getSynopsis(self):
103         return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
104
105     longdesc = """Retrieve a file from the virtual drive and write it to the
106     local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file
107     will be written to stdout."""
108
109     def getUsage(self, width=None):
110         t = VDriveOptions.getUsage(self, width)
111         t += """
112 Examples:
113  % tahoe get FOO |less            # write to stdout
114  % tahoe get tahoe:FOO |less      # same
115  % tahoe get FOO bar              # write to local file
116  % tahoe get tahoe:FOO bar        # same
117 """
118         return t
119
120 class PutOptions(VDriveOptions):
121     optFlags = [
122         ("mutable", "m", "Create a mutable file instead of an immutable one."),
123         ]
124
125     def parseArgs(self, arg1=None, arg2=None):
126         # cat FILE | tahoe put           # create unlinked file from stdin
127         # cat FILE | tahoe put -         # same
128         # tahoe put bar                  # create unlinked file from local 'bar'
129         # cat FILE | tahoe put - FOO     # create tahoe:FOO from stdin
130         # tahoe put bar FOO              # copy local 'bar' to tahoe:FOO
131         # tahoe put bar tahoe:FOO        # same
132
133         if arg1 is not None and arg2 is not None:
134             self.from_file = arg1
135             self.to_file = arg2
136         elif arg1 is not None and arg2 is None:
137             self.from_file = arg1 # might be "-"
138             self.to_file = None
139         else:
140             self.from_file = None
141             self.to_file = None
142         if self.from_file == "-":
143             self.from_file = None
144
145     def getSynopsis(self):
146         return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
147
148     longdesc = """Put a file into the virtual drive (copying the file's
149     contents from the local filesystem). If VDRIVE_FILE is missing, upload
150     the file but do not link it into a directory: prints the new filecap to
151     stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin.
152     VDRIVE_FILE is assumed to start with tahoe: unless otherwise specified."""
153
154     def getUsage(self, width=None):
155         t = VDriveOptions.getUsage(self, width)
156         t += """
157 Examples:
158  % cat FILE | tahoe put                # create unlinked file from stdin
159  % cat FILE | tahoe -                  # same
160  % tahoe put bar                       # create unlinked file from local 'bar'
161  % cat FILE | tahoe put - FOO          # create tahoe:FOO from stdin
162  % tahoe put bar FOO                   # copy local 'bar' to tahoe:FOO
163  % tahoe put bar tahoe:FOO             # same
164  % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place
165 """
166         return t
167
168 class CpOptions(VDriveOptions):
169     optFlags = [
170         ("recursive", "r", "Copy source directory recursively."),
171         ("verbose", "v", "Be noisy about what is happening."),
172         ("caps-only", None,
173          "When copying to local files, write out filecaps instead of actual "
174          "data. (only useful for debugging and tree-comparison purposes)"),
175         ]
176     def parseArgs(self, *args):
177         if len(args) < 2:
178             raise usage.UsageError("cp requires at least two arguments")
179         self.sources = args[:-1]
180         self.destination = args[-1]
181     def getSynopsis(self):
182         return "Usage: tahoe [options] cp FROM.. TO"
183     longdesc = """
184     Use 'tahoe cp' to copy files between a local filesystem and a Tahoe
185     virtual filesystem. Any FROM/TO arguments that begin with an alias
186     indicate Tahoe-side files, and arguments which do not indicate local
187     files. Directories will be copied recursively. New Tahoe-side directories
188     will be created when necessary. Assuming that you have previously set up
189     an alias 'home' with 'tahoe create-alias home', here are some examples:
190
191     tahoe cp ~/foo.txt home:  # creates tahoe-side home:foo.txt
192
193     tahoe cp ~/foo.txt /tmp/bar.txt home:  # copies two files to home:
194
195     tahoe cp ~/Pictures home:stuff/my-pictures  # copies directory recursively
196
197     You can also use a dircap as either FROM or TO target:
198
199     tahoe cp URI:DIR2-RO:j74uhg25nwdpjpacl6rkat2yhm:kav7ijeft5h7r7rxdp5bgtlt3viv32yabqajkrdykozia5544jqa/wiki.html ./   # copy Zooko's wiki page to a local file
200
201     This command still has some limitations: symlinks, special files (device
202     nodes, named pipes), and non-ASCII filenames are not handled very well.
203     Arguments should probably not have trailing slashes. 'tahoe cp' does not
204     behave as much like /bin/cp as you would wish, especially with respect to
205     trailing slashes.
206     """
207
208 class RmOptions(VDriveOptions):
209     def parseArgs(self, where):
210         self.where = where
211
212     def getSynopsis(self):
213         return "%s rm VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
214
215 class MvOptions(VDriveOptions):
216     def parseArgs(self, frompath, topath):
217         self.from_file = frompath
218         self.to_file = topath
219
220     def getSynopsis(self):
221         return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
222     longdesc = """
223     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'.
224
225     If moving a remote file into a remote directory, you'll need to append a '/' to the name of the remote directory, e.g., 'tahoe mv tahoe:file1 tahoe:dir/', not 'tahoe mv tahoe:file1 tahoe:dir'.
226
227     Note that it is not possible to use this command to move local files to the grid -- use 'tahoe cp' for that.
228     """
229
230 class LnOptions(VDriveOptions):
231     def parseArgs(self, frompath, topath):
232         self.from_file = frompath
233         self.to_file = topath
234
235     def getSynopsis(self):
236         return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
237
238 class BackupConfigurationError(Exception):
239     pass
240
241 class BackupOptions(VDriveOptions):
242     optFlags = [
243         ("verbose", "v", "Be noisy about what is happening."),
244         ("ignore-timestamps", None, "Do not use backupdb timestamps to decide if a local file is unchanged."),
245         ]
246
247     vcs_patterns = ('CVS', 'RCS', 'SCCS', '.git', '.gitignore', '.cvsignore', '.svn',
248                    '.arch-ids','{arch}', '=RELEASE-ID', '=meta-update', '=update',
249                    '.bzr', '.bzrignore', '.bzrtags', '.hg', '.hgignore', '_darcs')
250
251     def __init__(self):
252         super(BackupOptions, self).__init__()
253         self['exclude'] = set()
254
255     def parseArgs(self, localdir, topath):
256         self.from_dir = localdir
257         self.to_dir = topath
258
259     def getSynopsis(Self):
260         return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0])
261
262     def opt_exclude(self, pattern):
263         """Ignore files matching a glob pattern. You may give multiple
264         '--exclude' options."""
265         g = pattern.strip()
266         if g:
267             exclude = self['exclude']
268             exclude.add(g)
269
270     def opt_exclude_from(self, filepath):
271         """Ignore file matching glob patterns listed in file, one per
272         line."""
273         try:
274             exclude_file = file(filepath)
275         except:
276             raise BackupConfigurationError('Error opening exclude file %r.' % filepath)
277         try:
278             for line in exclude_file:
279                 self.opt_exclude(line)
280         finally:
281             exclude_file.close()
282
283     def opt_exclude_vcs(self):
284         """Exclude files and directories used by following version
285         control systems: 'CVS', 'RCS', 'SCCS', 'SVN', 'Arch',
286         'Bazaar', 'Mercurial', and 'Darcs'."""
287         for pattern in self.vcs_patterns:
288             self.opt_exclude(pattern)
289
290     def filter_listdir(self, listdir):
291         """Yields non-excluded childpaths in path."""
292         exclude = self['exclude']
293         exclude_regexps = [re.compile(fnmatch.translate(pat)) for pat in exclude]
294         for filename in listdir:
295             for regexp in exclude_regexps:
296                 if regexp.match(filename):
297                     break
298             else:
299                 yield filename
300
301     longdesc = """Add a versioned backup of the local FROM directory to a timestamped subdir of the (tahoe) TO/Archives directory, sharing as many files and directories as possible with the previous backup. Creates TO/Latest as a reference to the latest backup. Behaves somewhat like 'rsync -a --link-dest=TO/Archives/(previous) FROM TO/Archives/(new); ln -sf TO/Archives/(new) TO/Latest'."""
302
303 class WebopenOptions(VDriveOptions):
304     def parseArgs(self, where=''):
305         self.where = where
306
307     def getSynopsis(self):
308         return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
309
310     longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive. When called without arguments, opens to the Welcome page."""
311
312 class ManifestOptions(VDriveOptions):
313     optFlags = [
314         ("storage-index", "s", "Only print storage index strings, not pathname+cap"),
315         ("verify-cap", None, "Only print verifycap, not pathname+cap"),
316         ("repair-cap", None, "Only print repaircap, not pathname+cap"),
317         ("raw", "r", "Display raw JSON data instead of parsed"),
318         ]
319     def parseArgs(self, where=''):
320         self.where = where
321
322     def getSynopsis(self):
323         return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
324
325     longdesc = """Print a list of all files/directories reachable from the given starting point."""
326
327 class StatsOptions(VDriveOptions):
328     optFlags = [
329         ("raw", "r", "Display raw JSON data instead of parsed"),
330         ]
331     def parseArgs(self, where=''):
332         self.where = where
333
334     def getSynopsis(self):
335         return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
336
337     longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
338
339 class CheckOptions(VDriveOptions):
340     optFlags = [
341         ("raw", None, "Display raw JSON data instead of parsed"),
342         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
343         ("repair", None, "Automatically repair any problems found"),
344         ("add-lease", None, "Add/renew lease on all shares"),
345         ]
346     def parseArgs(self, where=''):
347         self.where = where
348
349     def getSynopsis(self):
350         return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
351
352     longdesc = """Check a single file or directory: count how many shares are available, verify their hashes. Optionally repair the file if any problems were found."""
353
354 class DeepCheckOptions(VDriveOptions):
355     optFlags = [
356         ("raw", None, "Display raw JSON data instead of parsed"),
357         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
358         ("repair", None, "Automatically repair any problems found"),
359         ("add-lease", None, "Add/renew lease on all shares"),
360         ("verbose", "v", "Be noisy about what is happening."),
361         ]
362     def parseArgs(self, where=''):
363         self.where = where
364
365     def getSynopsis(self):
366         return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
367
368     longdesc = """Check all files/directories reachable from the given starting point (which must be a directory), like 'tahoe check' but for multiple files. Optionally repair any problems found."""
369
370 subCommands = [
371     ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
372     ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
373     ["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
374     ["list-aliases", None, ListAliasOptions, "List all alias caps"],
375     ["ls", None, ListOptions, "List a directory"],
376     ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
377     ["put", None, PutOptions, "Upload a file into the virtual drive."],
378     ["cp", None, CpOptions, "Copy one or more files."],
379     ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
380     ["mv", None, MvOptions, "Move a file within the virtual drive."],
381     ["ln", None, LnOptions, "Make an additional link to an existing file."],
382     ["backup", None, BackupOptions, "Make target dir look like local dir."],
383     ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
384     ["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
385     ["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
386     ["check", None, CheckOptions, "Check a single file or directory"],
387     ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"],
388     ]
389
390 def mkdir(options):
391     from allmydata.scripts import tahoe_mkdir
392     rc = tahoe_mkdir.mkdir(options)
393     return rc
394
395 def add_alias(options):
396     from allmydata.scripts import tahoe_add_alias
397     rc = tahoe_add_alias.add_alias(options)
398     return rc
399
400 def create_alias(options):
401     from allmydata.scripts import tahoe_add_alias
402     rc = tahoe_add_alias.create_alias(options)
403     return rc
404
405 def list_aliases(options):
406     from allmydata.scripts import tahoe_add_alias
407     rc = tahoe_add_alias.list_aliases(options)
408     return rc
409
410 def list(options):
411     from allmydata.scripts import tahoe_ls
412     rc = tahoe_ls.list(options)
413     return rc
414
415 def get(options):
416     from allmydata.scripts import tahoe_get
417     rc = tahoe_get.get(options)
418     if rc == 0:
419         if options.to_file is None:
420             # be quiet, since the file being written to stdout should be
421             # proof enough that it worked, unless the user is unlucky
422             # enough to have picked an empty file
423             pass
424         else:
425             print >>options.stderr, "%s retrieved and written to %s" % \
426                   (options.from_file, options.to_file)
427     return rc
428
429 def put(options):
430     from allmydata.scripts import tahoe_put
431     rc = tahoe_put.put(options)
432     return rc
433
434 def cp(options):
435     from allmydata.scripts import tahoe_cp
436     rc = tahoe_cp.copy(options)
437     return rc
438
439 def rm(options):
440     from allmydata.scripts import tahoe_rm
441     rc = tahoe_rm.rm(options)
442     return rc
443
444 def mv(options):
445     from allmydata.scripts import tahoe_mv
446     rc = tahoe_mv.mv(options, mode="move")
447     return rc
448
449 def ln(options):
450     from allmydata.scripts import tahoe_mv
451     rc = tahoe_mv.mv(options, mode="link")
452     return rc
453
454 def backup(options):
455     from allmydata.scripts import tahoe_backup
456     rc = tahoe_backup.backup(options)
457     return rc
458
459 def webopen(options, opener=None):
460     from allmydata.scripts import tahoe_webopen
461     rc = tahoe_webopen.webopen(options, opener=opener)
462     return rc
463
464 def manifest(options):
465     from allmydata.scripts import tahoe_manifest
466     rc = tahoe_manifest.manifest(options)
467     return rc
468
469 def stats(options):
470     from allmydata.scripts import tahoe_manifest
471     rc = tahoe_manifest.stats(options)
472     return rc
473
474 def check(options):
475     from allmydata.scripts import tahoe_check
476     rc = tahoe_check.check(options)
477     return rc
478
479 def deepcheck(options):
480     from allmydata.scripts import tahoe_check
481     rc = tahoe_check.deepcheck(options)
482     return rc
483
484 dispatch = {
485     "mkdir": mkdir,
486     "add-alias": add_alias,
487     "create-alias": create_alias,
488     "list-aliases": list_aliases,
489     "ls": list,
490     "get": get,
491     "put": put,
492     "cp": cp,
493     "rm": rm,
494     "mv": mv,
495     "ln": ln,
496     "backup": backup,
497     "webopen": webopen,
498     "manifest": manifest,
499     "stats": stats,
500     "check": check,
501     "deep-check": deepcheck,
502     }