2 import os.path, re, sys, fnmatch
3 from twisted.python import usage
4 from allmydata.scripts.common import BaseOptions, get_aliases
6 NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
8 class VDriveOptions(BaseOptions, usage.Options):
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."
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."]
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()
31 self['node-directory'] = os.path.expanduser(self['node-directory'])
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" %
38 raise usage.UsageError(msg)
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'] += "/"
45 aliases = get_aliases(self['node-directory'])
47 aliases["tahoe"] = self['dir-cap']
48 self.aliases = aliases # maps alias name to dircap
51 class MakeDirectoryOptions(VDriveOptions):
52 def parseArgs(self, where=""):
54 longdesc = """Create a new directory, either unlinked or as a subdirectory."""
56 class AddAliasOptions(VDriveOptions):
57 def parseArgs(self, alias, cap):
61 def getSynopsis(self):
62 return "%s add-alias ALIAS DIRCAP" % (os.path.basename(sys.argv[0]),)
64 longdesc = """Add a new alias for an existing directory."""
66 class CreateAliasOptions(VDriveOptions):
67 def parseArgs(self, alias):
70 def getSynopsis(self):
71 return "%s create-alias ALIAS" % (os.path.basename(sys.argv[0]),)
73 longdesc = """Creates a new directory and adds an alias for it."""
75 class ListAliasOptions(VDriveOptions):
76 longdesc = """Displays a table of all configured aliases."""
78 class ListOptions(VDriveOptions):
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"),
86 def parseArgs(self, where=""):
89 longdesc = """List the contents of some portion of the virtual drive."""
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
100 if self.to_file == "-":
103 def getSynopsis(self):
104 return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
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."""
110 def getUsage(self, width=None):
111 t = VDriveOptions.getUsage(self, width)
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
121 class PutOptions(VDriveOptions):
123 ("mutable", "m", "Create a mutable file instead of an immutable one."),
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
134 if arg1 is not None and arg2 is not None:
135 self.from_file = arg1
137 elif arg1 is not None and arg2 is None:
138 self.from_file = arg1 # might be "-"
141 self.from_file = None
143 if self.from_file == "-":
144 self.from_file = None
146 def getSynopsis(self):
147 return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
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."""
155 def getUsage(self, width=None):
156 t = VDriveOptions.getUsage(self, width)
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
169 class CpOptions(VDriveOptions):
171 ("recursive", "r", "Copy source directory recursively."),
172 ("verbose", "v", "Be noisy about what is happening."),
174 def parseArgs(self, *args):
176 raise usage.UsageError("cp requires at least two arguments")
177 self.sources = args[:-1]
178 self.destination = args[-1]
180 class RmOptions(VDriveOptions):
181 def parseArgs(self, where):
184 def getSynopsis(self):
185 return "%s rm VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
187 class MvOptions(VDriveOptions):
188 def parseArgs(self, frompath, topath):
189 self.from_file = frompath
190 self.to_file = topath
192 def getSynopsis(self):
193 return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
195 class LnOptions(VDriveOptions):
196 def parseArgs(self, frompath, topath):
197 self.from_file = frompath
198 self.to_file = topath
200 def getSynopsis(self):
201 return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
203 class BackupConfigurationError(Exception):
206 class BackupOptions(VDriveOptions):
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."),
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', '.hgrags',
219 super(BackupOptions, self).__init__()
222 def parseArgs(self, localdir, topath):
223 self.from_dir = localdir
226 def getSynopsis(Self):
227 return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0])
229 def opt_exclude(self, pattern):
230 """Ignore files matching a glob pattern. You may give multiple
231 '--exclude' options."""
234 exclude = self['exclude']
238 def opt_exclude_from(self, filepath):
239 """Ignore file matching glob patterns listed in file, one per
242 exclude_file = file(filepath)
244 raise BackupConfigurationError('Error opening exclude file %r.' % filepath)
246 for line in exclude_file:
247 self.opt_exclude(line)
251 def opt_exclude_vcs(self):
252 """Exclude files and directories used by following version
253 control systems: 'CVS', 'RCS', 'SCCS', 'SVN', 'Arch',
254 'Bazaar', 'Mercurial', and 'Darcs'."""
255 for pattern in self.vcs_patterns:
256 self.opt_exclude(pattern)
258 def filter_listdir(self, listdir):
259 """Yields non-excluded childpaths in path."""
260 exclude = self['exclude']
261 excluded_dirmembers = []
262 if listdir and exclude:
263 # expand patterns with a reduce taste
264 for pattern in exclude:
265 excluded_dirmembers += fnmatch.filter(listdir, pattern)
267 for filename in listdir:
268 if filename not in excluded_dirmembers:
271 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'."""
273 class WebopenOptions(VDriveOptions):
274 def parseArgs(self, where=''):
277 def getSynopsis(self):
278 return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
280 longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive."""
282 class ManifestOptions(VDriveOptions):
284 ("storage-index", "s", "Only print storage index strings, not pathname+cap"),
285 ("verify-cap", None, "Only print verifycap, not pathname+cap"),
286 ("repair-cap", None, "Only print repaircap, not pathname+cap"),
287 ("raw", "r", "Display raw JSON data instead of parsed"),
289 def parseArgs(self, where=''):
292 def getSynopsis(self):
293 return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
295 longdesc = """Print a list of all files/directories reachable from the given starting point."""
297 class StatsOptions(VDriveOptions):
299 ("raw", "r", "Display raw JSON data instead of parsed"),
301 def parseArgs(self, where=''):
304 def getSynopsis(self):
305 return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
307 longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
309 class CheckOptions(VDriveOptions):
311 ("raw", None, "Display raw JSON data instead of parsed"),
312 ("verify", None, "Verify all hashes, instead of merely querying share presence"),
313 ("repair", None, "Automatically repair any problems found"),
314 ("add-lease", None, "Add/renew lease on all shares"),
316 def parseArgs(self, where=''):
319 def getSynopsis(self):
320 return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
322 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."""
324 class DeepCheckOptions(VDriveOptions):
326 ("raw", None, "Display raw JSON data instead of parsed"),
327 ("verify", None, "Verify all hashes, instead of merely querying share presence"),
328 ("repair", None, "Automatically repair any problems found"),
329 ("add-lease", None, "Add/renew lease on all shares"),
330 ("verbose", "v", "Be noisy about what is happening."),
332 def parseArgs(self, where=''):
335 def getSynopsis(self):
336 return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
338 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."""
341 ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
342 ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
343 ["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
344 ["list-aliases", None, ListAliasOptions, "List all alias caps"],
345 ["ls", None, ListOptions, "List a directory"],
346 ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
347 ["put", None, PutOptions, "Upload a file into the virtual drive."],
348 ["cp", None, CpOptions, "Copy one or more files."],
349 ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
350 ["mv", None, MvOptions, "Move a file within the virtual drive."],
351 ["ln", None, LnOptions, "Make an additional link to an existing file."],
352 ["backup", None, BackupOptions, "Make target dir look like local dir."],
353 ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
354 ["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
355 ["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
356 ["check", None, CheckOptions, "Check a single file or directory"],
357 ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"],
361 from allmydata.scripts import tahoe_mkdir
362 rc = tahoe_mkdir.mkdir(options)
365 def add_alias(options):
366 from allmydata.scripts import tahoe_add_alias
367 rc = tahoe_add_alias.add_alias(options)
370 def create_alias(options):
371 from allmydata.scripts import tahoe_add_alias
372 rc = tahoe_add_alias.create_alias(options)
375 def list_aliases(options):
376 from allmydata.scripts import tahoe_add_alias
377 rc = tahoe_add_alias.list_aliases(options)
381 from allmydata.scripts import tahoe_ls
382 rc = tahoe_ls.list(options)
386 from allmydata.scripts import tahoe_get
387 rc = tahoe_get.get(options)
389 if options.to_file is None:
390 # be quiet, since the file being written to stdout should be
391 # proof enough that it worked, unless the user is unlucky
392 # enough to have picked an empty file
395 print >>options.stderr, "%s retrieved and written to %s" % \
396 (options.from_file, options.to_file)
400 from allmydata.scripts import tahoe_put
401 rc = tahoe_put.put(options)
405 from allmydata.scripts import tahoe_cp
406 rc = tahoe_cp.copy(options)
410 from allmydata.scripts import tahoe_rm
411 rc = tahoe_rm.rm(options)
415 from allmydata.scripts import tahoe_mv
416 rc = tahoe_mv.mv(options, mode="move")
420 from allmydata.scripts import tahoe_mv
421 rc = tahoe_mv.mv(options, mode="link")
425 from allmydata.scripts import tahoe_backup
426 rc = tahoe_backup.backup(options)
429 def webopen(options, opener=None):
430 from allmydata.scripts import tahoe_webopen
431 rc = tahoe_webopen.webopen(options, opener=opener)
434 def manifest(options):
435 from allmydata.scripts import tahoe_manifest
436 rc = tahoe_manifest.manifest(options)
440 from allmydata.scripts import tahoe_manifest
441 rc = tahoe_manifest.stats(options)
445 from allmydata.scripts import tahoe_check
446 rc = tahoe_check.check(options)
449 def deepcheck(options):
450 from allmydata.scripts import tahoe_check
451 rc = tahoe_check.deepcheck(options)
456 "add-alias": add_alias,
457 "create-alias": create_alias,
458 "list-aliases": list_aliases,
468 "manifest": manifest,
471 "deep-check": deepcheck,