]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/root.py
webapi: pass client through constructor arguments, remove IClient, should make it...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / root.py
1 import time
2
3 from twisted.internet import address
4 from twisted.web import http
5 from nevow import rend, url, loaders, tags as T
6 from nevow.inevow import IRequest
7 from nevow.static import File as nevow_File # TODO: merge with static.File?
8 from nevow.util import resource_filename
9 from formless import webform
10
11 import allmydata # to display import path
12 from allmydata import get_package_versions_string
13 from allmydata import provisioning
14 from allmydata.util import idlib, log
15 from allmydata.interfaces import IFileNode
16 from allmydata.web import filenode, directory, unlinked, status, operations
17 from allmydata.web import reliability, storage
18 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
19      get_arg, RenderMixin
20
21
22 class URIHandler(RenderMixin, rend.Page):
23     # I live at /uri . There are several operations defined on /uri itself,
24     # mostly involved with creation of unlinked files and directories.
25
26     def __init__(self, client):
27         rend.Page.__init__(self, client)
28         self.client = client
29
30     def render_GET(self, ctx):
31         req = IRequest(ctx)
32         uri = get_arg(req, "uri", None)
33         if uri is None:
34             raise WebError("GET /uri requires uri=")
35         there = url.URL.fromContext(ctx)
36         there = there.clear("uri")
37         # I thought about escaping the childcap that we attach to the URL
38         # here, but it seems that nevow does that for us.
39         there = there.child(uri)
40         return there
41
42     def render_PUT(self, ctx):
43         req = IRequest(ctx)
44         # either "PUT /uri" to create an unlinked file, or
45         # "PUT /uri?t=mkdir" to create an unlinked directory
46         t = get_arg(req, "t", "").strip()
47         if t == "":
48             mutable = bool(get_arg(req, "mutable", "").strip())
49             if mutable:
50                 return unlinked.PUTUnlinkedSSK(req, self.client)
51             else:
52                 return unlinked.PUTUnlinkedCHK(req, self.client)
53         if t == "mkdir":
54             return unlinked.PUTUnlinkedCreateDirectory(req, self.client)
55         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
56                   "and POST?t=mkdir")
57         raise WebError(errmsg, http.BAD_REQUEST)
58
59     def render_POST(self, ctx):
60         # "POST /uri?t=upload&file=newfile" to upload an
61         # unlinked file or "POST /uri?t=mkdir" to create a
62         # new directory
63         req = IRequest(ctx)
64         t = get_arg(req, "t", "").strip()
65         if t in ("", "upload"):
66             mutable = bool(get_arg(req, "mutable", "").strip())
67             if mutable:
68                 return unlinked.POSTUnlinkedSSK(req, self.client)
69             else:
70                 return unlinked.POSTUnlinkedCHK(req, self.client)
71         if t == "mkdir":
72             return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
73         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
74                   "and POST?t=mkdir")
75         raise WebError(errmsg, http.BAD_REQUEST)
76
77     def childFactory(self, ctx, name):
78         # 'name' is expected to be a URI
79         try:
80             node = self.client.create_node_from_uri(name)
81             return directory.make_handler_for(node, self.client)
82         except (TypeError, AssertionError):
83             raise WebError("'%s' is not a valid file- or directory- cap"
84                            % name)
85
86 class FileHandler(rend.Page):
87     # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
88     # file can be downloaded correctly by tools like "wget".
89
90     def __init__(self, client):
91         rend.Page.__init__(self, client)
92         self.client = client
93
94     def childFactory(self, ctx, name):
95         req = IRequest(ctx)
96         if req.method not in ("GET", "HEAD"):
97             raise WebError("/file can only be used with GET or HEAD")
98         # 'name' must be a file URI
99         try:
100             node = self.client.create_node_from_uri(name)
101         except (TypeError, AssertionError):
102             raise WebError("'%s' is not a valid file- or directory- cap"
103                            % name)
104         if not IFileNode.providedBy(node):
105             raise WebError("'%s' is not a file-cap" % name)
106         return filenode.FileNodeDownloadHandler(self.client, node)
107
108     def renderHTTP(self, ctx):
109         raise WebError("/file must be followed by a file-cap and a name",
110                        http.NOT_FOUND)
111
112 class IncidentReporter(RenderMixin, rend.Page):
113     def render_POST(self, ctx):
114         req = IRequest(ctx)
115         log.msg(format="User reports incident through web page: %(details)s",
116                 details=get_arg(req, "details", ""),
117                 level=log.WEIRD, umid="LkD9Pw")
118         req.setHeader("content-type", "text/plain")
119         return "Thank you for your report!"
120
121 class NoReliability(rend.Page):
122     docFactory = loaders.xmlstr('''\
123 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
124   <head>
125     <title>AllMyData - Tahoe</title>
126     <link href="/webform_css" rel="stylesheet" type="text/css"/>
127     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
128   </head>
129   <body>
130   <h2>"Reliability" page not available</h2>
131   <p>Please install the python "NumPy" module to enable this page.</p>
132   </body>
133 </html>
134 ''')
135
136 class Root(rend.Page):
137
138     addSlash = True
139     docFactory = getxmlfile("welcome.xhtml")
140
141     def __init__(self, client):
142         rend.Page.__init__(self, client)
143         self.client = client
144         self.child_operations = operations.OphandleTable()
145
146         self.child_uri = URIHandler(client)
147         self.child_cap = URIHandler(client)
148
149         self.child_file = FileHandler(client)
150         self.child_named = FileHandler(client)
151         self.child_status = status.Status(client) # TODO: use client.history
152         self.child_statistics = status.Statistics(client.stats_provider)
153
154     def child_helper_status(self, ctx):
155         # the Helper isn't attached until after the Tub starts, so this child
156         # needs to created on each request
157         try:
158             helper = self.client.getServiceNamed("helper")
159         except KeyError:
160             helper = None
161         return status.HelperStatus(helper)
162
163     child_webform_css = webform.defaultCSS
164     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
165
166     child_provisioning = provisioning.ProvisioningTool()
167     if reliability.is_available():
168         child_reliability = reliability.ReliabilityTool()
169     else:
170         child_reliability = NoReliability()
171
172     child_report_incident = IncidentReporter()
173
174     def data_version(self, ctx, data):
175         return get_package_versions_string()
176     def data_import_path(self, ctx, data):
177         return str(allmydata)
178     def data_my_nodeid(self, ctx, data):
179         return idlib.nodeid_b2a(self.client.nodeid)
180     def data_my_nickname(self, ctx, data):
181         return self.client.nickname
182
183     def render_services(self, ctx, data):
184         ul = T.ul()
185         try:
186             ss = self.client.getServiceNamed("storage")
187             allocated_s = abbreviate_size(ss.allocated_size())
188             allocated = "about %s allocated" % allocated_s
189             reserved = "%s reserved" % abbreviate_size(ss.reserved_space)
190             ul[T.li["Storage Server: %s, %s" % (allocated, reserved)]]
191         except KeyError:
192             ul[T.li["Not running storage server"]]
193
194         try:
195             h = self.client.getServiceNamed("helper")
196             stats = h.get_stats()
197             active_uploads = stats["chk_upload_helper.active_uploads"]
198             ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
199         except KeyError:
200             ul[T.li["Not running helper"]]
201
202         return ctx.tag[ul]
203
204     def data_introducer_furl(self, ctx, data):
205         return self.client.introducer_furl
206     def data_connected_to_introducer(self, ctx, data):
207         if self.client.connected_to_introducer():
208             return "yes"
209         return "no"
210
211     def data_helper_furl(self, ctx, data):
212         try:
213             uploader = self.client.getServiceNamed("uploader")
214         except KeyError:
215             return None
216         furl, connected = uploader.get_helper_info()
217         return furl
218     def data_connected_to_helper(self, ctx, data):
219         try:
220             uploader = self.client.getServiceNamed("uploader")
221         except KeyError:
222             return "no" # we don't even have an Uploader
223         furl, connected = uploader.get_helper_info()
224         if connected:
225             return "yes"
226         return "no"
227
228     def data_known_storage_servers(self, ctx, data):
229         ic = self.client.introducer_client
230         servers = [c
231                    for c in ic.get_all_connectors().values()
232                    if c.service_name == "storage"]
233         return len(servers)
234
235     def data_connected_storage_servers(self, ctx, data):
236         ic = self.client.introducer_client
237         return len(ic.get_all_connections_for("storage"))
238
239     def data_services(self, ctx, data):
240         ic = self.client.introducer_client
241         c = [ (service_name, nodeid, rsc)
242               for (nodeid, service_name), rsc
243               in ic.get_all_connectors().items() ]
244         c.sort()
245         return c
246
247     def render_service_row(self, ctx, data):
248         (service_name, nodeid, rsc) = data
249         ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
250         ctx.fillSlots("nickname", rsc.nickname)
251         if rsc.rref:
252             rhost = rsc.remote_host
253             if nodeid == self.client.nodeid:
254                 rhost_s = "(loopback)"
255             elif isinstance(rhost, address.IPv4Address):
256                 rhost_s = "%s:%d" % (rhost.host, rhost.port)
257             else:
258                 rhost_s = str(rhost)
259             connected = "Yes: to " + rhost_s
260             since = rsc.last_connect_time
261         else:
262             connected = "No"
263             since = rsc.last_loss_time
264
265         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
266         ctx.fillSlots("connected", connected)
267         ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
268         ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
269                                                  time.localtime(rsc.announcement_time)))
270         ctx.fillSlots("version", rsc.version)
271         ctx.fillSlots("service_name", rsc.service_name)
272
273         return ctx.tag
274
275     def render_download_form(self, ctx, data):
276         # this is a form where users can download files by URI
277         form = T.form(action="uri", method="get",
278                       enctype="multipart/form-data")[
279             T.fieldset[
280             T.legend(class_="freeform-form-label")["Download a file"],
281             "URI to download: ",
282             T.input(type="text", name="uri"), " ",
283             "Filename to download as: ",
284             T.input(type="text", name="filename"), " ",
285             T.input(type="submit", value="Download!"),
286             ]]
287         return T.div[form]
288
289     def render_view_form(self, ctx, data):
290         # this is a form where users can download files by URI, or jump to a
291         # named directory
292         form = T.form(action="uri", method="get",
293                       enctype="multipart/form-data")[
294             T.fieldset[
295             T.legend(class_="freeform-form-label")["View a file or directory"],
296             "URI to view: ",
297             T.input(type="text", name="uri"), " ",
298             T.input(type="submit", value="View!"),
299             ]]
300         return T.div[form]
301
302     def render_upload_form(self, ctx, data):
303         # this is a form where users can upload unlinked files
304         form = T.form(action="uri", method="post",
305                       enctype="multipart/form-data")[
306             T.fieldset[
307             T.legend(class_="freeform-form-label")["Upload a file"],
308             "Choose a file: ",
309             T.input(type="file", name="file", class_="freeform-input-file"),
310             T.input(type="hidden", name="t", value="upload"),
311             " Mutable?:", T.input(type="checkbox", name="mutable"),
312             T.input(type="submit", value="Upload!"),
313             ]]
314         return T.div[form]
315
316     def render_mkdir_form(self, ctx, data):
317         # this is a form where users can create new directories
318         form = T.form(action="uri", method="post",
319                       enctype="multipart/form-data")[
320             T.fieldset[
321             T.legend(class_="freeform-form-label")["Create a directory"],
322             T.input(type="hidden", name="t", value="mkdir"),
323             T.input(type="hidden", name="redirect_to_result", value="true"),
324             T.input(type="submit", value="Create Directory!"),
325             ]]
326         return T.div[form]
327
328     def render_incident_button(self, ctx, data):
329         # this button triggers a foolscap-logging "incident"
330         form = T.form(action="report_incident", method="post",
331                       enctype="multipart/form-data")[
332             T.fieldset[
333             T.legend(class_="freeform-form-label")["Report an Incident"],
334             T.input(type="hidden", name="t", value="report-incident"),
335             "What went wrong?: ",
336             T.input(type="text", name="details"), " ",
337             T.input(type="submit", value="Report!"),
338             ]]
339         return T.div[form]