source file: /home/buildslave/tahoe/edgy/build/src/allmydata/scripts/cli.py
file stats: 256 lines, 252 executed: 98.4% covered
coverage versus previous test: 0 lines added, 0 lines removed
    1. import os.path, re, sys, fnmatch
    2. from twisted.python import usage
    3. from allmydata.scripts.common import BaseOptions, get_aliases
    4. 
    5. NODEURL_RE=re.compile("http://([^:]*)(:([1-9][0-9]*))?")
    6. 
    7. class VDriveOptions(BaseOptions, usage.Options):
    8.     optParameters = [
    9.         ["node-directory", "d", "~/.tahoe",
   10.          "Look here to find out which Tahoe node should be used for all "
   11.          "operations. The directory should either contain a full Tahoe node, "
   12.          "or a file named node.url which points to some other Tahoe node. "
   13.          "It should also contain a file named private/aliases which contains "
   14.          "the mapping from alias name to root dirnode URI."
   15.          ],
   16.         ["node-url", "u", None,
   17.          "URL of the tahoe node to use, a URL like \"http://127.0.0.1:3456\". "
   18.          "This overrides the URL found in the --node-directory ."],
   19.         ["dir-cap", None, None,
   20.          "Which dirnode URI should be used as the 'tahoe' alias."]
   21.         ]
   22. 
   23.     def postOptions(self):
   24.         # compute a node-url from the existing options, put in self['node-url']
   25.         if self['node-directory']:
   26.             if sys.platform == 'win32' and self['node-directory'] == '~/.tahoe':
   27.                 from allmydata.windows import registry
   28.                 self['node-directory'] = registry.get_base_dir_path()
   29.             else:
   30.                 self['node-directory'] = os.path.expanduser(self['node-directory'])
   31.         if self['node-url']:
   32.             if (not isinstance(self['node-url'], basestring)
   33.                 or not NODEURL_RE.match(self['node-url'])):
   34.                 msg = ("--node-url is required to be a string and look like "
   35.                        "\"http://HOSTNAMEORADDR:PORT\", not: %r" %
   36.                        (self['node-url'],))
   37.                 raise usage.UsageError(msg)
   38.         else:
   39.             node_url_file = os.path.join(self['node-directory'], "node.url")
   40.             self['node-url'] = open(node_url_file, "r").read().strip()
   41.         if self['node-url'][-1] != "/":
   42.             self['node-url'] += "/"
   43. 
   44.         aliases = get_aliases(self['node-directory'])
   45.         if self['dir-cap']:
   46.             aliases["tahoe"] = self['dir-cap']
   47.         self.aliases = aliases # maps alias name to dircap
   48. 
   49. 
   50. class MakeDirectoryOptions(VDriveOptions):
   51.     def parseArgs(self, where=""):
   52.         self.where = where
   53.     longdesc = """Create a new directory, either unlinked or as a subdirectory."""
   54. 
   55. class AddAliasOptions(VDriveOptions):
   56.     def parseArgs(self, alias, cap):
   57.         self.alias = alias
   58.         self.cap = cap
   59. 
   60.     def getSynopsis(self):
   61.         return "%s add-alias ALIAS DIRCAP" % (os.path.basename(sys.argv[0]),)
   62. 
   63.     longdesc = """Add a new alias for an existing directory."""
   64. 
   65. class CreateAliasOptions(VDriveOptions):
   66.     def parseArgs(self, alias):
   67.         self.alias = alias
   68. 
   69.     def getSynopsis(self):
   70.         return "%s create-alias ALIAS" % (os.path.basename(sys.argv[0]),)
   71. 
   72.     longdesc = """Creates a new directory and adds an alias for it."""
   73. 
   74. class ListAliasOptions(VDriveOptions):
   75.     longdesc = """Displays a table of all configured aliases."""
   76. 
   77. class ListOptions(VDriveOptions):
   78.     optFlags = [
   79.         ("long", "l", "Use long format: show file sizes, and timestamps"),
   80.         ("uri", "u", "Show file/directory URIs"),
   81.         ("readonly-uri", None, "Show readonly file/directory URIs"),
   82.         ("classify", "F", "Append '/' to directory names, and '*' to mutable"),
   83.         ("json", None, "Show the raw JSON output"),
   84.         ]
   85.     def parseArgs(self, where=""):
   86.         self.where = where
   87. 
   88.     longdesc = """List the contents of some portion of the virtual drive."""
   89. 
   90. class GetOptions(VDriveOptions):
   91.     def parseArgs(self, arg1, arg2=None):
   92.         # tahoe get FOO |less            # write to stdout
   93.         # tahoe get tahoe:FOO |less      # same
   94.         # tahoe get FOO bar              # write to local file
   95.         # tahoe get tahoe:FOO bar        # same
   96. 
   97.         self.from_file = arg1
   98.         self.to_file = arg2
   99.         if self.to_file == "-":
  100.             self.to_file = None
  101. 
  102.     def getSynopsis(self):
  103.         return "%s get VDRIVE_FILE LOCAL_FILE" % (os.path.basename(sys.argv[0]),)
  104. 
  105.     longdesc = """Retrieve a file from the virtual drive and write it to the
  106.     local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file
  107.     will be written to stdout."""
  108. 
  109.     def getUsage(self, width=None):
  110.         t = VDriveOptions.getUsage(self, width)
  111.         t += """
  112. Examples:
  113.  % tahoe get FOO |less            # write to stdout
  114.  % tahoe get tahoe:FOO |less      # same
  115.  % tahoe get FOO bar              # write to local file
  116.  % tahoe get tahoe:FOO bar        # same
  117. """
  118.         return t
  119. 
  120. class PutOptions(VDriveOptions):
  121.     optFlags = [
  122.         ("mutable", "m", "Create a mutable file instead of an immutable one."),
  123.         ]
  124. 
  125.     def parseArgs(self, arg1=None, arg2=None):
  126.         # cat FILE | tahoe put           # create unlinked file from stdin
  127.         # cat FILE | tahoe put -         # same
  128.         # tahoe put bar                  # create unlinked file from local 'bar'
  129.         # cat FILE | tahoe put - FOO     # create tahoe:FOO from stdin
  130.         # tahoe put bar FOO              # copy local 'bar' to tahoe:FOO
  131.         # tahoe put bar tahoe:FOO        # same
  132. 
  133.         if arg1 is not None and arg2 is not None:
  134.             self.from_file = arg1
  135.             self.to_file = arg2
  136.         elif arg1 is not None and arg2 is None:
  137.             self.from_file = arg1 # might be "-"
  138.             self.to_file = None
  139.         else:
  140.             self.from_file = None
  141.             self.to_file = None
  142.         if self.from_file == "-":
  143.             self.from_file = None
  144. 
  145.     def getSynopsis(self):
  146.         return "%s put LOCAL_FILE VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
  147. 
  148.     longdesc = """Put a file into the virtual drive (copying the file's
  149.     contents from the local filesystem). If VDRIVE_FILE is missing, upload
  150.     the file but do not link it into a directory: prints the new filecap to
  151.     stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin.
  152.     VDRIVE_FILE is assumed to start with tahoe: unless otherwise specified."""
  153. 
  154.     def getUsage(self, width=None):
  155.         t = VDriveOptions.getUsage(self, width)
  156.         t += """
  157. Examples:
  158.  % cat FILE | tahoe put                # create unlinked file from stdin
  159.  % cat FILE | tahoe -                  # same
  160.  % tahoe put bar                       # create unlinked file from local 'bar'
  161.  % cat FILE | tahoe put - FOO          # create tahoe:FOO from stdin
  162.  % tahoe put bar FOO                   # copy local 'bar' to tahoe:FOO
  163.  % tahoe put bar tahoe:FOO             # same
  164.  % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place
  165. """
  166.         return t
  167. 
  168. class CpOptions(VDriveOptions):
  169.     optFlags = [
  170.         ("recursive", "r", "Copy source directory recursively."),
  171.         ("verbose", "v", "Be noisy about what is happening."),
  172.         ("caps-only", None,
  173.          "When copying to local files, write out filecaps instead of actual "
  174.          "data. (only useful for debugging and tree-comparison purposes)"),
  175.         ]
  176.     def parseArgs(self, *args):
  177.         if len(args) < 2:
  178.             raise usage.UsageError("cp requires at least two arguments")
  179.         self.sources = args[:-1]
  180.         self.destination = args[-1]
  181.     def getSynopsis(self):
  182.         return "Usage: tahoe [options] cp FROM.. TO"
  183.     longdesc = """
  184.     Use 'tahoe cp' to copy files between a local filesystem and a Tahoe
  185.     virtual filesystem. Any FROM/TO arguments that begin with an alias
  186.     indicate Tahoe-side files, and arguments which do not indicate local
  187.     files. Directories will be copied recursively. New Tahoe-side directories
  188.     will be created when necessary. Assuming that you have previously set up
  189.     an alias 'home' with 'tahoe create-alias home', here are some examples:
  190. 
  191.     tahoe cp ~/foo.txt home:  # creates tahoe-side home:foo.txt
  192. 
  193.     tahoe cp ~/foo.txt /tmp/bar.txt home:  # copies two files to home:
  194. 
  195.     tahoe cp ~/Pictures home:stuff/my-pictures  # copies directory recursively
  196. 
  197.     You can also use a dircap as either FROM or TO target:
  198. 
  199.     tahoe cp URI:DIR2-RO:j74uhg25nwdpjpacl6rkat2yhm:kav7ijeft5h7r7rxdp5bgtlt3viv32yabqajkrdykozia5544jqa/wiki.html ./   # copy Zooko's wiki page to a local file
  200. 
  201.     This command still has some limitations: symlinks, special files (device
  202.     nodes, named pipes), and non-ASCII filenames are not handled very well.
  203.     Arguments should probably not have trailing slashes. 'tahoe cp' does not
  204.     behave as much like /bin/cp as you would wish, especially with respect to
  205.     trailing slashes.
  206.     """
  207. 
  208. class RmOptions(VDriveOptions):
  209.     def parseArgs(self, where):
  210.         self.where = where
  211. 
  212.     def getSynopsis(self):
  213.         return "%s rm VDRIVE_FILE" % (os.path.basename(sys.argv[0]),)
  214. 
  215. class MvOptions(VDriveOptions):
  216.     def parseArgs(self, frompath, topath):
  217.         self.from_file = frompath
  218.         self.to_file = topath
  219. 
  220.     def getSynopsis(self):
  221.         return "%s mv FROM TO" % (os.path.basename(sys.argv[0]),)
  222.     longdesc = """
  223.     Use 'tahoe mv' to move files that are already on the grid elsewhere on the grid, e.g., 'tahoe mv alias:some_file alias:new_file'.
  224. 
  225.     If moving a remote file into a remote directory, you'll need to append a '/' to the name of the remote directory, e.g., 'tahoe mv tahoe:file1 tahoe:dir/', not 'tahoe mv tahoe:file1 tahoe:dir'.
  226. 
  227.     Note that it is not possible to use this command to move local files to the grid -- use 'tahoe cp' for that.
  228.     """
  229. 
  230. class LnOptions(VDriveOptions):
  231.     def parseArgs(self, frompath, topath):
  232.         self.from_file = frompath
  233.         self.to_file = topath
  234. 
  235.     def getSynopsis(self):
  236.         return "%s ln FROM TO" % (os.path.basename(sys.argv[0]),)
  237. 
  238. class BackupConfigurationError(Exception):
  239.     pass
  240. 
  241. class BackupOptions(VDriveOptions):
  242.     optFlags = [
  243.         ("verbose", "v", "Be noisy about what is happening."),
  244.         ("ignore-timestamps", None, "Do not use backupdb timestamps to decide if a local file is unchanged."),
  245.         ]
  246. 
  247.     vcs_patterns = ('CVS', 'RCS', 'SCCS', '.git', '.gitignore', '.cvsignore', '.svn',
  248.                    '.arch-ids','{arch}', '=RELEASE-ID', '=meta-update', '=update',
  249.                    '.bzr', '.bzrignore', '.bzrtags', '.hg', '.hgignore', '_darcs')
  250. 
  251.     def __init__(self):
  252.         super(BackupOptions, self).__init__()
  253.         self['exclude'] = set()
  254. 
  255.     def parseArgs(self, localdir, topath):
  256.         self.from_dir = localdir
  257.         self.to_dir = topath
  258. 
  259.     def getSynopsis(Self):
  260.         return "%s backup FROM ALIAS:TO" % os.path.basename(sys.argv[0])
  261. 
  262.     def opt_exclude(self, pattern):
  263.         """Ignore files matching a glob pattern. You may give multiple
  264.         '--exclude' options."""
  265.         g = pattern.strip()
  266.         if g:
  267.             exclude = self['exclude']
  268.             exclude.add(g)
  269. 
  270.     def opt_exclude_from(self, filepath):
  271.         """Ignore file matching glob patterns listed in file, one per
  272.         line."""
  273.         try:
  274.             exclude_file = file(filepath)
  275.         except:
  276.             raise BackupConfigurationError('Error opening exclude file %r.' % filepath)
  277.         try:
  278.             for line in exclude_file:
  279.                 self.opt_exclude(line)
  280.         finally:
  281.             exclude_file.close()
  282. 
  283.     def opt_exclude_vcs(self):
  284.         """Exclude files and directories used by following version
  285.         control systems: 'CVS', 'RCS', 'SCCS', 'SVN', 'Arch',
  286.         'Bazaar', 'Mercurial', and 'Darcs'."""
  287.         for pattern in self.vcs_patterns:
  288.             self.opt_exclude(pattern)
  289. 
  290.     def filter_listdir(self, listdir):
  291.         """Yields non-excluded childpaths in path."""
  292.         exclude = self['exclude']
  293.         exclude_regexps = [re.compile(fnmatch.translate(pat)) for pat in exclude]
  294.         for filename in listdir:
  295.             for regexp in exclude_regexps:
  296.                 if regexp.match(filename):
  297.                     break
  298.             else:
  299.                 yield filename
  300. 
  301.     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'."""
  302. 
  303. class WebopenOptions(VDriveOptions):
  304.     def parseArgs(self, where=''):
  305.         self.where = where
  306. 
  307.     def getSynopsis(self):
  308.         return "%s webopen [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
  309. 
  310.     longdesc = """Opens a webbrowser to the contents of some portion of the virtual drive. When called without arguments, opens to the Welcome page."""
  311. 
  312. class ManifestOptions(VDriveOptions):
  313.     optFlags = [
  314.         ("storage-index", "s", "Only print storage index strings, not pathname+cap"),
  315.         ("verify-cap", None, "Only print verifycap, not pathname+cap"),
  316.         ("repair-cap", None, "Only print repaircap, not pathname+cap"),
  317.         ("raw", "r", "Display raw JSON data instead of parsed"),
  318.         ]
  319.     def parseArgs(self, where=''):
  320.         self.where = where
  321. 
  322.     def getSynopsis(self):
  323.         return "%s manifest [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
  324. 
  325.     longdesc = """Print a list of all files/directories reachable from the given starting point."""
  326. 
  327. class StatsOptions(VDriveOptions):
  328.     optFlags = [
  329.         ("raw", "r", "Display raw JSON data instead of parsed"),
  330.         ]
  331.     def parseArgs(self, where=''):
  332.         self.where = where
  333. 
  334.     def getSynopsis(self):
  335.         return "%s stats [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
  336. 
  337.     longdesc = """Print statistics about of all files/directories reachable from the given starting point."""
  338. 
  339. class CheckOptions(VDriveOptions):
  340.     optFlags = [
  341.         ("raw", None, "Display raw JSON data instead of parsed"),
  342.         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
  343.         ("repair", None, "Automatically repair any problems found"),
  344.         ("add-lease", None, "Add/renew lease on all shares"),
  345.         ]
  346.     def parseArgs(self, where=''):
  347.         self.where = where
  348. 
  349.     def getSynopsis(self):
  350.         return "%s check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
  351. 
  352.     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."""
  353. 
  354. class DeepCheckOptions(VDriveOptions):
  355.     optFlags = [
  356.         ("raw", None, "Display raw JSON data instead of parsed"),
  357.         ("verify", None, "Verify all hashes, instead of merely querying share presence"),
  358.         ("repair", None, "Automatically repair any problems found"),
  359.         ("add-lease", None, "Add/renew lease on all shares"),
  360.         ("verbose", "v", "Be noisy about what is happening."),
  361.         ]
  362.     def parseArgs(self, where=''):
  363.         self.where = where
  364. 
  365.     def getSynopsis(self):
  366.         return "%s deep-check [ALIAS:PATH]" % (os.path.basename(sys.argv[0]),)
  367. 
  368.     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."""
  369. 
  370. subCommands = [
  371.     ["mkdir", None, MakeDirectoryOptions, "Create a new directory"],
  372.     ["add-alias", None, AddAliasOptions, "Add a new alias cap"],
  373.     ["create-alias", None, CreateAliasOptions, "Create a new alias cap"],
  374.     ["list-aliases", None, ListAliasOptions, "List all alias caps"],
  375.     ["ls", None, ListOptions, "List a directory"],
  376.     ["get", None, GetOptions, "Retrieve a file from the virtual drive."],
  377.     ["put", None, PutOptions, "Upload a file into the virtual drive."],
  378.     ["cp", None, CpOptions, "Copy one or more files."],
  379.     ["rm", None, RmOptions, "Unlink a file or directory in the virtual drive."],
  380.     ["mv", None, MvOptions, "Move a file within the virtual drive."],
  381.     ["ln", None, LnOptions, "Make an additional link to an existing file."],
  382.     ["backup", None, BackupOptions, "Make target dir look like local dir."],
  383.     ["webopen", None, WebopenOptions, "Open a webbrowser to the root_dir"],
  384.     ["manifest", None, ManifestOptions, "List all files/dirs in a subtree"],
  385.     ["stats", None, StatsOptions, "Print statistics about all files/dirs in a subtree"],
  386.     ["check", None, CheckOptions, "Check a single file or directory"],
  387.     ["deep-check", None, DeepCheckOptions, "Check all files/directories reachable from a starting point"],
  388.     ]
  389. 
  390. def mkdir(options):
  391.     from allmydata.scripts import tahoe_mkdir
  392.     rc = tahoe_mkdir.mkdir(options)
  393.     return rc
  394. 
  395. def add_alias(options):
  396.     from allmydata.scripts import tahoe_add_alias
  397.     rc = tahoe_add_alias.add_alias(options)
  398.     return rc
  399. 
  400. def create_alias(options):
  401.     from allmydata.scripts import tahoe_add_alias
  402.     rc = tahoe_add_alias.create_alias(options)
  403.     return rc
  404. 
  405. def list_aliases(options):
  406.     from allmydata.scripts import tahoe_add_alias
  407.     rc = tahoe_add_alias.list_aliases(options)
  408.     return rc
  409. 
  410. def list(options):
  411.     from allmydata.scripts import tahoe_ls
  412.     rc = tahoe_ls.list(options)
  413.     return rc
  414. 
  415. def get(options):
  416.     from allmydata.scripts import tahoe_get
  417.     rc = tahoe_get.get(options)
  418.     if rc == 0:
  419.         if options.to_file is None:
  420.             # be quiet, since the file being written to stdout should be
  421.             # proof enough that it worked, unless the user is unlucky
  422.             # enough to have picked an empty file
  423.             pass
  424.         else:
  425.             print >>options.stderr, "%s retrieved and written to %s" % \
  426.                   (options.from_file, options.to_file)
  427.     return rc
  428. 
  429. def put(options):
  430.     from allmydata.scripts import tahoe_put
  431.     rc = tahoe_put.put(options)
  432.     return rc
  433. 
  434. def cp(options):
  435.     from allmydata.scripts import tahoe_cp
  436.     rc = tahoe_cp.copy(options)
  437.     return rc
  438. 
  439. def rm(options):
  440.     from allmydata.scripts import tahoe_rm
  441.     rc = tahoe_rm.rm(options)
  442.     return rc
  443. 
  444. def mv(options):
  445.     from allmydata.scripts import tahoe_mv
  446.     rc = tahoe_mv.mv(options, mode="move")
  447.     return rc
  448. 
  449. def ln(options):
  450.     from allmydata.scripts import tahoe_mv
  451.     rc = tahoe_mv.mv(options, mode="link")
  452.     return rc
  453. 
  454. def backup(options):
  455.     from allmydata.scripts import tahoe_backup
  456.     rc = tahoe_backup.backup(options)
  457.     return rc
  458. 
  459. def webopen(options, opener=None):
  460.     from allmydata.scripts import tahoe_webopen
  461.     rc = tahoe_webopen.webopen(options, opener=opener)
  462.     return rc
  463. 
  464. def manifest(options):
  465.     from allmydata.scripts import tahoe_manifest
  466.     rc = tahoe_manifest.manifest(options)
  467.     return rc
  468. 
  469. def stats(options):
  470.     from allmydata.scripts import tahoe_manifest
  471.     rc = tahoe_manifest.stats(options)
  472.     return rc
  473. 
  474. def check(options):
  475.     from allmydata.scripts import tahoe_check
  476.     rc = tahoe_check.check(options)
  477.     return rc
  478. 
  479. def deepcheck(options):
  480.     from allmydata.scripts import tahoe_check
  481.     rc = tahoe_check.deepcheck(options)
  482.     return rc
  483. 
  484. dispatch = {
  485.     "mkdir": mkdir,
  486.     "add-alias": add_alias,
  487.     "create-alias": create_alias,
  488.     "list-aliases": list_aliases,
  489.     "ls": list,
  490.     "get": get,
  491.     "put": put,
  492.     "cp": cp,
  493.     "rm": rm,
  494.     "mv": mv,
  495.     "ln": ln,
  496.     "backup": backup,
  497.     "webopen": webopen,
  498.     "manifest": manifest,
  499.     "stats": stats,
  500.     "check": check,
  501.     "deep-check": deepcheck,
  502.     }