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