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