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