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