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