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