]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/cli.py
add --add-lease to 'tahoe check', 'tahoe deep-check', and webapi.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / cli.py
1
2 import os.path, re, sys
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 class CreateAliasOptions(VDriveOptions):
62     def parseArgs(self, alias):
63         self.alias = alias
64
65 class ListAliasOptions(VDriveOptions):
66     pass
67
68 class ListOptions(VDriveOptions):
69     optFlags = [
70         ("long", "l", "Use long format: show file sizes, and timestamps"),
71         ("uri", "u", "Show file/directory URIs"),
72         ("readonly-uri", None, "Show readonly file/directory URIs"),
73         ("classify", "F", "Append '/' to directory names, and '*' to mutable"),
74         ("json", None, "Show the raw JSON output"),
75         ]
76     def parseArgs(self, where=""):
77         self.where = where
78
79     longdesc = """List the contents of some portion of the virtual drive."""
80
81 class GetOptions(VDriveOptions):
82     def parseArgs(self, arg1, arg2=None):
83         # tahoe get FOO |less            # write to stdout
84         # tahoe get tahoe:FOO |less      # same
85         # tahoe get FOO bar              # write to local file
86         # tahoe get tahoe:FOO bar        # same
87
88         self.from_file = arg1
89         self.to_file = arg2
90         if self.to_file == "-":
91             self.to_file = None
92
93     def getSynopsis(self):
94         return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
95
96     longdesc = """Retrieve a file from the virtual drive and write it to the
97     local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file
98     will be written to stdout."""
99
100     def getUsage(self, width=None):
101         t = VDriveOptions.getUsage(self, width)
102         t += """
103 Examples:
104  % tahoe get FOO |less            # write to stdout
105  % tahoe get tahoe:FOO |less      # same
106  % tahoe get FOO bar              # write to local file
107  % tahoe get tahoe:FOO bar        # same
108 """
109         return t
110
111 class PutOptions(VDriveOptions):
112     optFlags = [
113         ("mutable", "m", "Create a mutable file instead of an immutable one."),
114         ]
115
116     def parseArgs(self, arg1=None, arg2=None):
117         # cat FILE | tahoe put           # create unlinked file from stdin
118         # cat FILE | tahoe put -         # same
119         # tahoe put bar                  # create unlinked file from local 'bar'
120         # cat FILE | tahoe put - FOO     # create tahoe:FOO from stdin
121         # tahoe put bar FOO              # copy local 'bar' to tahoe:FOO
122         # tahoe put bar tahoe:FOO        # same
123
124         if arg1 is not None and arg2 is not None:
125             self.from_file = arg1
126             self.to_file = arg2
127         elif arg1 is not None and arg2 is None:
128             self.from_file = arg1 # might be "-"
129             self.to_file = None
130         else:
131             self.from_file = None
132             self.to_file = None
133         if self.from_file == "-":
134             self.from_file = None
135
136     def getSynopsis(self):
137         return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
138
139     longdesc = """Put a file into the virtual drive (copying the file's
140     contents from the local filesystem). If VDRIVE_FILE is missing, upload
141     the file but do not link it into a directory: prints the new filecap to
142     stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin.
143     VDRIVE_FILE is assumed to start with tahoe: unless otherwise specified."""
144
145     def getUsage(self, width=None):
146         t = VDriveOptions.getUsage(self, width)
147         t += """
148 Examples:
149  % cat FILE | tahoe put                # create unlinked file from stdin
150  % cat FILE | tahoe -                  # same
151  % tahoe put bar                       # create unlinked file from local 'bar'
152  % cat FILE | tahoe put - FOO          # create tahoe:FOO from stdin
153  % tahoe put bar FOO                   # copy local 'bar' to tahoe:FOO
154  % tahoe put bar tahoe:FOO             # same
155  % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place
156 """
157         return t
158
159 class CpOptions(VDriveOptions):
160     optFlags = [
161         ("recursive", "r", "Copy source directory recursively."),
162         ("verbose", "v", "Be noisy about what is happening."),
163         ]
164     def parseArgs(self, *args):
165         if len(args) < 2:
166             raise usage.UsageError("cp requires at least two arguments")
167         self.sources = args[:-1]
168         self.destination = args[-1]
169
170 class RmOptions(VDriveOptions):
171     def parseArgs(self, where):
172         self.where = where
173
174     def getSynopsis(self):
175         return "%s rm VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
176
177 class MvOptions(VDriveOptions):
178     def parseArgs(self, frompath, topath):
179         self.from_file = frompath
180         self.to_file = topath
181
182     def getSynopsis(self):
183         return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
184
185 class LnOptions(VDriveOptions):
186     def parseArgs(self, frompath, topath):
187         self.from_file = frompath
188         self.to_file = topath
189
190     def getSynopsis(self):
191         return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
192
193 class BackupOptions(VDriveOptions):
194     optFlags = [
195         ("verbose", "v", "Be noisy about what is happening."),
196         ("no-backupdb", None, "Do not use the SQLite-based backup-database (always upload all files)."),
197         ("ignore-timestamps", None, "Do not use backupdb timestamps to decide if a local file is unchanged."),
198         ]
199
200     def parseArgs(self, localdir, topath):
201         self.from_dir = localdir
202         self.to_dir = topath
203
204     def getSynopsis(Self):
205         return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0])
206
207     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'."""
208
209 class WebopenOptions(VDriveOptions):
210     def parseArgs(self, where=''):
211         self.where = where
212
213     def getSynopsis(self):
214         return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
215
216     longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive."""
217
218 class ManifestOptions(VDriveOptions):
219     optFlags = [
220         ("storage-index", "s", "Only print storage index strings, not pathname+cap"),
221         ("verify-cap", None, "Only print verifycap, not pathname+cap"),
222         ("repair-cap", None, "Only print repaircap, not pathname+cap"),
223         ("raw", "r", "Display raw JSON data instead of parsed"),
224         ]
225     def parseArgs(self, where=''):
226         self.where = where
227
228     def getSynopsis(self):
229         return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
230
231     longdesc = """Print a list of all files/directories reachable from the given starting point."""
232
233 class StatsOptions(VDriveOptions):
234     optFlags = [
235         ("raw", "r", "Display raw JSON data instead of parsed"),
236         ]
237     def parseArgs(self, where=''):
238         self.where = where
239
240     def getSynopsis(self):
241         return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
242
243     longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
244
245 class CheckOptions(VDriveOptions):
246     optFlags = [
247         ("raw", None, "Display raw JSON data instead of parsed"),
248         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
249         ("repair", None, "Automatically repair any problems found"),
250         ("add-lease", None, "Add/renew lease on all shares"),
251         ]
252     def parseArgs(self, where=''):
253         self.where = where
254
255     def getSynopsis(self):
256         return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
257
258     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."""
259
260 class DeepCheckOptions(VDriveOptions):
261     optFlags = [
262         ("raw", None, "Display raw JSON data instead of parsed"),
263         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
264         ("repair", None, "Automatically repair any problems found"),
265         ("add-lease", None, "Add/renew lease on all shares"),
266         ("verbose", "v", "Be noisy about what is happening."),
267         ]
268     def parseArgs(self, where=''):
269         self.where = where
270
271     def getSynopsis(self):
272         return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
273
274     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."""
275
276 subCommands = [
277     ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
278     ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
279     ["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
280     ["list-aliases", None, ListAliasOptions, "List all alias caps"],
281     ["ls", None, ListOptions, "List a directory"],
282     ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
283     ["put", None, PutOptions, "Upload a file into the virtual drive."],
284     ["cp", None, CpOptions, "Copy one or more files."],
285     ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
286     ["mv", None, MvOptions, "Move a file within the virtual drive."],
287     ["ln", None, LnOptions, "Make an additional link to an existing file."],
288     ["backup", None, BackupOptions, "Make target dir look like local dir."],
289     ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
290     ["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
291     ["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
292     ["check", None, CheckOptions, "Check a single file or directory"],
293     ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"],
294     ]
295
296 def mkdir(options):
297     from allmydata.scripts import tahoe_mkdir
298     rc = tahoe_mkdir.mkdir(options)
299     return rc
300
301 def add_alias(options):
302     from allmydata.scripts import tahoe_add_alias
303     rc = tahoe_add_alias.add_alias(options)
304     return rc
305
306 def create_alias(options):
307     from allmydata.scripts import tahoe_add_alias
308     rc = tahoe_add_alias.create_alias(options)
309     return rc
310
311 def list_aliases(options):
312     from allmydata.scripts import tahoe_add_alias
313     rc = tahoe_add_alias.list_aliases(options)
314     return rc
315
316 def list(options):
317     from allmydata.scripts import tahoe_ls
318     rc = tahoe_ls.list(options)
319     return rc
320
321 def get(options):
322     from allmydata.scripts import tahoe_get
323     rc = tahoe_get.get(options)
324     if rc == 0:
325         if options.to_file is None:
326             # be quiet, since the file being written to stdout should be
327             # proof enough that it worked, unless the user is unlucky
328             # enough to have picked an empty file
329             pass
330         else:
331             print >>options.stderr, "%s retrieved and written to %s" % \
332                   (options.from_file, options.to_file)
333     return rc
334
335 def put(options):
336     from allmydata.scripts import tahoe_put
337     rc = tahoe_put.put(options)
338     return rc
339
340 def cp(options):
341     from allmydata.scripts import tahoe_cp
342     rc = tahoe_cp.copy(options)
343     return rc
344
345 def rm(options):
346     from allmydata.scripts import tahoe_rm
347     rc = tahoe_rm.rm(options)
348     return rc
349
350 def mv(options):
351     from allmydata.scripts import tahoe_mv
352     rc = tahoe_mv.mv(options, mode="move")
353     return rc
354
355 def ln(options):
356     from allmydata.scripts import tahoe_mv
357     rc = tahoe_mv.mv(options, mode="link")
358     return rc
359
360 def backup(options):
361     from allmydata.scripts import tahoe_backup
362     rc = tahoe_backup.backup(options)
363     return rc
364
365 def webopen(options, opener=None):
366     from allmydata.scripts import tahoe_webopen
367     rc = tahoe_webopen.webopen(options, opener=opener)
368     return rc
369
370 def manifest(options):
371     from allmydata.scripts import tahoe_manifest
372     rc = tahoe_manifest.manifest(options)
373     return rc
374
375 def stats(options):
376     from allmydata.scripts import tahoe_manifest
377     rc = tahoe_manifest.stats(options)
378     return rc
379
380 def check(options):
381     from allmydata.scripts import tahoe_check
382     rc = tahoe_check.check(options)
383     return rc
384
385 def deepcheck(options):
386     from allmydata.scripts import tahoe_check
387     rc = tahoe_check.deepcheck(options)
388     return rc
389
390 dispatch = {
391     "mkdir": mkdir,
392     "add-alias": add_alias,
393     "create-alias": create_alias,
394     "list-aliases": list_aliases,
395     "ls": list,
396     "get": get,
397     "put": put,
398     "cp": cp,
399     "rm": rm,
400     "mv": mv,
401     "ln": ln,
402     "backup": backup,
403     "webopen": webopen,
404     "manifest": manifest,
405     "stats": stats,
406     "check": check,
407     "deep-check": deepcheck,
408     }
409