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