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