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