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
11 from formless import annotate, webform
14 return loaders.xmlfile(util.sibpath(__file__, "web/%s" % name))
16 class IClient(Interface):
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")
26 class Welcome(rend.Page):
28 docFactory = getxmlfile("welcome.xhtml")
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:
36 def data_num_peers(self, ctx, data):
37 #client = inevow.ISite(ctx)._client
39 return len(client.get_all_peerids())
40 def data_num_connected_peers(self, ctx, data):
41 return len(IClient(ctx).get_all_peerids())
43 def data_peers(self, ctx, data):
46 for nodeid in sorted(client.get_all_peerids()):
47 row = (idlib.b2a(nodeid),)
51 def render_row(self, ctx, data):
53 ctx.fillSlots("peerid", nodeid_a)
56 # this is a form where users can download files by URI
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")
70 def download(self, uri, filename, ctx):
71 log.msg("webish downloading URI")
72 target = url.here.sibling("download_uri").add("uri", uri)
74 target = target.add("filename", filename)
77 def render_forms(self, ctx, data):
78 return webform.renderForms()
81 class Directory(rend.Page):
83 docFactory = getxmlfile("directory.xhtml")
85 def __init__(self, dirnode, dirname):
86 self._dirnode = dirnode
87 self._dirname = dirname
89 def childFactory(self, ctx, name):
90 if name.startswith("freeform"): # ick
92 if self._dirname == "/":
95 dirname = self._dirname + "/" + name
96 d = self._dirnode.callRemote("get", name)
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)
105 def render_title(self, ctx, data):
106 return ctx.tag["Directory of '%s':" % self._dirname]
108 def render_header(self, ctx, data):
109 return "Directory of '%s':" % self._dirname
111 def data_children(self, ctx, data):
112 d = self._dirnode.callRemote("list")
115 def render_row(self, ctx, data):
117 if isinstance(target, str):
119 dlurl = urllib.quote(name)
120 ctx.fillSlots("filename",
121 T.a(href=dlurl)[html.escape(name)])
122 ctx.fillSlots("type", "FILE")
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)])
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"),
138 ctx.fillSlots("delete", delete)
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", "-")
149 def render_forms(self, ctx, data):
150 return webform.renderForms()
152 def bind_upload(self, ctx):
154 # Note: this comment is no longer accurate, as it reflects the older
155 # (apparently deprecated) formless.autocallable /
156 # annotate.TypedInterface approach.
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
165 up = annotate.FileUpload(label="Choose a file to upload: ",
167 requiredFailMessage="Do iT!")
168 contentsarg = annotate.Argument("contents", up)
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")
175 def upload(self, contents, ctx):
176 # contents is a cgi.FieldStorage instance
177 log.msg("starting webish upload")
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))
185 log.msg("webish upload complete")
189 return url.here.add("results",
190 "upload of '%s' complete!" % contents.filename)
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")
199 def mkdir(self, name):
201 log.msg("making new webish directory")
202 d = self._dirnode.callRemote("add_directory", name)
204 log.msg("webish mkdir complete")
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])
216 d.addCallback(_deleted)
219 class WebDownloadTarget:
220 implements(IDownloadTarget)
221 def __init__(self, req):
225 def write(self, data):
226 self._req.write(data)
231 def register_canceller(self, cb):
236 class TypedFile(static.File):
237 # serve data from a named file, but using a Content-Type derived from a
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,
245 self.contentEncodings,
248 class Downloader(resource.Resource):
249 def __init__(self, downloader, name, uri):
250 self._downloader = downloader
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)
263 req.setHeader('content-encoding', encoding)
265 t = WebDownloadTarget(req)
266 #dl = IDownloader(ctx)
267 dl = self._downloader
268 dl.download(self._uri, t)
269 return server.NOT_DONE_YET
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
284 elif "uri" in req.args:
285 # http://host/download_uri?uri=URIGOESHERE
286 uri = req.args["uri"][0]
289 child = Downloader(dl, filename, uri)
291 return rend.Page.locateChild(self, ctx, segments)
293 child_webform_css = webform.defaultCSS
295 child_welcome = Welcome()
298 class WebishServer(service.MultiService):
301 def __init__(self, webport):
302 service.MultiService.__init__(self)
304 placeholder = static.Data("sorry, still initializing", "text/plain")
305 self.root.putChild("vdrive", placeholder)
306 self.root.putChild("", url.here.child("welcome"))#Welcome())
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
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
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
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)