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 data_children(self, ctx, data):
471 d.addCallback(lambda dict: sorted(dict.items()))
472 def _stall_some(items):
473 # Deferreds don't optimize out tail recursion, and the way
474 # Nevow's flattener handles Deferreds doesn't take this into
475 # account. As a result, large lists of Deferreds that fire in the
476 # same turn (i.e. the output of defer.succeed) will cause a stack
477 # overflow. To work around this, we insert a turn break after
478 # every 100 items, using foolscap's fireEventually(). This gives
479 # the stack a chance to be popped. It would also work to put
480 # every item in its own turn, but that'd be a lot more
481 # inefficient. This addresses ticket #237, for which I was never
482 # able to create a failing unit test.
484 for i,item in enumerate(items):
486 output.append(fireEventually(item))
490 d.addCallback(_stall_some)
493 def render_row(self, ctx, data):
494 name, (target, metadata) = data
495 name = name.encode("utf-8")
496 assert not isinstance(name, unicode)
497 nameurl = urllib.quote(name, safe="") # encode any slashes too
500 here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
501 if self.node.is_readonly():
505 # this creates a button which will cause our child__delete method
506 # to be invoked, which deletes the file and then redirects the
507 # browser back to this directory
508 delete = T.form(action=here, method="post")[
509 T.input(type='hidden', name='t', value='delete'),
510 T.input(type='hidden', name='name', value=name),
511 T.input(type='hidden', name='when_done', value="."),
512 T.input(type='submit', value='del', name="del"),
515 rename = T.form(action=here, method="get")[
516 T.input(type='hidden', name='t', value='rename-form'),
517 T.input(type='hidden', name='name', value=name),
518 T.input(type='hidden', name='when_done', value="."),
519 T.input(type='submit', value='rename', name="rename"),
522 ctx.fillSlots("delete", delete)
523 ctx.fillSlots("rename", rename)
526 TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
527 if "ctime" in metadata:
528 ctime = time.strftime(TIME_FORMAT,
529 time.localtime(metadata["ctime"]))
530 times.append("c: " + ctime)
531 if "mtime" in metadata:
532 mtime = time.strftime(TIME_FORMAT,
533 time.localtime(metadata["mtime"]))
536 times.append("m: " + mtime)
537 ctx.fillSlots("times", times)
539 assert (IFileNode.providedBy(target)
540 or IDirectoryNode.providedBy(target)
541 or IMutableFileNode.providedBy(target)), target
543 quoted_uri = urllib.quote(target.get_uri())
545 if IMutableFileNode.providedBy(target):
546 # to prevent javascript in displayed .html files from stealing a
547 # secret directory URI from the URL, send the browser to a URI-based
548 # page that doesn't know about the directory at all
549 dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
551 ctx.fillSlots("filename",
552 T.a(href=dlurl)[html.escape(name)])
553 ctx.fillSlots("type", "SSK")
555 ctx.fillSlots("size", "?")
557 info_link = "%s/uri/%s?t=info" % (root, quoted_uri)
559 elif IFileNode.providedBy(target):
560 dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
562 ctx.fillSlots("filename",
563 T.a(href=dlurl)[html.escape(name)])
564 ctx.fillSlots("type", "FILE")
566 ctx.fillSlots("size", target.get_size())
568 info_link = "%s/uri/%s?t=info" % (root, quoted_uri)
570 elif IDirectoryNode.providedBy(target):
572 uri_link = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
573 ctx.fillSlots("filename",
574 T.a(href=uri_link)[html.escape(name)])
575 if target.is_readonly():
579 ctx.fillSlots("type", dirtype)
580 ctx.fillSlots("size", "-")
581 info_link = "%s/uri/%s/?t=info" % (root, quoted_uri)
583 ctx.fillSlots("info", T.a(href=info_link)["More Info"])
587 def render_forms(self, ctx, data):
590 if self.node.is_readonly():
591 forms.append(T.div["No upload forms: directory is read-only"])
594 mkdir = T.form(action=".", method="post",
595 enctype="multipart/form-data")[
597 T.input(type="hidden", name="t", value="mkdir"),
598 T.input(type="hidden", name="when_done", value="."),
599 T.legend(class_="freeform-form-label")["Create a new directory"],
600 "New directory name: ",
601 T.input(type="text", name="name"), " ",
602 T.input(type="submit", value="Create"),
604 forms.append(T.div(class_="freeform-form")[mkdir])
606 upload = T.form(action=".", method="post",
607 enctype="multipart/form-data")[
609 T.input(type="hidden", name="t", value="upload"),
610 T.input(type="hidden", name="when_done", value="."),
611 T.legend(class_="freeform-form-label")["Upload a file to this directory"],
612 "Choose a file to upload: ",
613 T.input(type="file", name="file", class_="freeform-input-file"),
615 T.input(type="submit", value="Upload"),
617 T.input(type="checkbox", name="mutable"),
619 forms.append(T.div(class_="freeform-form")[upload])
621 mount = T.form(action=".", method="post",
622 enctype="multipart/form-data")[
624 T.input(type="hidden", name="t", value="uri"),
625 T.input(type="hidden", name="when_done", value="."),
626 T.legend(class_="freeform-form-label")["Attach a file or directory"
630 T.input(type="text", name="name"), " ",
631 "URI of new child: ",
632 T.input(type="text", name="uri"), " ",
633 T.input(type="submit", value="Attach"),
635 forms.append(T.div(class_="freeform-form")[mount])
638 def render_results(self, ctx, data):
640 return get_arg(req, "results", "")
643 def DirectoryJSONMetadata(ctx, dirnode):
647 for name, (childnode, metadata) in children.iteritems():
648 if childnode.is_readonly():
650 ro_uri = childnode.get_uri()
652 rw_uri = childnode.get_uri()
653 ro_uri = childnode.get_readonly_uri()
654 if IFileNode.providedBy(childnode):
655 kiddata = ("filenode", {'size': childnode.get_size(),
656 'metadata': metadata,
659 assert IDirectoryNode.providedBy(childnode), (childnode,
661 kiddata = ("dirnode", {'metadata': metadata})
663 kiddata[1]["ro_uri"] = ro_uri
665 kiddata[1]["rw_uri"] = rw_uri
666 kiddata[1]['mutable'] = childnode.is_mutable()
668 if dirnode.is_readonly():
670 dro_uri = dirnode.get_uri()
672 drw_uri = dirnode.get_uri()
673 dro_uri = dirnode.get_readonly_uri()
674 contents = { 'children': kids }
676 contents['ro_uri'] = dro_uri
678 contents['rw_uri'] = drw_uri
679 contents['mutable'] = dirnode.is_mutable()
680 data = ("dirnode", contents)
681 return simplejson.dumps(data, indent=1) + "\n"
683 d.addCallback(text_plain, ctx)
688 def DirectoryURI(ctx, dirnode):
689 return text_plain(dirnode.get_uri(), ctx)
691 def DirectoryReadonlyURI(ctx, dirnode):
692 return text_plain(dirnode.get_readonly_uri(), ctx)
694 class RenameForm(rend.Page):
696 docFactory = getxmlfile("rename-form.xhtml")
698 def render_title(self, ctx, data):
699 return ctx.tag["Directory SI=%s" % abbreviated_dirnode(self.original)]
701 def render_header(self, ctx, data):
703 "in directory SI=%s" % abbreviated_dirnode(self.original),
706 if self.original.is_readonly():
707 header.append(" (readonly!)")
709 return ctx.tag[header]
711 def render_when_done(self, ctx, data):
712 return T.input(type="hidden", name="when_done", value=".")
714 def render_get_name(self, ctx, data):
716 name = get_arg(req, "name", "")
717 ctx.tag.attributes['value'] = name
721 class ManifestResults(rend.Page, ReloadMixin):
722 docFactory = getxmlfile("manifest.xhtml")
724 def __init__(self, monitor):
725 self.monitor = monitor
727 def renderHTTP(self, ctx):
728 output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
730 return self.text(ctx)
732 return self.json(ctx)
733 return rend.Page.renderHTTP(self, ctx)
735 def slashify_path(self, path):
738 return "/".join([p.encode("utf-8") for p in path])
741 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
743 is_finished = self.monitor.is_finished()
744 lines.append("finished: " + {True: "yes", False: "no"}[is_finished])
745 for (path, cap) in self.monitor.get_status()["manifest"]:
746 lines.append(self.slashify_path(path) + " " + cap)
747 return "\n".join(lines) + "\n"
750 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
754 status = { "stats": s["stats"],
755 "finished": m.is_finished(),
756 "origin": base32.b2a(m.origin_si),
759 # don't return manifest/verifycaps/SIs unless the operation is
760 # done, to save on CPU/memory (both here and in the HTTP client
761 # who has to unpack the JSON). Tests show that the ManifestWalker
762 # needs about 1092 bytes per item, the JSON we generate here
763 # requires about 503 bytes per item, and some internal overhead
764 # (perhaps transport-layer buffers in twisted.web?) requires an
765 # additional 1047 bytes per item.
766 status.update({ "manifest": s["manifest"],
767 "verifycaps": [i for i in s["verifycaps"]],
768 "storage-index": [i for i in s["storage-index"]],
770 # simplejson doesn't know how to serialize a set. We use a
771 # generator that walks the set rather than list(setofthing) to
772 # save a small amount of memory (4B*len) and a moderate amount of
774 return simplejson.dumps(status, indent=1)
776 def _si_abbrev(self):
777 return base32.b2a(self.monitor.origin_si)[:6]
779 def render_title(self, ctx):
780 return T.title["Manifest of SI=%s" % self._si_abbrev()]
782 def render_header(self, ctx):
783 return T.p["Manifest of SI=%s" % self._si_abbrev()]
785 def data_items(self, ctx, data):
786 return self.monitor.get_status()["manifest"]
788 def render_row(self, ctx, (path, cap)):
789 ctx.fillSlots("path", self.slashify_path(path))
791 # TODO: we need a clean consistent way to get the type of a cap string
792 if cap.startswith("URI:CHK") or cap.startswith("URI:SSK"):
793 nameurl = urllib.quote(path[-1].encode("utf-8"))
794 uri_link = "%s/file/%s/@@named=/%s" % (root, urllib.quote(cap),
797 uri_link = "%s/uri/%s" % (root, urllib.quote(cap))
798 ctx.fillSlots("cap", T.a(href=uri_link)[cap])
801 class DeepSizeResults(rend.Page):
802 def __init__(self, monitor):
803 self.monitor = monitor
805 def renderHTTP(self, ctx):
806 output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
807 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
809 return self.json(ctx)
811 is_finished = self.monitor.is_finished()
812 output = "finished: " + {True: "yes", False: "no"}[is_finished] + "\n"
814 stats = self.monitor.get_status()
815 total = (stats.get("size-immutable-files", 0)
816 + stats.get("size-mutable-files", 0)
817 + stats.get("size-directories", 0))
818 output += "size: %d\n" % total
822 status = {"finished": self.monitor.is_finished(),
823 "size": self.monitor.get_status(),
825 return simplejson.dumps(status)
827 class DeepStatsResults(rend.Page):
828 def __init__(self, monitor):
829 self.monitor = monitor
831 def renderHTTP(self, ctx):
833 inevow.IRequest(ctx).setHeader("content-type", "text/plain")
834 s = self.monitor.get_status().copy()
835 s["finished"] = self.monitor.is_finished()
836 return simplejson.dumps(s, indent=1)
838 class ManifestStreamer(dirnode.DeepStats):
839 implements(IPushProducer)
841 def __init__(self, ctx, origin):
842 dirnode.DeepStats.__init__(self, origin)
843 self.req = IRequest(ctx)
845 def setMonitor(self, monitor):
846 self.monitor = monitor
847 def pauseProducing(self):
849 def resumeProducing(self):
851 def stopProducing(self):
852 self.monitor.cancel()
854 def add_node(self, node, path):
855 dirnode.DeepStats.add_node(self, node, path)
857 "cap": node.get_uri()}
859 if IDirectoryNode.providedBy(node):
860 d["type"] = "directory"
864 v = node.get_verify_cap()
869 r = node.get_repair_cap()
874 si = node.get_storage_index()
877 d["storage-index"] = si
879 j = simplejson.dumps(d, ensure_ascii=True)
881 self.req.write(j+"\n")
884 stats = dirnode.DeepStats.get_results(self)
885 d = {"type": "stats",
888 j = simplejson.dumps(d, ensure_ascii=True)
890 self.req.write(j+"\n")