]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/cli.py
#509 CLI: add 'tahoe manifest --stream'
[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
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         ("verify-cap", None, "Only print verifycap, not pathname+cap"),
204         ("repair-cap", None, "Only print repaircap, not pathname+cap"),
205         ("raw", "r", "Display raw JSON data instead of parsed"),
206         ("stream", None, "Stream lines of JSON to stdout. With --storage-index or --verify-cap, stream those strings to stdout."),
207         ]
208     def parseArgs(self, where=''):
209         self.where = where
210
211     def getSynopsis(self):
212         return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
213
214     longdesc = """Print a list of all files/directories reachable from the given starting point."""
215
216 class StatsOptions(VDriveOptions):
217     optFlags = [
218         ("raw", "r", "Display raw JSON data instead of parsed"),
219         ]
220     def parseArgs(self, where=''):
221         self.where = where
222
223     def getSynopsis(self):
224         return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
225
226     longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
227
228 class CheckOptions(VDriveOptions):
229     optFlags = [
230         ("raw", "r", "Display raw JSON data instead of parsed"),
231         ("verify", "v", "Verify all hashes, instead of merely querying share presence"),
232         ("repair", "r", "Automatically repair any problems found"),
233         ]
234     def parseArgs(self, where=''):
235         self.where = where
236
237     def getSynopsis(self):
238         return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
239
240     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."""
241
242 class DeepCheckOptions(VDriveOptions):
243     optFlags = [
244         ("raw", "r", "Display raw JSON data instead of parsed"),
245         ("verify", "v", "Verify all hashes, instead of merely querying share presence"),
246         ("repair", "r", "Automatically repair any problems found"),
247         ]
248     def parseArgs(self, where=''):
249         self.where = where
250
251     def getSynopsis(self):
252         return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
253
254     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."""
255
256 subCommands = [
257     ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
258     ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
259     ["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
260     ["list-aliases", None, ListAliasOptions, "List all alias caps"],
261     ["ls", None, ListOptions, "List a directory"],
262     ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
263     ["put", None, PutOptions, "Upload a file into the virtual drive."],
264     ["cp", None, CpOptions, "Copy one or more files."],
265     ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
266     ["mv", None, MvOptions, "Move a file within the virtual drive."],
267     ["ln", None, LnOptions, "Make an additional link to an existing file."],
268     ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
269     ["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
270     ["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
271     ["check", None, CheckOptions, "Check a single file or directory"],
272     ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"],
273     ]
274
275 def mkdir(options):
276     from allmydata.scripts import tahoe_mkdir
277     rc = tahoe_mkdir.mkdir(options)
278     return rc
279
280 def add_alias(options):
281     from allmydata.scripts import tahoe_add_alias
282     rc = tahoe_add_alias.add_alias(options)
283     return rc
284
285 def create_alias(options):
286     from allmydata.scripts import tahoe_add_alias
287     rc = tahoe_add_alias.create_alias(options)
288     return rc
289
290 def list_aliases(options):
291     from allmydata.scripts import tahoe_add_alias
292     rc = tahoe_add_alias.list_aliases(options)
293     return rc
294
295 def list(options):
296     from allmydata.scripts import tahoe_ls
297     rc = tahoe_ls.list(options)
298     return rc
299
300 def get(options):
301     from allmydata.scripts import tahoe_get
302     rc = tahoe_get.get(options)
303     if rc == 0:
304         if options.to_file is None:
305             # be quiet, since the file being written to stdout should be
306             # proof enough that it worked, unless the user is unlucky
307             # enough to have picked an empty file
308             pass
309         else:
310             print >>options.stderr, "%s retrieved and written to %s" % \
311                   (options.from_file, options.to_file)
312     return rc
313
314 def put(options):
315     from allmydata.scripts import tahoe_put
316     rc = tahoe_put.put(options)
317     return rc
318
319 def cp(options):
320     from allmydata.scripts import tahoe_cp
321     rc = tahoe_cp.copy(options)
322     return rc
323
324 def rm(options):
325     from allmydata.scripts import tahoe_rm
326     rc = tahoe_rm.rm(options)
327     return rc
328
329 def mv(options):
330     from allmydata.scripts import tahoe_mv
331     rc = tahoe_mv.mv(options, mode="move")
332     return rc
333
334 def ln(options):
335     from allmydata.scripts import tahoe_mv
336     rc = tahoe_mv.mv(options, mode="link")
337     return rc
338
339 def webopen(options, opener=None):
340     from allmydata.scripts import tahoe_webopen
341     rc = tahoe_webopen.webopen(options, opener=opener)
342     return rc
343
344 def manifest(options):
345     from allmydata.scripts import tahoe_manifest
346     rc = tahoe_manifest.manifest(options)
347     return rc
348
349 def stats(options):
350     from allmydata.scripts import tahoe_manifest
351     rc = tahoe_manifest.stats(options)
352     return rc
353
354 def check(options):
355     from allmydata.scripts import tahoe_check
356     rc = tahoe_check.check(options)
357     return rc
358
359 def deepcheck(options):
360     from allmydata.scripts import tahoe_check
361     rc = tahoe_check.deepcheck(options)
362     return rc
363
364 dispatch = {
365     "mkdir": mkdir,
366     "add-alias": add_alias,
367     "create-alias": create_alias,
368     "list-aliases": list_aliases,
369     "ls": list,
370     "get": get,
371     "put": put,
372     "cp": cp,
373     "rm": rm,
374     "mv": mv,
375     "ln": ln,
376     "webopen": webopen,
377     "manifest": manifest,
378     "stats": stats,
379     "check": check,
380     "deep-check": deepcheck,
381     }
382