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