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