]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/common.py
Basedir/node directory option improvements for ticket798 branch. addresses #188,...
[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     # unit tests can override these to point at StringIO instances
29     stdin = sys.stdin
30     stdout = sys.stdout
31     stderr = sys.stderr
32
33     optFlags = [
34         ["quiet", "q", "Operate silently."],
35         ["version", "V", "Display version numbers and exit."],
36         ["version-and-path", None, "Display version numbers and paths to their locations and exit."],
37     ]
38     optParameters = [
39         ["node-directory", "d", None, "Specify which Tahoe node directory should be used." + (
40             _default_nodedir and (" [default for most commands: " + quote_output(_default_nodedir) + "]") or "")],
41     ]
42
43     def opt_version(self):
44         import allmydata
45         print >>self.stdout, allmydata.get_package_versions_string()
46         sys.exit(0)
47
48     def opt_version_and_path(self):
49         import allmydata
50         print >>self.stdout, allmydata.get_package_versions_string(show_paths=True)
51         sys.exit(0)
52
53
54 class BasedirMixin:
55     default_nodedir = _default_nodedir
56     allow_multiple = True
57
58     optParameters = [
59         ["basedir", "C", None, "Same as --node-directory."],
60     ]
61     optFlags = [
62         ["multiple", "m", "Specify multiple node directories at once"],
63     ]
64
65     def parseArgs(self, *args):
66         if self['node-directory'] and self['basedir']:
67             raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) "
68                                    "options cannot both be used.")
69
70         if self['node-directory'] or self['basedir']:
71             self.basedirs = [argv_to_abspath(self['node-directory'] or self['basedir'])]
72         else:
73             self.basedirs = []
74
75         if self.allow_multiple and self['multiple']:
76             self.basedirs.extend(map(argv_to_abspath, args))
77         else:
78             if len(args) > 0:
79                 self.basedirs.append(argv_to_abspath(args[0]))
80             if len(args) > 1:
81                 raise usage.UsageError("I wasn't expecting so many arguments." +
82                     (self.allow_multiple and
83                      " Use the --multiple option to specify more than one node directory." or ""))
84
85             if len(args) == 0 and self.default_nodedir and not self.basedirs:
86                 self.basedirs.append(self.default_nodedir)
87             elif len(args) > 0:
88                 self.basedirs.append(argv_to_abspath(args[0]))
89
90     def postOptions(self):
91         if not self.basedirs:
92             raise usage.UsageError("A base directory for the node must be provided.")
93         del self['basedir']
94         self['basedirs'] = self.basedirs
95
96
97 DEFAULT_ALIAS = u"tahoe"
98
99
100 def get_aliases(nodedir):
101     from allmydata import uri
102     aliases = {}
103     aliasfile = os.path.join(nodedir, "private", "aliases")
104     rootfile = os.path.join(nodedir, "private", "root_dir.cap")
105     try:
106         f = open(rootfile, "r")
107         rootcap = f.read().strip()
108         if rootcap:
109             aliases[DEFAULT_ALIAS] = uri.from_string_dirnode(rootcap).to_string()
110     except EnvironmentError:
111         pass
112     try:
113         f = codecs.open(aliasfile, "r", "utf-8")
114         for line in f.readlines():
115             line = line.strip()
116             if line.startswith("#") or not line:
117                 continue
118             name, cap = line.split(u":", 1)
119             # normalize it: remove http: prefix, urldecode
120             cap = cap.strip().encode('utf-8')
121             aliases[name] = uri.from_string_dirnode(cap).to_string()
122     except EnvironmentError:
123         pass
124     return aliases
125
126 class DefaultAliasMarker:
127     pass
128
129 pretend_platform_uses_lettercolon = False # for tests
130 def platform_uses_lettercolon_drivename():
131     if ("win32" in sys.platform.lower()
132         or "cygwin" in sys.platform.lower()
133         or pretend_platform_uses_lettercolon):
134         return True
135     return False
136
137
138 class TahoeError(Exception):
139     def __init__(self, msg):
140         Exception.__init__(self, msg)
141         self.msg = msg
142
143     def display(self, err):
144         print >>err, self.msg
145
146
147 class UnknownAliasError(TahoeError):
148     def __init__(self, msg):
149         TahoeError.__init__(self, "error: " + msg)
150
151
152 def get_alias(aliases, path_unicode, default):
153     """
154     Transform u"work:path/filename" into (aliases[u"work"], u"path/filename".encode('utf-8')).
155     If default=None, then an empty alias is indicated by returning
156     DefaultAliasMarker. We special-case strings with a recognized cap URI
157     prefix, to make it easy to access specific files/directories by their
158     caps.
159     If the transformed alias is either not found in aliases, or is blank
160     and default is not found in aliases, an UnknownAliasError is
161     raised.
162     """
163     precondition(isinstance(path_unicode, unicode), path_unicode)
164
165     from allmydata import uri
166     path = path_unicode.encode('utf-8').strip(" ")
167     if uri.has_uri_prefix(path):
168         # We used to require "URI:blah:./foo" in order to get a subpath,
169         # stripping out the ":./" sequence. We still allow that for compatibility,
170         # but now also allow just "URI:blah/foo".
171         sep = path.find(":./")
172         if sep != -1:
173             return path[:sep], path[sep+3:]
174         sep = path.find("/")
175         if sep != -1:
176             return path[:sep], path[sep+1:]
177         return path, ""
178     colon = path.find(":")
179     if colon == -1:
180         # no alias
181         if default == None:
182             return DefaultAliasMarker, path
183         if default not in aliases:
184             raise UnknownAliasError("No alias specified, and the default "
185                                     "'tahoe' alias doesn't exist. To create "
186                                     "it, use 'tahoe create-alias tahoe'.")
187         return aliases[default], path
188     if colon == 1 and default is None and platform_uses_lettercolon_drivename():
189         # treat C:\why\must\windows\be\so\weird as a local path, not a tahoe
190         # file in the "C:" alias
191         return DefaultAliasMarker, path
192
193     # decoding must succeed because path is valid UTF-8 and colon & space are ASCII
194     alias = path[:colon].decode('utf-8')
195     if u"/" in alias:
196         # no alias, but there's a colon in a dirname/filename, like
197         # "foo/bar:7"
198         if default == None:
199             return DefaultAliasMarker, path
200         if default not in aliases:
201             raise UnknownAliasError("No alias specified, and the default "
202                                     "'tahoe' alias doesn't exist. To create "
203                                     "it, use 'tahoe create-alias tahoe'.")
204         return aliases[default], path
205     if alias not in aliases:
206         raise UnknownAliasError("Unknown alias %s, please create it with 'tahoe add-alias' or 'tahoe create-alias'." %
207                                 quote_output(alias))
208     return aliases[alias], path[colon+1:]
209
210 def escape_path(path):
211     segments = path.split("/")
212     return "/".join([urllib.quote(unicode_to_url(s)) for s in segments])