]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/webish.py
ca86f61eb78bd69fe542568b68daaed4005878b6
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / webish.py
1
2 from twisted.application import service, strports
3 from twisted.web import static, resource, server, html
4 from twisted.python import util, log
5 from nevow import inevow, rend, loaders, appserver, url, tags as T
6 from allmydata.util import idlib
7 from allmydata.interfaces import IDownloadTarget#, IDownloader
8 from allmydata import upload
9 from zope.interface import implements, Interface
10 import urllib
11 from formless import annotate, webform
12
13 def getxmlfile(name):
14     return loaders.xmlfile(util.sibpath(__file__, "web/%s" % name))
15
16 class IClient(Interface):
17     pass
18
19 def get_downloader_service(ctx):
20     return IClient(ctx).getServiceNamed("downloader")
21 def get_uploader_service(ctx):
22     return IClient(ctx).getServiceNamed("uploader")
23 def get_vdrive_service(ctx):
24     return IClient(ctx).getServiceNamed("vdrive")
25
26 class Welcome(rend.Page):
27     addSlash = True
28     docFactory = getxmlfile("welcome.xhtml")
29
30     def data_queen_pburl(self, ctx, data):
31         return IClient(ctx).introducer_furl
32     def data_connected_to_queen(self, ctx, data):
33         if IClient(ctx).connected_to_vdrive:
34             return "yes"
35         return "no"
36     def data_num_peers(self, ctx, data):
37         #client = inevow.ISite(ctx)._client
38         client = IClient(ctx)
39         return len(client.get_all_peerids())
40     def data_num_connected_peers(self, ctx, data):
41         return len(IClient(ctx).get_all_peerids())
42
43     def data_peers(self, ctx, data):
44         d = []
45         client = IClient(ctx)
46         for nodeid in sorted(client.get_all_peerids()):
47             row = (idlib.b2a(nodeid),)
48             d.append(row)
49         return d
50
51     def render_row(self, ctx, data):
52         (nodeid_a,) = data
53         ctx.fillSlots("peerid", nodeid_a)
54         return ctx.tag
55
56     # this is a form where users can download files by URI
57
58     def bind_download(self, ctx):
59         uriarg = annotate.Argument("uri",
60                                    annotate.String("URI of file to download: "))
61         namearg = annotate.Argument("filename",
62                                     annotate.String("Filename to download as: "))
63         ctxarg = annotate.Argument("ctx", annotate.Context())
64         meth = annotate.Method(arguments=[uriarg, namearg, ctxarg],
65                                label="Download File by URI")
66         # buttons always use value=data.label
67         # MethodBindingRenderer uses value=(data.action or data.label)
68         return annotate.MethodBinding("download", meth, action="Download")
69
70     def download(self, uri, filename, ctx):
71         log.msg("webish downloading URI")
72         target = url.here.sibling("download_uri").add("uri", uri)
73         if filename:
74             target = target.add("filename", filename)
75         return target
76
77     def render_forms(self, ctx, data):
78         return webform.renderForms()
79
80
81 class Directory(rend.Page):
82     addSlash = True
83     docFactory = getxmlfile("directory.xhtml")
84
85     def __init__(self, dirnode, dirname):
86         self._dirnode = dirnode
87         self._dirname = dirname
88
89     def childFactory(self, ctx, name):
90         if name.startswith("freeform"): # ick
91             return None
92         if self._dirname == "/":
93             dirname = "/" + name
94         else:
95             dirname = self._dirname + "/" + name
96         d = self._dirnode.callRemote("get", name)
97         def _got_child(res):
98             if isinstance(res, str):
99                 dl = get_downloader_service(ctx)
100                 return Downloader(dl, name, res)
101             return Directory(res, dirname)
102         d.addCallback(_got_child)
103         return d
104
105     def render_title(self, ctx, data):
106         return ctx.tag["Directory of '%s':" % self._dirname]
107
108     def render_header(self, ctx, data):
109         return "Directory of '%s':" % self._dirname
110
111     def data_children(self, ctx, data):
112         d = self._dirnode.callRemote("list")
113         return d
114
115     def render_row(self, ctx, data):
116         name, target = data
117         if isinstance(target, str):
118             # file
119             dlurl = urllib.quote(name)
120             ctx.fillSlots("filename",
121                           T.a(href=dlurl)[html.escape(name)])
122             ctx.fillSlots("type", "FILE")
123             uri = target
124             dl_uri_url = url.root.child("download_uri").child(uri)
125             # add a filename= query argument to give it a Content-Type
126             dl_uri_url = dl_uri_url.add("filename", name)
127             ctx.fillSlots("uri", T.a(href=dl_uri_url)[html.escape(uri)])
128
129             # this creates a button which will cause our child__delete method
130             # to be invoked, which deletes the file and then redirects the
131             # browser back to this directory
132             del_url = url.here.child("_delete")
133             #del_url = del_url.add("uri", target)
134             del_url = del_url.add("name", name)
135             delete = T.form(action=del_url, method="post")[
136                 T.input(type='submit', value='del', name="del"),
137                 ]
138             ctx.fillSlots("delete", delete)
139         else:
140             # directory
141             subdir_url = urllib.quote(name)
142             ctx.fillSlots("filename",
143                           T.a(href=subdir_url)[html.escape(name)])
144             ctx.fillSlots("type", "DIR")
145             ctx.fillSlots("uri", "-")
146             ctx.fillSlots("delete", "-")
147         return ctx.tag
148
149     def render_forms(self, ctx, data):
150         return webform.renderForms()
151
152     def bind_upload(self, ctx):
153         """upload1"""
154         # Note: this comment is no longer accurate, as it reflects the older
155         # (apparently deprecated) formless.autocallable /
156         # annotate.TypedInterface approach.
157
158         # Each method gets a box. The string in the autocallable(action=)
159         # argument is put on the border of the box, as well as in the submit
160         # button. The top-most contents of the box are the method's
161         # docstring, if any. Each row contains a string for the argument
162         # followed by the argument's input box. If you do not provide an
163         # action= argument to autocallable, the method name is capitalized
164         # and used instead.
165         up = annotate.FileUpload(label="Choose a file to upload: ",
166                                  required=True,
167                                  requiredFailMessage="Do iT!")
168         contentsarg = annotate.Argument("contents", up)
169
170         ctxarg = annotate.Argument("ctx", annotate.Context())
171         meth = annotate.Method(arguments=[contentsarg, ctxarg],
172                                label="Upload File to this directory")
173         return annotate.MethodBinding("upload", meth, action="Upload")
174
175     def upload(self, contents, ctx):
176         # contents is a cgi.FieldStorage instance
177         log.msg("starting webish upload")
178
179         uploader = get_uploader_service(ctx)
180         d = uploader.upload(upload.Data(contents.value))
181         name = contents.filename
182         d.addCallback(lambda vid:
183                       self._dirnode.callRemote("add_file", name, vid))
184         def _done(res):
185             log.msg("webish upload complete")
186             return res
187         d.addCallback(_done)
188         return d
189         return url.here.add("results",
190                             "upload of '%s' complete!" % contents.filename)
191
192     def bind_mkdir(self, ctx):
193         """Make new directory 1"""
194         namearg = annotate.Argument("name",
195                                     annotate.String("New directory name: "))
196         meth = annotate.Method(arguments=[namearg], label="Make New Subdirectory")
197         return annotate.MethodBinding("mkdir", meth, action="Create Directory")
198
199     def mkdir(self, name):
200         """mkdir2"""
201         log.msg("making new webish directory")
202         d = self._dirnode.callRemote("add_directory", name)
203         def _done(res):
204             log.msg("webish mkdir complete")
205             return res
206         d.addCallback(_done)
207         return d
208
209     def child__delete(self, ctx):
210         # perform the delete, then redirect back to the directory page
211         args = inevow.IRequest(ctx).args
212         vdrive = get_vdrive_service(ctx)
213         d = vdrive.remove(self._dirnode, args["name"][0])
214         def _deleted(res):
215             return url.here.up()
216         d.addCallback(_deleted)
217         return d
218
219 class WebDownloadTarget:
220     implements(IDownloadTarget)
221     def __init__(self, req):
222         self._req = req
223     def open(self):
224         pass
225     def write(self, data):
226         self._req.write(data)
227     def close(self):
228         self._req.finish()
229     def fail(self):
230         self._req.finish()
231     def register_canceller(self, cb):
232         pass
233     def finish(self):
234         pass
235
236 class TypedFile(static.File):
237     # serve data from a named file, but using a Content-Type derived from a
238     # different filename
239     isLeaf = True
240     def __init__(self, path, requested_filename):
241         static.File.__init__(self, path)
242         gte = static.getTypeAndEncoding
243         self.type, self.encoding = gte(requested_filename,
244                                        self.contentTypes,
245                                        self.contentEncodings,
246                                        self.defaultType)
247
248 class Downloader(resource.Resource):
249     def __init__(self, downloader, name, uri):
250         self._downloader = downloader
251         self._name = name
252         self._uri = uri
253
254     def render(self, ctx):
255         req = inevow.IRequest(ctx)
256         gte = static.getTypeAndEncoding
257         type, encoding = gte(self._name,
258                              static.File.contentTypes,
259                              static.File.contentEncodings,
260                              defaultType="text/plain")
261         req.setHeader("content-type", type)
262         if encoding:
263             req.setHeader('content-encoding', encoding)
264
265         t = WebDownloadTarget(req)
266         #dl = IDownloader(ctx)
267         dl = self._downloader
268         dl.download(self._uri, t)
269         return server.NOT_DONE_YET
270
271
272
273 class Root(rend.Page):
274     def locateChild(self, ctx, segments):
275         if segments[0] == "download_uri":
276             req = inevow.IRequest(ctx)
277             dl = get_downloader_service(ctx)
278             filename = "unknown_filename"
279             if "filename" in req.args:
280                 filename = req.args["filename"][0]
281             if len(segments) > 1:
282                 # http://host/download_uri/URIGOESHERE
283                 uri = segments[1]
284             elif "uri" in req.args:
285                 # http://host/download_uri?uri=URIGOESHERE
286                 uri = req.args["uri"][0]
287             else:
288                 return rend.NotFound
289             child = Downloader(dl, filename, uri)
290             return child, ()
291         return rend.Page.locateChild(self, ctx, segments)
292
293     child_webform_css = webform.defaultCSS
294
295     child_welcome = Welcome()
296
297
298 class WebishServer(service.MultiService):
299     name = "webish"
300
301     def __init__(self, webport):
302         service.MultiService.__init__(self)
303         self.root = Root()
304         placeholder = static.Data("sorry, still initializing", "text/plain")
305         self.root.putChild("vdrive", placeholder)
306         self.root.putChild("", url.here.child("welcome"))#Welcome())
307                            
308         self.site = site = appserver.NevowSite(self.root)
309         s = strports.service(webport, site)
310         s.setServiceParent(self)
311         self.listener = s # stash it so the tests can query for the portnum
312
313     def startService(self):
314         service.MultiService.startService(self)
315         # to make various services available to render_* methods, we stash a
316         # reference to the client on the NevowSite. This will be available by
317         # adapting the 'context' argument to a special marker interface named
318         # IClient.
319         self.site.remember(self.parent, IClient)
320         # I thought you could do the same with an existing interface, but
321         # apparently 'ISite' does not exist
322         #self.site._client = self.parent
323
324     def set_root_dirnode(self, dirnode):
325         self.root.putChild("vdrive", Directory(dirnode, "/"))
326         # I tried doing it this way and for some reason it didn't seem to work
327         #print "REMEMBERING", self.site, dl, IDownloader
328         #self.site.remember(dl, IDownloader)
329