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