]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/root.py
webish: complete rewrite, break into smaller pieces, auto-create directories, improve...
[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
16 from allmydata.interfaces import IFileNode
17 from allmydata.web import filenode, directory, unlinked, status
18 from allmydata.web.common import abbreviate_size, IClient, getxmlfile, \
19      WebError, get_arg
20
21
22
23 class URIHandler(rend.Page):
24     # I live at /uri . There are several operations defined on /uri itself,
25     # mostly involed with creation of unlinked files and directories.
26
27     def renderHTTP(self, ctx):
28         request = IRequest(ctx)
29
30         # if we were using regular twisted.web Resources (and the regular
31         # twisted.web.server.Request object) then we could implement
32         # render_PUT and render_GET. But Nevow's request handler
33         # (NevowRequest.gotPageContext) goes directly to renderHTTP. Copy
34         # some code from the Resource.render method that Nevow bypasses, to
35         # do the same thing.
36         m = getattr(self, 'render_' + request.method, None)
37         if not m:
38             from twisted.web.server import UnsupportedMethod
39             raise UnsupportedMethod(getattr(self, 'allowedMethods', ()))
40         return m(ctx)
41
42     def render_GET(self, ctx):
43         req = IRequest(ctx)
44         uri = get_arg(req, "uri", None)
45         if uri is None:
46             raise WebError("GET /uri requires uri=")
47         there = url.URL.fromContext(ctx)
48         there = there.clear("uri")
49         # I thought about escaping the childcap that we attach to the URL
50         # here, but it seems that nevow does that for us.
51         there = there.child(uri)
52         return there
53
54     def render_PUT(self, ctx):
55         req = IRequest(ctx)
56         # either "PUT /uri" to create an unlinked file, or
57         # "PUT /uri?t=mkdir" to create an unlinked directory
58         t = get_arg(req, "t", "").strip()
59         if t == "":
60             mutable = bool(get_arg(req, "mutable", "").strip())
61             if mutable:
62                 return unlinked.PUTUnlinkedSSK(ctx)
63             else:
64                 return unlinked.PUTUnlinkedCHK(ctx)
65         if t == "mkdir":
66             return unlinked.PUTUnlinkedCreateDirectory(ctx)
67         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
68                   "and POST?t=mkdir")
69         raise WebError(errmsg, http.BAD_REQUEST)
70
71     def render_POST(self, ctx):
72         # "POST /uri?t=upload&file=newfile" to upload an
73         # unlinked file or "POST /uri?t=mkdir" to create a
74         # new directory
75         req = IRequest(ctx)
76         t = get_arg(req, "t", "").strip()
77         if t in ("", "upload"):
78             mutable = bool(get_arg(req, "mutable", "").strip())
79             if mutable:
80                 return unlinked.POSTUnlinkedSSK(ctx)
81             else:
82                 return unlinked.POSTUnlinkedCHK(ctx)
83         if t == "mkdir":
84             return unlinked.POSTUnlinkedCreateDirectory(ctx)
85         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
86                   "and POST?t=mkdir")
87         raise WebError(errmsg, http.BAD_REQUEST)
88
89     def childFactory(self, ctx, name):
90         # 'name' is expected to be a URI
91         client = IClient(ctx)
92         try:
93             node = client.create_node_from_uri(name)
94             return directory.make_handler_for(node)
95         except (TypeError, AssertionError):
96             raise WebError("'%s' is not a valid file- or directory- cap"
97                            % name)
98
99 class FileHandler(rend.Page):
100     # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
101     # file can be downloaded correctly by tools like "wget".
102
103     def childFactory(self, ctx, name):
104         req = IRequest(ctx)
105         if req.method not in ("GET", "HEAD"):
106             raise WebError("/file can only be used with GET or HEAD")
107         # 'name' must be a file URI
108         client = IClient(ctx)
109         try:
110             node = client.create_node_from_uri(name)
111         except (TypeError, AssertionError):
112             raise WebError("'%s' is not a valid file- or directory- cap"
113                            % name)
114         if not IFileNode.providedBy(node):
115             raise WebError("'%s' is not a file-cap" % name)
116         return filenode.FileNodeDownloadHandler(node)
117
118     def renderHTTP(self, ctx):
119         raise WebError("/file must be followed by a file-cap and a name",
120                        http.NOT_FOUND)
121
122 class Root(rend.Page):
123
124     addSlash = True
125     docFactory = getxmlfile("welcome.xhtml")
126
127     child_uri = URIHandler()
128     child_file = FileHandler()
129     child_named = FileHandler()
130
131     child_webform_css = webform.defaultCSS
132     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
133
134     child_provisioning = provisioning.ProvisioningTool()
135     child_status = status.Status()
136     child_helper_status = status.HelperStatus()
137     child_statistics = status.Statistics()
138
139     def data_version(self, ctx, data):
140         return get_package_versions_string()
141     def data_import_path(self, ctx, data):
142         return str(allmydata)
143     def data_my_nodeid(self, ctx, data):
144         return idlib.nodeid_b2a(IClient(ctx).nodeid)
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", "%s %s" % (idlib.nodeid_b2a(nodeid),
216                                            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