]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/cli.py
CLI: add 'ln', just like move but without the delete
[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
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 root_dir.cap which contains "
15          "the root dirnode URI that should be used."
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", "r", 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 = self.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     def get_aliases(self, nodedir):
50         from allmydata import uri
51         aliases = {}
52         aliasfile = os.path.join(nodedir, "private", "aliases")
53         rootfile = os.path.join(nodedir, "private", "root_dir.cap")
54         try:
55             f = open(rootfile, "r")
56             rootcap = f.read().strip()
57             if rootcap:
58                 aliases["tahoe"] = uri.from_string_dirnode(rootcap).to_string()
59         except EnvironmentError:
60             pass
61         try:
62             f = open(aliasfile, "r")
63             for line in f.readlines():
64                 line = line.strip()
65                 if line.startswith("#"):
66                     continue
67                 name, cap = line.split(":", 1)
68                 # normalize it: remove http: prefix, urldecode
69                 cap = cap.strip()
70                 aliases[name] = uri.from_string_dirnode(cap).to_string()
71         except EnvironmentError:
72             pass
73         return aliases
74
75 class MakeDirectoryOptions(VDriveOptions):
76     def parseArgs(self, where=""):
77         self.where = where
78     longdesc = """Create a new directory, either unlinked or as a subdirectory."""
79
80 class AddAliasOptions(VDriveOptions):
81     def parseArgs(self, alias, cap):
82         self.alias = alias
83         self.cap = cap
84
85 class ListOptions(VDriveOptions):
86     optFlags = [
87         ("long", "l", "Use long format: show file sizes, and timestamps"),
88         ("uri", "u", "Show file/directory URIs"),
89         ("readonly-uri", None, "Show readonly file/directory URIs"),
90         ("classify", "F", "Append '/' to directory names, and '*' to mutable"),
91         ("json", None, "Show the raw JSON output"),
92         ]
93     def parseArgs(self, where=""):
94         self.where = where
95
96     longdesc = """List the contents of some portion of the virtual drive."""
97
98 class GetOptions(VDriveOptions):
99     def parseArgs(self, arg1, arg2=None):
100         # tahoe get FOO |less            # write to stdout
101         # tahoe get tahoe:FOO |less      # same
102         # tahoe get FOO bar              # write to local file
103         # tahoe get tahoe:FOO bar        # same
104
105         self.from_file = arg1
106         self.to_file = arg2
107         if self.to_file == "-":
108             self.to_file = None
109
110     def getSynopsis(self):
111         return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
112
113     longdesc = """Retrieve a file from the virtual drive and write it to the
114     local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file
115     will be written to stdout."""
116
117 class PutOptions(VDriveOptions):
118     optFlags = [
119         ("mutable", "m", "Create a mutable file instead of an immutable one."),
120         ]
121
122     def parseArgs(self, arg1=None, arg2=None):
123         # cat FILE > tahoe put           # create unlinked file from stdin
124         # cat FILE > tahoe put FOO       # create tahoe:FOO from stdin
125         # cat FILE > tahoe put tahoe:FOO # same
126         # tahoe put bar FOO              # copy local 'bar' to tahoe:FOO
127         # tahoe put bar tahoe:FOO        # same
128
129         if arg1 is not None and arg2 is not None:
130             self.from_file = arg1
131             self.to_file = arg2
132         elif arg1 is not None and arg2 is None:
133             self.from_file = None
134             self.to_file = arg1
135         else:
136             self.from_file = arg1
137             self.to_file = arg2
138         if self.from_file == "-":
139             self.from_file = None
140
141     def getSynopsis(self):
142         return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
143
144     longdesc = """Put a file into the virtual drive (copying the file's
145     contents from the local filesystem). LOCAL_FILE is required to be a
146     local file (it can't be stdin)."""
147
148 class RmOptions(VDriveOptions):
149     def parseArgs(self, where):
150         self.where = where
151
152     def getSynopsis(self):
153         return "%s rm VE_FILE" % (os.path.basename(sys.argv[0]),)
154
155 class MvOptions(VDriveOptions):
156     def parseArgs(self, frompath, topath):
157         self.from_file = frompath
158         self.to_file = topath
159
160     def getSynopsis(self):
161         return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
162
163 class LnOptions(VDriveOptions):
164     def parseArgs(self, frompath, topath):
165         self.from_file = frompath
166         self.to_file = topath
167
168     def getSynopsis(self):
169         return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
170
171 class WebopenOptions(VDriveOptions):
172     def parseArgs(self, vdrive_pathname=""):
173         self['vdrive_pathname'] = vdrive_pathname
174
175     longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive."""
176
177 class ReplOptions(usage.Options):
178     pass
179
180 subCommands = [
181     ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
182     ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
183     ["ls", None, ListOptions, "List a directory"],
184     ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
185     ["put", None, PutOptions, "Upload a file into the virtual drive."],
186     ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
187     ["mv", None, MvOptions, "Move a file within the virtual drive."],
188     ["ln", None, LnOptions, "Make an additional link to an existing file."],
189     ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
190     ["repl", None, ReplOptions, "Open a python interpreter"],
191     ]
192
193 def mkdir(config, stdout, stderr):
194     from allmydata.scripts import tahoe_mkdir
195     rc = tahoe_mkdir.mkdir(config['node-url'],
196                            config.aliases,
197                            config.where,
198                            stdout, stderr)
199     return rc
200
201 def add_alias(config, stdout, stderr):
202     from allmydata.scripts import tahoe_add_alias
203     rc = tahoe_add_alias.add_alias(config['node-directory'],
204                                    config.alias,
205                                    config.cap,
206                                    stdout, stderr)
207     return rc
208
209 def list(config, stdout, stderr):
210     from allmydata.scripts import tahoe_ls
211     rc = tahoe_ls.list(config['node-url'],
212                        config.aliases,
213                        config.where,
214                        config,
215                        stdout, stderr)
216     return rc
217
218 def get(config, stdout, stderr):
219     from allmydata.scripts import tahoe_get
220     rc = tahoe_get.get(config['node-url'],
221                        config.aliases,
222                        config.from_file,
223                        config.to_file,
224                        stdout, stderr)
225     if rc == 0:
226         if config.to_file is None:
227             # be quiet, since the file being written to stdout should be
228             # proof enough that it worked, unless the user is unlucky
229             # enough to have picked an empty file
230             pass
231         else:
232             print >>stderr, "%s retrieved and written to %s" % \
233                   (config.from_file, config.to_file)
234     return rc
235
236 def put(config, stdout, stderr, stdin=sys.stdin):
237     from allmydata.scripts import tahoe_put
238     if config['quiet']:
239         verbosity = 0
240     else:
241         verbosity = 2
242     rc = tahoe_put.put(config['node-url'],
243                        config.aliases,
244                        config.from_file,
245                        config.to_file,
246                        config['mutable'],
247                        verbosity,
248                        stdin, stdout, stderr)
249     return rc
250
251 def rm(config, stdout, stderr):
252     from allmydata.scripts import tahoe_rm
253     if config['quiet']:
254         verbosity = 0
255     else:
256         verbosity = 2
257     rc = tahoe_rm.rm(config['node-url'],
258                      config.aliases,
259                      config.where,
260                      verbosity,
261                      stdout, stderr)
262     return rc
263
264 def mv(config, stdout, stderr):
265     from allmydata.scripts import tahoe_mv
266     rc = tahoe_mv.mv(config['node-url'],
267                      config.aliases,
268                      config.from_file,
269                      config.to_file,
270                      stdout, stderr,
271                      mode="move")
272     return rc
273
274 def ln(config, stdout, stderr):
275     from allmydata.scripts import tahoe_mv
276     rc = tahoe_mv.mv(config['node-url'],
277                      config.aliases,
278                      config.from_file,
279                      config.to_file,
280                      stdout, stderr,
281                      mode="link")
282     return rc
283
284 def webopen(config, stdout, stderr):
285     import urllib, webbrowser
286     nodeurl = config['node-url']
287     if nodeurl[-1] != "/":
288         nodeurl += "/"
289     url = nodeurl + "uri/%s/" % urllib.quote(config['dir-cap'])
290     if config['vdrive_pathname']:
291         url += urllib.quote(config['vdrive_pathname'])
292     webbrowser.open(url)
293     return 0
294
295 def repl(config, stdout, stderr):
296     import code
297     return code.interact()
298
299 dispatch = {
300     "mkdir": mkdir,
301     "add-alias": add_alias,
302     "ls": list,
303     "get": get,
304     "put": put,
305     "rm": rm,
306     "mv": mv,
307     "ln": ln,
308     "webopen": webopen,
309     "repl": repl,
310     }
311