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
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).queen_pburl
32 def data_connected_to_queen(self, ctx, data):
33 if IClient(ctx).queen:
36 def data_num_peers(self, ctx, data):
37 #client = inevow.ISite(ctx)._client
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:
49 ctx.fillSlots("peerid", idlib.b2a(data))
50 ctx.fillSlots("connected", connected)
53 # this is a form where users can download files by URI
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")
67 def download(self, uri, filename, ctx):
68 log.msg("webish downloading URI")
69 target = url.here.sibling("download_uri").add("uri", uri)
71 target = target.add("filename", filename)
74 def render_forms(self, ctx, data):
75 return webform.renderForms()
78 class Directory(rend.Page):
80 docFactory = getxmlfile("directory.xhtml")
82 def __init__(self, dirnode, dirname):
83 self._dirnode = dirnode
84 self._dirname = dirname
86 def childFactory(self, ctx, name):
87 if name.startswith("freeform"): # ick
89 if self._dirname == "/":
92 dirname = self._dirname + "/" + name
93 d = self._dirnode.callRemote("get", name)
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)
102 def render_title(self, ctx, data):
103 return ctx.tag["Directory of '%s':" % self._dirname]
105 def render_header(self, ctx, data):
106 return "Directory of '%s':" % self._dirname
108 def data_children(self, ctx, data):
109 d = self._dirnode.callRemote("list")
112 def render_row(self, ctx, data):
114 if isinstance(target, str):
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)])
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"),
135 ctx.fillSlots("delete", delete)
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", "-")
146 def render_forms(self, ctx, data):
147 return webform.renderForms()
149 def bind_upload(self, ctx):
151 # Note: this comment is no longer accurate, as it reflects the older
152 # (apparently deprecated) formless.autocallable /
153 # annotate.TypedInterface approach.
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
162 up = annotate.FileUpload(label="Choose a file to upload: ",
164 requiredFailMessage="Do iT!")
165 contentsarg = annotate.Argument("contents", up)
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")
172 def upload(self, contents, ctx):
173 # contents is a cgi.FieldStorage instance
174 log.msg("starting webish upload")
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))
182 log.msg("webish upload complete")
186 return url.here.add("results",
187 "upload of '%s' complete!" % contents.filename)
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")
196 def mkdir(self, name):
198 log.msg("making new webish directory")
199 d = self._dirnode.callRemote("add_directory", name)
201 log.msg("webish mkdir complete")
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])
213 d.addCallback(_deleted)
216 class WebDownloadTarget:
217 implements(IDownloadTarget)
218 def __init__(self, req):
222 def write(self, data):
223 self._req.write(data)
228 def register_canceller(self, cb):
233 class TypedFile(static.File):
234 # serve data from a named file, but using a Content-Type derived from a
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,
242 self.contentEncodings,
245 class Downloader(resource.Resource):
246 def __init__(self, downloader, name, verifierid):
247 self._downloader = downloader
249 self._verifierid = verifierid
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)
260 req.setHeader('content-encoding', encoding)
262 t = WebDownloadTarget(req)
263 #dl = IDownloader(ctx)
264 dl = self._downloader
265 dl.download(self._verifierid, t)
266 return server.NOT_DONE_YET
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
281 elif "uri" in req.args:
282 # http://host/download_uri?uri=URIGOESHERE
283 uri_a = req.args["uri"][0]
286 child = Downloader(dl, filename, idlib.a2b(uri_a))
288 return rend.Page.locateChild(self, ctx, segments)
290 child_webform_css = webform.defaultCSS
292 child_welcome = Welcome()
295 class WebishServer(service.MultiService):
298 def __init__(self, webport):
299 service.MultiService.__init__(self)
301 placeholder = static.Data("sorry, still initializing", "text/plain")
302 self.root.putChild("vdrive", placeholder)
303 self.root.putChild("", url.here.child("welcome"))#Welcome())
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
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
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
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)
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