6 from zope.interface import implements
7 from twisted.internet import defer
8 from twisted.internet.interfaces import IPushProducer
9 from twisted.python.failure import Failure
10 from twisted.web import http, html
11 from nevow import url, rend, inevow, tags as T
12 from nevow.inevow import IRequest
14 from foolscap.eventual import fireEventually
16 from allmydata.util import base32
17 from allmydata.uri import from_string_dirnode
18 from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \
19 ExistingChildError, NoSuchChildError
20 from allmydata.monitor import Monitor, OperationCancelledError
21 from allmydata import dirnode
22 from allmydata.web.common import text_plain, WebError, \
23 IClient, IOpHandleTable, NeedOperationHandleError, \
24 boolean_of_arg, get_arg, get_root, \
25 should_create_intermediate_directories, \
26 getxmlfile, RenderMixin
27 from allmydata.web.filenode import ReplaceMeMixin, \
28 FileNodeHandler, PlaceHolderNodeHandler
29 from allmydata.web.check_results import CheckResults, \
30 CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults
31 from allmydata.web.info import MoreInfo
32 from allmydata.web.operations import ReloadMixin
34 class BlockingFileError(Exception):
35 # TODO: catch and transform
36 """We cannot auto-create a parent directory, because there is a file in
39 def make_handler_for(node, parentnode=None, name=None):
41 assert IDirectoryNode.providedBy(parentnode)
42 if IMutableFileNode.providedBy(node):
43 return FileNodeHandler(node, parentnode, name)
44 if IFileNode.providedBy(node):
45 return FileNodeHandler(node, parentnode, name)
46 if IDirectoryNode.providedBy(node):
47 return DirectoryNodeHandler(node, parentnode, name)
48 raise WebError("Cannot provide handler for '%s'" % node)
50 class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
53 def __init__(self, node, parentnode=None, name=None):
54 rend.Page.__init__(self)
57 self.parentnode = parentnode
60 def childFactory(self, ctx, name):
62 name = name.decode("utf-8")
63 d = self.node.get(name)
64 d.addBoth(self.got_child, ctx, name)
65 # got_child returns a handler resource: FileNodeHandler or
66 # DirectoryNodeHandler
69 def got_child(self, node_or_failure, ctx, name):
71 if DEBUG: print "GOT_CHILD", name, node_or_failure
74 nonterminal = len(req.postpath) > 1
75 t = get_arg(req, "t", "").strip()
76 if isinstance(node_or_failure, Failure):
78 f.trap(NoSuchChildError)
79 # No child by this name. What should we do about it?
80 if DEBUG: print "no child", name
81 if DEBUG: print "postpath", req.postpath
83 if DEBUG: print " intermediate"
84 if should_create_intermediate_directories(req):
85 # create intermediate directories
86 if DEBUG: print " making intermediate directory"
87 d = self.node.create_empty_directory(name)
88 d.addCallback(make_handler_for, self.node, name)
91 if DEBUG: print " terminal"
93 if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
94 if DEBUG: print " making final directory"
96 d = self.node.create_empty_directory(name)
97 d.addCallback(make_handler_for, self.node, name)
99 if (method,t) in ( ("PUT",""), ("PUT","uri"), ):
100 if DEBUG: print " PUT, making leaf placeholder"
101 # we were trying to find the leaf filenode (to put a new
102 # file in its place), and it didn't exist. That's ok,
103 # since that's the leaf node that we're about to create.
104 # We make a dummy one, which will respond to the PUT
105 # request by replacing itself.
106 return PlaceHolderNodeHandler(self.node, name)
107 if DEBUG: print " 404"
108 # otherwise, we just return a no-such-child error
109 return rend.FourOhFour()
111 node = node_or_failure
112 if nonterminal and should_create_intermediate_directories(req):
113 if not IDirectoryNode.providedBy(node):
114 # we would have put a new directory here, but there was a
116 if DEBUG: print "blocking"
117 raise WebError("Unable to create directory '%s': "
118 "a file was in the way" % name,
120 if DEBUG: print "good child"
121 return make_handler_for(node, self.node, name)
123 def render_DELETE(self, ctx):
124 assert self.parentnode and self.name
125 d = self.parentnode.delete(self.name)
126 d.addCallback(lambda res: self.node.get_uri())
129 def render_GET(self, ctx):
130 client = IClient(ctx)
132 # This is where all of the directory-related ?t=* code goes.
133 t = get_arg(req, "t", "").strip()
135 # render the directory as HTML, using the docFactory and Nevow's
136 # whole templating thing.
137 return DirectoryAsHTML(self.node)
140 return DirectoryJSONMetadata(ctx, self.node)
142 return MoreInfo(self.node)
144 return DirectoryURI(ctx, self.node)
145 if t == "readonly-uri":
146 return DirectoryReadonlyURI(ctx, self.node)
147 if t == 'rename-form':
148 return RenameForm(self.node)
150 raise WebError("GET directory: bad t=%s" % t)
152 def render_PUT(self, ctx):
154 t = get_arg(req, "t", "").strip()
155 replace = boolean_of_arg(get_arg(req, "replace", "true"))
157 # our job was done by the traversal/create-intermediate-directory
158 # process that got us here.
159 return text_plain(self.node.get_uri(), ctx) # TODO: urlencode
162 # they're trying to set_uri and that name is already occupied
164 raise ExistingChildError()
165 d = self.replace_me_with_a_childcap(ctx, replace)
169 raise WebError("PUT to a directory")
171 def render_POST(self, ctx):
173 t = get_arg(req, "t", "").strip()
176 d = self._POST_mkdir(req)
179 d = self._POST_mkdir_p(req)
181 d = self._POST_upload(ctx) # this one needs the context
183 d = self._POST_uri(req)
185 d = self._POST_delete(req)
187 d = self._POST_rename(req)
189 d = self._POST_check(req)
190 elif t == "start-deep-check":
191 d = self._POST_start_deep_check(ctx)
192 elif t == "start-manifest":
193 d = self._POST_start_manifest(ctx)
194 elif t == "start-deep-size":
195 d = self._POST_start_deep_size(ctx)
196 elif t == "start-deep-stats":
197 d = self._POST_start_deep_stats(ctx)
198 elif t == "stream-manifest":
199 d = self._POST_stream_manifest(ctx)
200 elif t == "set_children":
202 d = self._POST_set_children(req)
204 raise WebError("POST to a directory with bad t=%s" % t)
206 when_done = get_arg(req, "when_done", None)
208 d.addCallback(lambda res: url.URL.fromString(when_done))
211 def _POST_mkdir(self, req):
212 name = get_arg(req, "name", "")
214 # our job is done, it was handled by the code in got_child
215 # which created the final directory (i.e. us)
216 return defer.succeed(self.node.get_uri()) # TODO: urlencode
217 name = name.decode("utf-8")
218 replace = boolean_of_arg(get_arg(req, "replace", "true"))
219 d = self.node.create_empty_directory(name, overwrite=replace)
220 d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
223 def _POST_mkdir_p(self, req):
224 path = get_arg(req, "path")
226 raise WebError("mkdir-p requires a path")
227 path_ = tuple([seg.decode("utf-8") for seg in path.split('/') if seg ])
229 d = self._get_or_create_directories(self.node, path_)
230 d.addCallback(lambda node: node.get_uri())
233 def _get_or_create_directories(self, node, path):
234 if not IDirectoryNode.providedBy(node):
235 # unfortunately it is too late to provide the name of the
236 # blocking directory in the error message.
237 raise BlockingFileError("cannot create directory because there "
238 "is a file in the way")
240 return defer.succeed(node)
241 d = node.get(path[0])
242 def _maybe_create(f):
243 f.trap(NoSuchChildError)
244 return node.create_empty_directory(path[0])
245 d.addErrback(_maybe_create)
246 d.addCallback(self._get_or_create_directories, path[1:])
249 def _POST_upload(self, ctx):
251 charset = get_arg(req, "_charset", "utf-8")
252 contents = req.fields["file"]
253 assert contents.filename is None or isinstance(contents.filename, str)
254 name = get_arg(req, "name")
255 name = name or contents.filename
259 # this prohibts empty, missing, and all-whitespace filenames
260 raise WebError("upload requires a name")
261 assert isinstance(name, str)
262 name = name.decode(charset)
264 raise WebError("name= may not contain a slash", http.BAD_REQUEST)
265 assert isinstance(name, unicode)
267 # since POST /uri/path/file?t=upload is equivalent to
268 # POST /uri/path/dir?t=upload&name=foo, just do the same thing that
269 # childFactory would do. Things are cleaner if we only do a subset of
270 # them, though, so we don't do: d = self.childFactory(ctx, name)
272 d = self.node.get(name)
273 def _maybe_got_node(node_or_failure):
274 if isinstance(node_or_failure, Failure):
276 f.trap(NoSuchChildError)
277 # create a placeholder which will see POST t=upload
278 return PlaceHolderNodeHandler(self.node, name)
280 node = node_or_failure
281 return make_handler_for(node, self.node, name)
282 d.addBoth(_maybe_got_node)
283 # now we have a placeholder or a filenodehandler, and we can just
284 # delegate to it. We could return the resource back out of
285 # DirectoryNodeHandler.renderHTTP, and nevow would recurse into it,
286 # but the addCallback() that handles when_done= would break.
287 d.addCallback(lambda child: child.renderHTTP(ctx))
290 def _POST_uri(self, req):
291 childcap = get_arg(req, "uri")
293 raise WebError("set-uri requires a uri")
294 name = get_arg(req, "name")
296 raise WebError("set-uri requires a name")
297 charset = get_arg(req, "_charset", "utf-8")
298 name = name.decode(charset)
299 replace = boolean_of_arg(get_arg(req, "replace", "true"))
300 d = self.node.set_uri(name, childcap, overwrite=replace)
301 d.addCallback(lambda res: childcap)
304 def _POST_delete(self, req):
305 name = get_arg(req, "name")
307 # apparently an <input type="hidden" name="name" value="">
308 # won't show up in the resulting encoded form.. the 'name'
309 # field is completely missing. So to allow deletion of an
310 # empty file, we have to pretend that None means ''. The only
311 # downide of this is a slightly confusing error message if
312 # someone does a POST without a name= field. For our own HTML
313 # thisn't a big deal, because we create the 'delete' POST
316 charset = get_arg(req, "_charset", "utf-8")
317 name = name.decode(charset)
318 d = self.node.delete(name)
319 d.addCallback(lambda res: "thing deleted")
322 def _POST_rename(self, req):
323 charset = get_arg(req, "_charset", "utf-8")
324 from_name = get_arg(req, "from_name")
325 if from_name is not None:
326 from_name = from_name.strip()
327 from_name = from_name.decode(charset)
328 assert isinstance(from_name, unicode)
329 to_name = get_arg(req, "to_name")
330 if to_name is not None:
331 to_name = to_name.strip()
332 to_name = to_name.decode(charset)
333 assert isinstance(to_name, unicode)
334 if not from_name or not to_name:
335 raise WebError("rename requires from_name and to_name")
336 if from_name == to_name:
337 return defer.succeed("redundant rename")
339 # allow from_name to contain slashes, so they can fix names that were
340 # accidentally created with them. But disallow them in to_name, to
341 # discourage the practice.
343 raise WebError("to_name= may not contain a slash", http.BAD_REQUEST)
345 replace = boolean_of_arg(get_arg(req, "replace", "true"))
346 d = self.node.move_child_to(from_name, self.node, to_name, replace)
347 d.addCallback(lambda res: "thing renamed")
350 def _POST_check(self, req):
351 # check this directory
352 verify = boolean_of_arg(get_arg(req, "verify", "false"))
353 repair = boolean_of_arg(get_arg(req, "repair", "false"))
355 d = self.node.check_and_repair(Monitor(), verify)
356 d.addCallback(lambda res: CheckAndRepairResults(res))
358 d = self.node.check(Monitor(), verify)
359 d.addCallback(lambda res: CheckResults(res))
362 def _start_operation(self, monitor, renderer, ctx):
363 table = IOpHandleTable(ctx)
364 table.add_monitor(ctx, monitor, renderer)
365 return table.redirect_to(ctx)
367 def _POST_start_deep_check(self, ctx):
368 # check this directory and everything reachable from it
369 if not get_arg(ctx, "ophandle"):
370 raise NeedOperationHandleError("slow operation requires ophandle=")
371 verify = boolean_of_arg(get_arg(ctx, "verify", "false"))
372 repair = boolean_of_arg(get_arg(ctx, "repair", "false"))
374 monitor = self.node.start_deep_check_and_repair(verify)
375 renderer = DeepCheckAndRepairResults(monitor)
377 monitor = self.node.start_deep_check(verify)
378 renderer = DeepCheckResults(monitor)
379 return self._start_operation(monitor, renderer, ctx)
381 def _POST_start_manifest(self, ctx):
382 if not get_arg(ctx, "ophandle"):
383 raise NeedOperationHandleError("slow operation requires ophandle=")
384 monitor = self.node.build_manifest()
385 renderer = ManifestResults(monitor)
386 return self._start_operation(monitor, renderer, ctx)
388 def _POST_start_deep_size(self, ctx):
389 if not get_arg(ctx, "ophandle"):
390 raise NeedOperationHandleError("slow operation requires ophandle=")
391 monitor = self.node.start_deep_stats()
392 renderer = DeepSizeResults(monitor)
393 return self._start_operation(monitor, renderer, ctx)
395 def _POST_start_deep_stats(self, ctx):
396 if not get_arg(ctx, "ophandle"):
397 raise NeedOperationHandleError("slow operation requires ophandle=")
398 monitor = self.node.start_deep_stats()
399 renderer = DeepStatsResults(monitor)
400 return self._start_operation(monitor, renderer, ctx)
402 def _POST_stream_manifest(self, ctx):
403 walker = ManifestStreamer(ctx, self.node)
404 monitor = self.node.deep_traverse(walker)
405 walker.setMonitor(monitor)
406 # register to hear stopProducing. The walker ignores pauseProducing.
407 IRequest(ctx).registerProducer(walker, True)
408 d = monitor.when_done()
410 IRequest(ctx).unregisterProducer()
414 f.trap(OperationCancelledError)
415 return "Operation Cancelled"
416 d.addErrback(_cancelled)
419 def _POST_set_children(self, req):
420 replace = boolean_of_arg(get_arg(req, "replace", "true"))
422 body = req.content.read()
424 children = simplejson.loads(body)
425 except ValueError, le:
426 le.args = tuple(le.args + (body,))
427 # TODO test handling of bad JSON
430 for name, (file_or_dir, mddict) in children.iteritems():
431 name = unicode(name) # simplejson-2.0.1 returns str *or* unicode
432 cap = str(mddict.get('rw_uri') or mddict.get('ro_uri'))
433 cs.append((name, cap, mddict.get('metadata')))
434 d = self.node.set_children(cs, replace)
435 d.addCallback(lambda res: "Okay so I did it.")
439 def abbreviated_dirnode(dirnode):
440 u = from_string_dirnode(dirnode.get_uri())
443 class DirectoryAsHTML(rend.Page):
444 # The remainder of this class is to render the directory into
445 # human+browser -oriented HTML.
446 docFactory = getxmlfile("directory.xhtml")
449 def __init__(self, node):
450 rend.Page.__init__(self)
453 def render_title(self, ctx, data):
454 si_s = abbreviated_dirnode(self.node)
455 header = ["Directory SI=%s" % si_s]
456 return ctx.tag[header]
458 def render_header(self, ctx, data):
459 si_s = abbreviated_dirnode(self.node)
460 header = ["Directory SI=%s" % si_s]
461 if self.node.is_readonly():
462 header.append(" (readonly)")
463 return ctx.tag[header]
465 def render_welcome(self, ctx, data):
467 return T.div[T.a(href=link)["Return to Welcome page"]]
469 def render_show_readonly(self, ctx, data):
470 if self.node.is_readonly():
472 rocap = self.node.get_readonly_uri()
474 uri_link = "%s/uri/%s/" % (root, urllib.quote(rocap))
475 return ctx.tag[T.a(href=uri_link)["Read-Only Version"]]
477 def data_children(self, ctx, data):
479 d.addCallback(lambda dict: sorted(dict.items()))
480 def _stall_some(items):
481 # Deferreds don't optimize out tail recursion, and the way
482 # Nevow's flattener handles Deferreds doesn't take this into
483 # account. As a result, large lists of Deferreds that fire in the
484 # same turn (i.e. the output of defer.succeed) will cause a stack
485 # overflow. To work around this, we insert a turn break after
486 # every 100 items, using foolscap's fireEventually(). This gives
487 # the stack a chance to be popped. It would also work to put
488 # every item in its own turn, but that'd be a lot more
489 # inefficient. This addresses ticket #237, for which I was never
490 # able to create a failing unit test.
492 for i,item in enumerate(items):
494 output.append(fireEventually(item))
498 d.addCallback(_stall_some)
501 def render_row(self, ctx, data):
502 name, (target, metadata) = data
503 name = name.encode("utf-8")
504 assert not isinstance(name, unicode)
505 nameurl = urllib.quote(name, safe="") # encode any slashes too
508 here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
509 if self.node.is_readonly():
513 # this creates a button which will cause our child__delete method
514 # to be invoked, which deletes the file and then redirects the
515 # browser back to this directory
516 delete = T.form(action=here, method="post")[
517 T.input(type='hidden', name='t', value='delete'),
518 T.input(type='hidden', name='name', value=name),
519 T.input(type='hidden', name='when_done', value="."),
520 T.input(type='submit', value='del', name="del"),
523 rename = T.form(action=here, method="get")[
524 T.input(type='hidden', name='t', value='rename-form'),
525 T.input(type='hidden', name='name', value=name),
526 T.input(type='hidden', name='when_done', value="."),
527 T.input(type='submit', value='rename', name="rename"),
530 ctx.fillSlots("delete", delete)
531 ctx.fillSlots("rename", rename)
534 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
535 if "ctime" in metadata:
536 ctime = time.strftime(TIME_FORMAT,
537 time.localtime(metadata["ctime"]))
538 times.append("c: " + ctime)
539 if "mtime" in metadata:
540 mtime = time.strftime(TIME_FORMAT,
541 time.localtime(metadata["mtime"]))
544 times.append("m: " + mtime)
545 ctx.fillSlots("times", times)
547 assert (IFileNode.providedBy(target)
548 or IDirectoryNode.providedBy(target)
549 or IMutableFileNode.providedBy(target)), target
551 quoted_uri = urllib.quote(target.get_uri())
553 if IMutableFileNode.providedBy(target):
554 # to prevent javascript in displayed .html files from stealing a
555 # secret directory URI from the URL, send the browser to a URI-based
556 # page that doesn't know about the directory at all
557 dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
559 ctx.fillSlots("filename",
560 T.a(href=dlurl)[html.escape(name)])
561 ctx.fillSlots("type", "SSK")
563 ctx.fillSlots("size", "?")
565 info_link = "%s/uri/%s?t=info" % (root, quoted_uri)
567 elif IFileNode.providedBy(target):
568 dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
570 ctx.fillSlots("filename",
571 T.a(href=dlurl)[html.escape(name)])
572 ctx.fillSlots("type", "FILE")
574 ctx.fillSlots("size", target.get_size())
576 info_link = "%s/uri/%s?t=info" % (root, quoted_uri)
578 elif IDirectoryNode.providedBy(target):
580 uri_link = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
581 ctx.fillSlots("filename",
582 T.a(href=uri_link)[html.escape(name)])
583 if target.is_readonly():
587 ctx.fillSlots("type", dirtype)
588 ctx.fillSlots("size", "-")
589 info_link = "%s/uri/%s/?t=info" % (root, quoted_uri)
591 ctx.fillSlots("info", T.a(href=info_link)["More Info"])
595 def render_forms(self, ctx, data):
598 if self.node.is_readonly():
599 forms.append(T.div["No upload forms: directory is read-only"])
602 mkdir = T.form(action=".", method="post",
603 enctype="multipart/form-data")[
605 T.input(type="hidden", name="t", value="mkdir"),
606 T.input(type="hidden", name="when_done", value="."),
607 T.legend(class_="freeform-form-label")["Create a new directory"],
608 "New directory name: ",
609 T.input(type="text", name="name"), " ",
610 T.input(type="submit", value="Create"),
612 forms.append(T.div(class_="freeform-form")[mkdir])
614 upload = T.form(action=".", method="post",
615 enctype="multipart/form-data")[
617 T.input(type="hidden", name="t", value="upload"),
618 T.input(type="hidden", name="when_done", value="."),
619 T.legend(class_="freeform-form-label")["Upload a file to this directory"],
620 "Choose a file to upload: ",
621 T.input(type="file", name="file", class_="freeform-input-file"),
623 T.input(type="submit", value="Upload"),
625 T.input(type="checkbox", name="mutable"),
627 forms.append(T.div(class_="freeform-form")[upload])
629 mount = T.form(action=".", method="post",
630 enctype="multipart/form-data")[
632 T.input(type="hidden", name="t", value="uri"),
633 T.input(type="hidden", name="when_done", value="."),
634 T.legend(class_="freeform-form-label")["Attach a file or directory"
638 T.input(type="text", name="name"), " ",
639 "URI of new child: ",
640 T.input(type="text", name="uri"), " ",
641 T.input(type="submit", value="Attach"),
643 forms.append(T.div(class_="freeform-form")[mount])
646 def render_results(self, ctx, data):
648 return get_arg(req, "results", "")
651 def DirectoryJSONMetadata(ctx, dirnode):
655 for name, (childnode, metadata) in children.iteritems():
656 if childnode.is_readonly():
658 ro_uri = childnode.get_uri()
660 rw_uri = childnode.get_uri()
661 ro_uri = childnode.get_readonly_uri()
662 if IFileNode.providedBy(childnode):
663 kiddata = ("filenode", {'size': childnode.get_size(),
664 'metadata': metadata,
667 assert IDirectoryNode.providedBy(childnode), (childnode,
669 kiddata = ("dirnode", {'metadata': metadata})
671 kiddata[1]["ro_uri"] = ro_uri
673 kiddata[1]["rw_uri"] = rw_uri
674 verifycap = childnode.get_verify_cap()
676 kiddata[1]['verify_uri'] = verifycap.to_string()
677 kiddata[1]['mutable'] = childnode.is_mutable()
679 if dirnode.is_readonly():
681 dro_uri = dirnode.get_uri()
683 drw_uri = dirnode.get_uri()
684 dro_uri = dirnode.get_readonly_uri()
685 contents = { 'children': kids }
687 contents['ro_uri'] = dro_uri
689 contents['rw_uri'] = drw_uri
690 verifycap = dirnode.get_verify_cap()
692 contents['verify_uri'] = verifycap.to_string()
693 contents['mutable'] = dirnode.is_mutable()
694 data = ("dirnode", contents)
695 return simplejson.dumps(data, indent=1) + "\n"
697 d.addCallback(text_plain, ctx)
702 def DirectoryURI(ctx, dirnode):
703 return text_plain(dirnode.get_uri(), ctx)
705 def DirectoryReadonlyURI(ctx, dirnode):
706 return text_plain(dirnode.get_readonly_uri(), ctx)
708 class RenameForm(rend.Page):
710 docFactory = getxmlfile("rename-form.xhtml")
712 def render_title(self, ctx, data):
713 return ctx.tag["Directory SI=%s" % abbreviated_dirnode(self.original)]
715 def render_header(self, ctx, data):
717 "in directory SI=%s" % abbreviated_dirnode(self.original),
720 if self.original.is_readonly():
721 header.append(" (readonly!)")
723 return ctx.tag[header]
725 def render_when_done(self, ctx, data):
726 return T.input(type="hidden", name="when_done", value=".")
728 def render_get_name(self, ctx, data):
730 name = get_arg(req, "name", "")
731 ctx.tag.attributes['value'] = name
735 class ManifestResults(rend.Page, ReloadMixin):
736 docFactory = getxmlfile("manifest.xhtml")
738 def __init__(self, monitor):
739 self.monitor = monitor
741 def renderHTTP(self, ctx):
742 output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
744 return self.text(ctx)
746 return self.json(ctx)
747 return rend.Page.renderHTTP(self, ctx)
749 def slashify_path(self, path):
752 return "/".join([p.encode("utf-8") for p in path])
755 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
757 is_finished = self.monitor.is_finished()
758 lines.append("finished: " + {True: "yes", False: "no"}[is_finished])
759 for (path, cap) in self.monitor.get_status()["manifest"]:
760 lines.append(self.slashify_path(path) + " " + cap)
761 return "\n".join(lines) + "\n"
764 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
768 status = { "stats": s["stats"],
769 "finished": m.is_finished(),
770 "origin": base32.b2a(m.origin_si),
773 # don't return manifest/verifycaps/SIs unless the operation is
774 # done, to save on CPU/memory (both here and in the HTTP client
775 # who has to unpack the JSON). Tests show that the ManifestWalker
776 # needs about 1092 bytes per item, the JSON we generate here
777 # requires about 503 bytes per item, and some internal overhead
778 # (perhaps transport-layer buffers in twisted.web?) requires an
779 # additional 1047 bytes per item.
780 status.update({ "manifest": s["manifest"],
781 "verifycaps": [i for i in s["verifycaps"]],
782 "storage-index": [i for i in s["storage-index"]],
784 # simplejson doesn't know how to serialize a set. We use a
785 # generator that walks the set rather than list(setofthing) to
786 # save a small amount of memory (4B*len) and a moderate amount of
788 return simplejson.dumps(status, indent=1)
790 def _si_abbrev(self):
791 return base32.b2a(self.monitor.origin_si)[:6]
793 def render_title(self, ctx):
794 return T.title["Manifest of SI=%s" % self._si_abbrev()]
796 def render_header(self, ctx):
797 return T.p["Manifest of SI=%s" % self._si_abbrev()]
799 def data_items(self, ctx, data):
800 return self.monitor.get_status()["manifest"]
802 def render_row(self, ctx, (path, cap)):
803 ctx.fillSlots("path", self.slashify_path(path))
805 # TODO: we need a clean consistent way to get the type of a cap string
806 if cap.startswith("URI:CHK") or cap.startswith("URI:SSK"):
807 nameurl = urllib.quote(path[-1].encode("utf-8"))
808 uri_link = "%s/file/%s/@@named=/%s" % (root, urllib.quote(cap),
811 uri_link = "%s/uri/%s" % (root, urllib.quote(cap))
812 ctx.fillSlots("cap", T.a(href=uri_link)[cap])
815 class DeepSizeResults(rend.Page):
816 def __init__(self, monitor):
817 self.monitor = monitor
819 def renderHTTP(self, ctx):
820 output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
821 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
823 return self.json(ctx)
825 is_finished = self.monitor.is_finished()
826 output = "finished: " + {True: "yes", False: "no"}[is_finished] + "\n"
828 stats = self.monitor.get_status()
829 total = (stats.get("size-immutable-files", 0)
830 + stats.get("size-mutable-files", 0)
831 + stats.get("size-directories", 0))
832 output += "size: %d\n" % total
836 status = {"finished": self.monitor.is_finished(),
837 "size": self.monitor.get_status(),
839 return simplejson.dumps(status)
841 class DeepStatsResults(rend.Page):
842 def __init__(self, monitor):
843 self.monitor = monitor
845 def renderHTTP(self, ctx):
847 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
848 s = self.monitor.get_status().copy()
849 s["finished"] = self.monitor.is_finished()
850 return simplejson.dumps(s, indent=1)
852 class ManifestStreamer(dirnode.DeepStats):
853 implements(IPushProducer)
855 def __init__(self, ctx, origin):
856 dirnode.DeepStats.__init__(self, origin)
857 self.req = IRequest(ctx)
859 def setMonitor(self, monitor):
860 self.monitor = monitor
861 def pauseProducing(self):
863 def resumeProducing(self):
865 def stopProducing(self):
866 self.monitor.cancel()
868 def add_node(self, node, path):
869 dirnode.DeepStats.add_node(self, node, path)
871 "cap": node.get_uri()}
873 if IDirectoryNode.providedBy(node):
874 d["type"] = "directory"
878 v = node.get_verify_cap()
883 r = node.get_repair_cap()
888 si = node.get_storage_index()
891 d["storage-index"] = si
893 j = simplejson.dumps(d, ensure_ascii=True)
895 self.req.write(j+"\n")
898 stats = dirnode.DeepStats.get_results(self)
899 d = {"type": "stats",
902 j = simplejson.dumps(d, ensure_ascii=True)
904 self.req.write(j+"\n")