]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/common.py
CLI: tolerate caps-from-future in unused aliases. Closes #1643.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / common.py
1
2 import os, sys, urllib
3 import codecs
4 from twisted.python import usage
5 from allmydata.util.assertutil import precondition
6 from allmydata.util.encodingutil import unicode_to_url, quote_output, argv_to_abspath
7 from allmydata.util.fileutil import abspath_expanduser_unicode
8
9
10 _default_nodedir = None
11 if sys.platform == 'win32':
12     from allmydata.windows import registry
13     path = registry.get_base_dir_path()
14     if path:
15         precondition(isinstance(path, unicode), path)
16         _default_nodedir = abspath_expanduser_unicode(path)
17
18 if _default_nodedir is None:
19     path = abspath_expanduser_unicode(u"~/.tahoe")
20     precondition(isinstance(path, unicode), path)
21     _default_nodedir = path
22
23 def get_default_nodedir():
24     return _default_nodedir
25
26
27 class BaseOptions(usage.Options):
28     def __init__(self):
29         super(BaseOptions, self).__init__()
30         self.command_name = os.path.basename(sys.argv[0])
31         if self.command_name == 'trial':
32             self.command_name = 'tahoe'
33
34     # Only allow "tahoe --version", not e.g. "tahoe start --version"
35     def opt_version(self):
36         raise usage.UsageError("--version not allowed on subcommands")
37
38 class BasedirOptions(BaseOptions):
39     default_nodedir = _default_nodedir
40
41     optParameters = [
42         ["basedir", "C", None, "Same as --node-directory (default %s)."
43          % get_default_nodedir()],
44     ]
45
46     def parseArgs(self, basedir=None):
47         if self.parent['node-directory'] and self['basedir']:
48             raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) options cannot both be used.")
49         if self.parent['node-directory'] and basedir:
50             raise usage.UsageError("The --node-directory (or -d) option and a basedir argument cannot both be used.")
51         if self['basedir'] and basedir:
52             raise usage.UsageError("The --basedir (or -C) option and a basedir argument cannot both be used.")
53
54         if basedir:
55             b = argv_to_abspath(basedir)
56         elif self['basedir']:
57             b = argv_to_abspath(self['basedir'])
58         elif self.parent['node-directory']:
59             b = argv_to_abspath(self.parent['node-directory'])
60         elif self.default_nodedir:
61             b = self.default_nodedir
62         else:
63             raise usage.UsageError("No default basedir available, you must provide one with --node-directory, --basedir, or a basedir argument")
64         self['basedir'] = b
65
66     def postOptions(self):
67         if not self['basedir']:
68             raise usage.UsageError("A base directory for the node must be provided.")
69
70
71 DEFAULT_ALIAS = u"tahoe"
72
73
74 def get_aliases(nodedir):
75     aliases = {}
76     aliasfile = os.path.join(nodedir, "private", "aliases")
77     rootfile = os.path.join(nodedir, "private", "root_dir.cap")
78     try:
79         f = open(rootfile, "r")
80         rootcap = f.read().strip()
81         if rootcap:
82             aliases[DEFAULT_ALIAS] = rootcap
83     except EnvironmentError:
84         pass
85     try:
86         f = codecs.open(aliasfile, "r", "utf-8")
87         for line in f.readlines():
88             line = line.strip()
89             if line.startswith("#") or not line:
90                 continue
91             name, cap = line.split(u":", 1)
92             # normalize it: remove http: prefix, urldecode
93             cap = cap.strip().encode('utf-8')
94             aliases[name] = cap
95     except EnvironmentError:
96         pass
97     return aliases
98
99 class DefaultAliasMarker:
100     pass
101
102 pretend_platform_uses_lettercolon = False # for tests
103 def platform_uses_lettercolon_drivename():
104     if ("win32" in sys.platform.lower()
105         or "cygwin" in sys.platform.lower()
106         or pretend_platform_uses_lettercolon):
107         return True
108     return False
109
110
111 class TahoeError(Exception):
112     def __init__(self, msg):
113         Exception.__init__(self, msg)
114         self.msg = msg
115
116     def display(self, err):
117         print >>err, self.msg
118
119
120 class UnknownAliasError(TahoeError):
121     def __init__(self, msg):
122         TahoeError.__init__(self, "error: " + msg)
123
124
125 def get_alias(aliases, path_unicode, default):
126     """
127     Transform u"work:path/filename" into (aliases[u"work"], u"path/filename".encode('utf-8')).
128     If default=None, then an empty alias is indicated by returning
129     DefaultAliasMarker. We special-case strings with a recognized cap URI
130     prefix, to make it easy to access specific files/directories by their
131     caps.
132     If the transformed alias is either not found in aliases, or is blank
133     and default is not found in aliases, an UnknownAliasError is
134     raised.
135     """
136     precondition(isinstance(path_unicode, unicode), path_unicode)
137
138     from allmydata import uri
139     path = path_unicode.encode('utf-8').strip(" ")
140     if uri.has_uri_prefix(path):
141         # We used to require "URI:blah:./foo" in order to get a subpath,
142         # stripping out the ":./" sequence. We still allow that for compatibility,
143         # but now also allow just "URI:blah/foo".
144         sep = path.find(":./")
145         if sep != -1:
146             return path[:sep], path[sep+3:]
147         sep = path.find("/")
148         if sep != -1:
149             return path[:sep], path[sep+1:]
150         return path, ""
151     colon = path.find(":")
152     if colon == -1:
153         # no alias
154         if default == None:
155             return DefaultAliasMarker, path
156         if default not in aliases:
157             raise UnknownAliasError("No alias specified, and the default %s alias doesn't exist. "
158                                     "To create it, use 'tahoe create-alias %s'."
159                                     % (quote_output(default), quote_output(default, quotemarks=False)))
160         return uri.from_string_dirnode(aliases[default]).to_string(), path
161     if colon == 1 and default is None and platform_uses_lettercolon_drivename():
162         # treat C:\why\must\windows\be\so\weird as a local path, not a tahoe
163         # file in the "C:" alias
164         return DefaultAliasMarker, path
165
166     # decoding must succeed because path is valid UTF-8 and colon & space are ASCII
167     alias = path[:colon].decode('utf-8')
168     if u"/" in alias:
169         # no alias, but there's a colon in a dirname/filename, like
170         # "foo/bar:7"
171         if default == None:
172             return DefaultAliasMarker, path
173         if default not in aliases:
174             raise UnknownAliasError("No alias specified, and the default %s alias doesn't exist. "
175                                     "To create it, use 'tahoe create-alias %s'."
176                                     % (quote_output(default), quote_output(default, quotemarks=False)))
177         return uri.from_string_dirnode(aliases[default]).to_string(), path
178     if alias not in aliases:
179         raise UnknownAliasError("Unknown alias %s, please create it with 'tahoe add-alias' or 'tahoe create-alias'." %
180                                 quote_output(alias))
181     return uri.from_string_dirnode(aliases[alias]).to_string(), path[colon+1:]
182
183 def escape_path(path):
184     segments = path.split("/")
185     return "/".join([urllib.quote(unicode_to_url(s)) for s in segments])