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