]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/directory.py
webapi: fix t=rename from==to, it used to delete the file
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / directory.py
1
2 import simplejson
3 import urllib
4 import time
5
6 from twisted.internet import defer
7 from twisted.python.failure import Failure
8 from twisted.web import http, html
9 from nevow import url, rend, inevow, tags as T
10 from nevow.inevow import IRequest
11
12 from foolscap.eventual import fireEventually
13
14 from allmydata.util import base32
15 from allmydata.uri import from_string_dirnode
16 from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \
17      ExistingChildError
18 from allmydata.monitor import Monitor
19 from allmydata.web.common import text_plain, WebError, \
20      IClient, IOpHandleTable, NeedOperationHandleError, \
21      boolean_of_arg, get_arg, get_root, \
22      should_create_intermediate_directories, \
23      getxmlfile, RenderMixin
24 from allmydata.web.filenode import ReplaceMeMixin, \
25      FileNodeHandler, PlaceHolderNodeHandler
26 from allmydata.web.checker_results import CheckerResults, \
27      CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults
28 from allmydata.web.info import MoreInfo
29 from allmydata.web.operations import ReloadMixin
30
31 class BlockingFileError(Exception):
32     # TODO: catch and transform
33     """We cannot auto-create a parent directory, because there is a file in
34     the way"""
35
36 def make_handler_for(node, parentnode=None, name=None):
37     if parentnode:
38         assert IDirectoryNode.providedBy(parentnode)
39     if IFileNode.providedBy(node):
40         return FileNodeHandler(node, parentnode, name)
41     if IMutableFileNode.providedBy(node):
42         return FileNodeHandler(node, parentnode, name)
43     if IDirectoryNode.providedBy(node):
44         return DirectoryNodeHandler(node, parentnode, name)
45     raise WebError("Cannot provide handler for '%s'" % node)
46
47 class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
48     addSlash = True
49
50     def __init__(self, node, parentnode=None, name=None):
51         rend.Page.__init__(self)
52         assert node
53         self.node = node
54         self.parentnode = parentnode
55         self.name = name
56
57     def childFactory(self, ctx, name):
58         req = IRequest(ctx)
59         name = name.decode("utf-8")
60         d = self.node.get(name)
61         d.addBoth(self.got_child, ctx, name)
62         # got_child returns a handler resource: FileNodeHandler or
63         # DirectoryNodeHandler
64         return d
65
66     def got_child(self, node_or_failure, ctx, name):
67         DEBUG = False
68         if DEBUG: print "GOT_CHILD", name, node_or_failure
69         req = IRequest(ctx)
70         method = req.method
71         nonterminal = len(req.postpath) > 1
72         t = get_arg(req, "t", "").strip()
73         if isinstance(node_or_failure, Failure):
74             f = node_or_failure
75             f.trap(KeyError)
76             # No child by this name. What should we do about it?
77             if DEBUG: print "no child", name
78             if DEBUG: print "postpath", req.postpath
79             if nonterminal:
80                 if DEBUG: print " intermediate"
81                 if should_create_intermediate_directories(req):
82                     # create intermediate directories
83                     if DEBUG: print " making intermediate directory"
84                     d = self.node.create_empty_directory(name)
85                     d.addCallback(make_handler_for, self.node, name)
86                     return d
87             else:
88                 if DEBUG: print " terminal"
89                 # terminal node
90                 if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
91                     if DEBUG: print " making final directory"
92                     # final directory
93                     d = self.node.create_empty_directory(name)
94                     d.addCallback(make_handler_for, self.node, name)
95                     return d
96                 if (method,t) in ( ("PUT",""), ("PUT","uri"), ):
97                     if DEBUG: print " PUT, making leaf placeholder"
98                     # we were trying to find the leaf filenode (to put a new
99                     # file in its place), and it didn't exist. That's ok,
100                     # since that's the leaf node that we're about to create.
101                     # We make a dummy one, which will respond to the PUT
102                     # request by replacing itself.
103                     return PlaceHolderNodeHandler(self.node, name)
104             if DEBUG: print " 404"
105             # otherwise, we just return a no-such-child error
106             return rend.FourOhFour()
107
108         node = node_or_failure
109         if nonterminal and should_create_intermediate_directories(req):
110             if not IDirectoryNode.providedBy(node):
111                 # we would have put a new directory here, but there was a
112                 # file in the way.
113                 if DEBUG: print "blocking"
114                 raise WebError("Unable to create directory '%s': "
115                                "a file was in the way" % name,
116                                http.CONFLICT)
117         if DEBUG: print "good child"
118         return make_handler_for(node, self.node, name)
119
120     def render_DELETE(self, ctx):
121         assert self.parentnode and self.name
122         d = self.parentnode.delete(self.name)
123         d.addCallback(lambda res: self.node.get_uri())
124         return d
125
126     def render_GET(self, ctx):
127         client = IClient(ctx)
128         req = IRequest(ctx)
129         # This is where all of the directory-related ?t=* code goes.
130         t = get_arg(req, "t", "").strip()
131         if not t:
132             # render the directory as HTML, using the docFactory and Nevow's
133             # whole templating thing.
134             return DirectoryAsHTML(self.node)
135
136         if t == "json":
137             return DirectoryJSONMetadata(ctx, self.node)
138         if t == "info":
139             return MoreInfo(self.node)
140         if t == "uri":
141             return DirectoryURI(ctx, self.node)
142         if t == "readonly-uri":
143             return DirectoryReadonlyURI(ctx, self.node)
144         if t == 'rename-form':
145             return RenameForm(self.node)
146
147         raise WebError("GET directory: bad t=%s" % t)
148
149     def render_PUT(self, ctx):
150         req = IRequest(ctx)
151         t = get_arg(req, "t", "").strip()
152         replace = boolean_of_arg(get_arg(req, "replace", "true"))
153         if t == "mkdir":
154             # our job was done by the traversal/create-intermediate-directory
155             # process that got us here.
156             return text_plain(self.node.get_uri(), ctx) # TODO: urlencode
157         if t == "uri":
158             if not replace:
159                 # they're trying to set_uri and that name is already occupied
160                 # (by us).
161                 raise ExistingChildError()
162             d = self.parentnode.replace_me_with_a_childcap(ctx, replace)
163             # TODO: results
164             return d
165
166         raise WebError("PUT to a directory")
167
168     def render_POST(self, ctx):
169         req = IRequest(ctx)
170         t = get_arg(req, "t", "").strip()
171
172         if t == "mkdir":
173             d = self._POST_mkdir(req)
174         elif t == "mkdir-p":
175             # TODO: docs, tests
176             d = self._POST_mkdir_p(req)
177         elif t == "upload":
178             d = self._POST_upload(ctx) # this one needs the context
179         elif t == "uri":
180             d = self._POST_uri(req)
181         elif t == "delete":
182             d = self._POST_delete(req)
183         elif t == "rename":
184             d = self._POST_rename(req)
185         elif t == "check":
186             d = self._POST_check(req)
187         elif t == "start-deep-check":
188             d = self._POST_start_deep_check(ctx)
189         elif t == "start-manifest":
190             d = self._POST_start_manifest(ctx)
191         elif t == "start-deep-size":
192             d = self._POST_start_deep_size(ctx)
193         elif t == "start-deep-stats":
194             d = self._POST_start_deep_stats(ctx)
195         elif t == "set_children":
196             # TODO: docs
197             d = self._POST_set_children(req)
198         else:
199             raise WebError("POST to a directory with bad t=%s" % t)
200
201         when_done = get_arg(req, "when_done", None)
202         if when_done:
203             d.addCallback(lambda res: url.URL.fromString(when_done))
204         return d
205
206     def _POST_mkdir(self, req):
207         name = get_arg(req, "name", "")
208         if not name:
209             # our job is done, it was handled by the code in got_child
210             # which created the final directory (i.e. us)
211             return defer.succeed(self.node.get_uri()) # TODO: urlencode
212         name = name.decode("utf-8")
213         replace = boolean_of_arg(get_arg(req, "replace", "true"))
214         d = self.node.create_empty_directory(name, overwrite=replace)
215         d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
216         return d
217
218     def _POST_mkdir_p(self, req):
219         path = get_arg(req, "path")
220         if not path:
221             raise WebError("mkdir-p requires a path")
222         path_ = tuple([seg.decode("utf-8") for seg in path.split('/') if seg ])
223         # TODO: replace
224         d = self._get_or_create_directories(self.node, path_)
225         d.addCallback(lambda node: node.get_uri())
226         return d
227
228     def _get_or_create_directories(self, node, path):
229         if not IDirectoryNode.providedBy(node):
230             # unfortunately it is too late to provide the name of the
231             # blocking directory in the error message.
232             raise BlockingFileError("cannot create directory because there "
233                                     "is a file in the way")
234         if not path:
235             return defer.succeed(node)
236         d = node.get(path[0])
237         def _maybe_create(f):
238             f.trap(KeyError)
239             return node.create_empty_directory(path[0])
240         d.addErrback(_maybe_create)
241         d.addCallback(self._get_or_create_directories, path[1:])
242         return d
243
244     def _POST_upload(self, ctx):
245         req = IRequest(ctx)
246         charset = get_arg(req, "_charset", "utf-8")
247         contents = req.fields["file"]
248         assert contents.filename is None or isinstance(contents.filename, str)
249         name = get_arg(req, "name")
250         name = name or contents.filename
251         if name is not None:
252             name = name.strip()
253         if not name:
254             # this prohibts empty, missing, and all-whitespace filenames
255             raise WebError("upload requires a name")
256         assert isinstance(name, str)
257         name = name.decode(charset)
258         if "/" in name:
259             raise WebError("name= may not contain a slash", http.BAD_REQUEST)
260         assert isinstance(name, unicode)
261
262         # since POST /uri/path/file?t=upload is equivalent to
263         # POST /uri/path/dir?t=upload&name=foo, just do the same thing that
264         # childFactory would do. Things are cleaner if we only do a subset of
265         # them, though, so we don't do: d = self.childFactory(ctx, name)
266
267         d = self.node.get(name)
268         def _maybe_got_node(node_or_failure):
269             if isinstance(node_or_failure, Failure):
270                 f = node_or_failure
271                 f.trap(KeyError)
272                 # create a placeholder which will see POST t=upload
273                 return PlaceHolderNodeHandler(self.node, name)
274             else:
275                 node = node_or_failure
276                 return make_handler_for(node, self.node, name)
277         d.addBoth(_maybe_got_node)
278         # now we have a placeholder or a filenodehandler, and we can just
279         # delegate to it. We could return the resource back out of
280         # DirectoryNodeHandler.renderHTTP, and nevow would recurse into it,
281         # but the addCallback() that handles when_done= would break.
282         d.addCallback(lambda child: child.renderHTTP(ctx))
283         return d
284
285     def _POST_uri(self, req):
286         childcap = get_arg(req, "uri")
287         if not childcap:
288             raise WebError("set-uri requires a uri")
289         name = get_arg(req, "name")
290         if not name:
291             raise WebError("set-uri requires a name")
292         charset = get_arg(req, "_charset", "utf-8")
293         name = name.decode(charset)
294         replace = boolean_of_arg(get_arg(req, "replace", "true"))
295         d = self.node.set_uri(name, childcap, overwrite=replace)
296         d.addCallback(lambda res: childcap)
297         return d
298
299     def _POST_delete(self, req):
300         name = get_arg(req, "name")
301         if name is None:
302             # apparently an <input type="hidden" name="name" value="">
303             # won't show up in the resulting encoded form.. the 'name'
304             # field is completely missing. So to allow deletion of an
305             # empty file, we have to pretend that None means ''. The only
306             # downide of this is a slightly confusing error message if
307             # someone does a POST without a name= field. For our own HTML
308             # thisn't a big deal, because we create the 'delete' POST
309             # buttons ourselves.
310             name = ''
311         charset = get_arg(req, "_charset", "utf-8")
312         name = name.decode(charset)
313         d = self.node.delete(name)
314         d.addCallback(lambda res: "thing deleted")
315         return d
316
317     def _POST_rename(self, req):
318         charset = get_arg(req, "_charset", "utf-8")
319         from_name = get_arg(req, "from_name")
320         if from_name is not None:
321             from_name = from_name.strip()
322             from_name = from_name.decode(charset)
323             assert isinstance(from_name, unicode)
324         to_name = get_arg(req, "to_name")
325         if to_name is not None:
326             to_name = to_name.strip()
327             to_name = to_name.decode(charset)
328             assert isinstance(to_name, unicode)
329         if not from_name or not to_name:
330             raise WebError("rename requires from_name and to_name")
331         if from_name == to_name:
332             return defer.succeed("redundant rename")
333
334         # allow from_name to contain slashes, so they can fix names that were
335         # accidentally created with them. But disallow them in to_name, to
336         # discourage the practice.
337         if "/" in to_name:
338             raise WebError("to_name= may not contain a slash", http.BAD_REQUEST)
339
340         replace = boolean_of_arg(get_arg(req, "replace", "true"))
341         d = self.node.move_child_to(from_name, self.node, to_name, replace)
342         d.addCallback(lambda res: "thing renamed")
343         return d
344
345     def _POST_check(self, req):
346         # check this directory
347         verify = boolean_of_arg(get_arg(req, "verify", "false"))
348         repair = boolean_of_arg(get_arg(req, "repair", "false"))
349         if repair:
350             d = self.node.check_and_repair(Monitor(), verify)
351             d.addCallback(lambda res: CheckAndRepairResults(res))
352         else:
353             d = self.node.check(Monitor(), verify)
354             d.addCallback(lambda res: CheckerResults(res))
355         return d
356
357     def _start_operation(self, monitor, renderer, ctx):
358         table = IOpHandleTable(ctx)
359         table.add_monitor(ctx, monitor, renderer)
360         return table.redirect_to(ctx)
361
362     def _POST_start_deep_check(self, ctx):
363         # check this directory and everything reachable from it
364         if not get_arg(ctx, "ophandle"):
365             raise NeedOperationHandleError("slow operation requires ophandle=")
366         verify = boolean_of_arg(get_arg(ctx, "verify", "false"))
367         repair = boolean_of_arg(get_arg(ctx, "repair", "false"))
368         if repair:
369             monitor = self.node.start_deep_check_and_repair(verify)
370             renderer = DeepCheckAndRepairResults(monitor)
371         else:
372             monitor = self.node.start_deep_check(verify)
373             renderer = DeepCheckResults(monitor)
374         return self._start_operation(monitor, renderer, ctx)
375
376     def _POST_start_manifest(self, ctx):
377         if not get_arg(ctx, "ophandle"):
378             raise NeedOperationHandleError("slow operation requires ophandle=")
379         monitor = self.node.build_manifest()
380         renderer = ManifestResults(monitor)
381         return self._start_operation(monitor, renderer, ctx)
382
383     def _POST_start_deep_size(self, ctx):
384         if not get_arg(ctx, "ophandle"):
385             raise NeedOperationHandleError("slow operation requires ophandle=")
386         monitor = self.node.start_deep_stats()
387         renderer = DeepSizeResults(monitor)
388         return self._start_operation(monitor, renderer, ctx)
389
390     def _POST_start_deep_stats(self, ctx):
391         if not get_arg(ctx, "ophandle"):
392             raise NeedOperationHandleError("slow operation requires ophandle=")
393         monitor = self.node.start_deep_stats()
394         renderer = DeepStatsResults(monitor)
395         return self._start_operation(monitor, renderer, ctx)
396
397     def _POST_set_children(self, req):
398         replace = boolean_of_arg(get_arg(req, "replace", "true"))
399         req.content.seek(0)
400         body = req.content.read()
401         try:
402             children = simplejson.loads(body)
403         except ValueError, le:
404             le.args = tuple(le.args + (body,))
405             # TODO test handling of bad JSON
406             raise
407         cs = []
408         for name, (file_or_dir, mddict) in children.iteritems():
409             name = unicode(name) # simplejson-2.0.1 returns str *or* unicode
410             cap = str(mddict.get('rw_uri') or mddict.get('ro_uri'))
411             cs.append((name, cap, mddict.get('metadata')))
412         d = self.node.set_children(cs, replace)
413         d.addCallback(lambda res: "Okay so I did it.")
414         # TODO: results
415         return d
416
417 def abbreviated_dirnode(dirnode):
418     u = from_string_dirnode(dirnode.get_uri())
419     si = u.get_filenode_uri().storage_index
420     si_s = base32.b2a(si)
421     return si_s[:6]
422
423 class DirectoryAsHTML(rend.Page):
424     # The remainder of this class is to render the directory into
425     # human+browser -oriented HTML.
426     docFactory = getxmlfile("directory.xhtml")
427     addSlash = True
428
429     def __init__(self, node):
430         rend.Page.__init__(self)
431         self.node = node
432
433     def render_title(self, ctx, data):
434         si_s = abbreviated_dirnode(self.node)
435         header = ["Directory SI=%s" % si_s]
436         return ctx.tag[header]
437
438     def render_header(self, ctx, data):
439         si_s = abbreviated_dirnode(self.node)
440         header = ["Directory SI=%s" % si_s]
441         if self.node.is_readonly():
442             header.append(" (readonly)")
443         return ctx.tag[header]
444
445     def render_welcome(self, ctx, data):
446         link = get_root(ctx)
447         return T.div[T.a(href=link)["Return to Welcome page"]]
448
449     def data_children(self, ctx, data):
450         d = self.node.list()
451         d.addCallback(lambda dict: sorted(dict.items()))
452         def _stall_some(items):
453             # Deferreds don't optimize out tail recursion, and the way
454             # Nevow's flattener handles Deferreds doesn't take this into
455             # account. As a result, large lists of Deferreds that fire in the
456             # same turn (i.e. the output of defer.succeed) will cause a stack
457             # overflow. To work around this, we insert a turn break after
458             # every 100 items, using foolscap's fireEventually(). This gives
459             # the stack a chance to be popped. It would also work to put
460             # every item in its own turn, but that'd be a lot more
461             # inefficient. This addresses ticket #237, for which I was never
462             # able to create a failing unit test.
463             output = []
464             for i,item in enumerate(items):
465                 if i % 100 == 0:
466                     output.append(fireEventually(item))
467                 else:
468                     output.append(item)
469             return output
470         d.addCallback(_stall_some)
471         return d
472
473     def render_row(self, ctx, data):
474         name, (target, metadata) = data
475         name = name.encode("utf-8")
476         assert not isinstance(name, unicode)
477         nameurl = urllib.quote(name, safe="") # encode any slashes too
478
479         root = get_root(ctx)
480         here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
481         if self.node.is_readonly():
482             delete = "-"
483             rename = "-"
484         else:
485             # this creates a button which will cause our child__delete method
486             # to be invoked, which deletes the file and then redirects the
487             # browser back to this directory
488             delete = T.form(action=here, method="post")[
489                 T.input(type='hidden', name='t', value='delete'),
490                 T.input(type='hidden', name='name', value=name),
491                 T.input(type='hidden', name='when_done', value="."),
492                 T.input(type='submit', value='del', name="del"),
493                 ]
494
495             rename = T.form(action=here, method="get")[
496                 T.input(type='hidden', name='t', value='rename-form'),
497                 T.input(type='hidden', name='name', value=name),
498                 T.input(type='hidden', name='when_done', value="."),
499                 T.input(type='submit', value='rename', name="rename"),
500                 ]
501
502         ctx.fillSlots("delete", delete)
503         ctx.fillSlots("rename", rename)
504
505         times = []
506         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
507         if "ctime" in metadata:
508             ctime = time.strftime(TIME_FORMAT,
509                                   time.localtime(metadata["ctime"]))
510             times.append("c: " + ctime)
511         if "mtime" in metadata:
512             mtime = time.strftime(TIME_FORMAT,
513                                   time.localtime(metadata["mtime"]))
514             if times:
515                 times.append(T.br())
516                 times.append("m: " + mtime)
517         ctx.fillSlots("times", times)
518
519         assert (IFileNode.providedBy(target)
520                 or IDirectoryNode.providedBy(target)
521                 or IMutableFileNode.providedBy(target)), target
522
523         quoted_uri = urllib.quote(target.get_uri())
524
525         if IMutableFileNode.providedBy(target):
526             # to prevent javascript in displayed .html files from stealing a
527             # secret directory URI from the URL, send the browser to a URI-based
528             # page that doesn't know about the directory at all
529             dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
530
531             ctx.fillSlots("filename",
532                           T.a(href=dlurl)[html.escape(name)])
533             ctx.fillSlots("type", "SSK")
534
535             ctx.fillSlots("size", "?")
536
537             text_plain_url = "%s/file/%s/@@named=/foo.txt" % (root, quoted_uri)
538             info_link = "%s?t=info" % nameurl
539
540         elif IFileNode.providedBy(target):
541             dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
542
543             ctx.fillSlots("filename",
544                           T.a(href=dlurl)[html.escape(name)])
545             ctx.fillSlots("type", "FILE")
546
547             ctx.fillSlots("size", target.get_size())
548
549             text_plain_url = "%s/file/%s/@@named=/foo.txt" % (root, quoted_uri)
550             info_link = "%s?t=info" % nameurl
551
552         elif IDirectoryNode.providedBy(target):
553             # directory
554             uri_link = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
555             ctx.fillSlots("filename",
556                           T.a(href=uri_link)[html.escape(name)])
557             if target.is_readonly():
558                 dirtype = "DIR-RO"
559             else:
560                 dirtype = "DIR"
561             ctx.fillSlots("type", dirtype)
562             ctx.fillSlots("size", "-")
563             info_link = "%s/?t=info" % nameurl
564
565         ctx.fillSlots("info", T.a(href=info_link)["More Info"])
566
567         return ctx.tag
568
569     def render_forms(self, ctx, data):
570         forms = []
571
572         if self.node.is_readonly():
573             forms.append(T.div["No upload forms: directory is read-only"])
574             return forms
575
576         mkdir = T.form(action=".", method="post",
577                        enctype="multipart/form-data")[
578             T.fieldset[
579             T.input(type="hidden", name="t", value="mkdir"),
580             T.input(type="hidden", name="when_done", value="."),
581             T.legend(class_="freeform-form-label")["Create a new directory"],
582             "New directory name: ",
583             T.input(type="text", name="name"), " ",
584             T.input(type="submit", value="Create"),
585             ]]
586         forms.append(T.div(class_="freeform-form")[mkdir])
587
588         upload = T.form(action=".", method="post",
589                         enctype="multipart/form-data")[
590             T.fieldset[
591             T.input(type="hidden", name="t", value="upload"),
592             T.input(type="hidden", name="when_done", value="."),
593             T.legend(class_="freeform-form-label")["Upload a file to this directory"],
594             "Choose a file to upload: ",
595             T.input(type="file", name="file", class_="freeform-input-file"),
596             " ",
597             T.input(type="submit", value="Upload"),
598             " Mutable?:",
599             T.input(type="checkbox", name="mutable"),
600             ]]
601         forms.append(T.div(class_="freeform-form")[upload])
602
603         mount = T.form(action=".", method="post",
604                         enctype="multipart/form-data")[
605             T.fieldset[
606             T.input(type="hidden", name="t", value="uri"),
607             T.input(type="hidden", name="when_done", value="."),
608             T.legend(class_="freeform-form-label")["Attach a file or directory"
609                                                    " (by URI) to this"
610                                                    " directory"],
611             "New child name: ",
612             T.input(type="text", name="name"), " ",
613             "URI of new child: ",
614             T.input(type="text", name="uri"), " ",
615             T.input(type="submit", value="Attach"),
616             ]]
617         forms.append(T.div(class_="freeform-form")[mount])
618         return forms
619
620     def render_results(self, ctx, data):
621         req = IRequest(ctx)
622         return get_arg(req, "results", "")
623
624
625 def DirectoryJSONMetadata(ctx, dirnode):
626     d = dirnode.list()
627     def _got(children):
628         kids = {}
629         for name, (childnode, metadata) in children.iteritems():
630             if childnode.is_readonly():
631                 rw_uri = None
632                 ro_uri = childnode.get_uri()
633             else:
634                 rw_uri = childnode.get_uri()
635                 ro_uri = childnode.get_readonly_uri()
636             if IFileNode.providedBy(childnode):
637                 kiddata = ("filenode", {'size': childnode.get_size(),
638                                         'metadata': metadata,
639                                         })
640             else:
641                 assert IDirectoryNode.providedBy(childnode), (childnode,
642                                                               children,)
643                 kiddata = ("dirnode", {'metadata': metadata})
644             if ro_uri:
645                 kiddata[1]["ro_uri"] = ro_uri
646             if rw_uri:
647                 kiddata[1]["rw_uri"] = rw_uri
648             kiddata[1]['mutable'] = childnode.is_mutable()
649             kids[name] = kiddata
650         if dirnode.is_readonly():
651             drw_uri = None
652             dro_uri = dirnode.get_uri()
653         else:
654             drw_uri = dirnode.get_uri()
655             dro_uri = dirnode.get_readonly_uri()
656         contents = { 'children': kids }
657         if dro_uri:
658             contents['ro_uri'] = dro_uri
659         if drw_uri:
660             contents['rw_uri'] = drw_uri
661         contents['mutable'] = dirnode.is_mutable()
662         data = ("dirnode", contents)
663         return simplejson.dumps(data, indent=1) + "\n"
664     d.addCallback(_got)
665     d.addCallback(text_plain, ctx)
666     return d
667
668
669
670 def DirectoryURI(ctx, dirnode):
671     return text_plain(dirnode.get_uri(), ctx)
672
673 def DirectoryReadonlyURI(ctx, dirnode):
674     return text_plain(dirnode.get_readonly_uri(), ctx)
675
676 class RenameForm(rend.Page):
677     addSlash = True
678     docFactory = getxmlfile("rename-form.xhtml")
679
680     def render_title(self, ctx, data):
681         return ctx.tag["Directory SI=%s" % abbreviated_dirnode(self.original)]
682
683     def render_header(self, ctx, data):
684         header = ["Rename "
685                   "in directory SI=%s" % abbreviated_dirnode(self.original),
686                   ]
687
688         if self.original.is_readonly():
689             header.append(" (readonly!)")
690         header.append(":")
691         return ctx.tag[header]
692
693     def render_when_done(self, ctx, data):
694         return T.input(type="hidden", name="when_done", value=".")
695
696     def render_get_name(self, ctx, data):
697         req = IRequest(ctx)
698         name = get_arg(req, "name", "")
699         ctx.tag.attributes['value'] = name
700         return ctx.tag
701
702
703 class ManifestResults(rend.Page, ReloadMixin):
704     docFactory = getxmlfile("manifest.xhtml")
705
706     def __init__(self, monitor):
707         self.monitor = monitor
708
709     def renderHTTP(self, ctx):
710         output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
711         if output == "text":
712             return self.text(ctx)
713         if output == "json":
714             return self.json(ctx)
715         return rend.Page.renderHTTP(self, ctx)
716
717     def slashify_path(self, path):
718         if not path:
719             return ""
720         return "/".join([p.encode("utf-8") for p in path])
721
722     def text(self, ctx):
723         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
724         lines = []
725         is_finished = self.monitor.is_finished()
726         lines.append("finished: " + {True: "yes", False: "no"}[is_finished])
727         for (path, cap) in self.monitor.get_status():
728             lines.append(self.slashify_path(path) + " " + cap)
729         return "\n".join(lines) + "\n"
730
731     def json(self, ctx):
732         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
733         m = self.monitor
734         status = {"manifest": m.get_status(),
735                   "finished": m.is_finished(),
736                   "origin": base32.b2a(m.origin_si),
737                   }
738         return simplejson.dumps(status, indent=1)
739
740     def _si_abbrev(self):
741         return base32.b2a(self.monitor.origin_si)[:6]
742
743     def render_title(self, ctx):
744         return T.title["Manifest of SI=%s" % self._si_abbrev()]
745
746     def render_header(self, ctx):
747         return T.p["Manifest of SI=%s" % self._si_abbrev()]
748
749     def data_items(self, ctx, data):
750         return self.monitor.get_status()
751
752     def render_row(self, ctx, (path, cap)):
753         ctx.fillSlots("path", self.slashify_path(path))
754         root = get_root(ctx)
755         # TODO: we need a clean consistent way to get the type of a cap string
756         if cap.startswith("URI:CHK") or cap.startswith("URI:SSK"):
757             nameurl = urllib.quote(path[-1].encode("utf-8"))
758             uri_link = "%s/file/%s/@@named=/%s" % (root, urllib.quote(cap),
759                                                    nameurl)
760         else:
761             uri_link = "%s/uri/%s" % (root, urllib.quote(cap))
762         ctx.fillSlots("cap", T.a(href=uri_link)[cap])
763         return ctx.tag
764
765 class DeepSizeResults(rend.Page):
766     def __init__(self, monitor):
767         self.monitor = monitor
768
769     def renderHTTP(self, ctx):
770         output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
771         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
772         if output == "json":
773             return self.json(ctx)
774         # plain text
775         is_finished = self.monitor.is_finished()
776         output = "finished: " + {True: "yes", False: "no"}[is_finished] + "\n"
777         if is_finished:
778             stats = self.monitor.get_status()
779             total = (stats.get("size-immutable-files", 0)
780                      + stats.get("size-mutable-files", 0)
781                      + stats.get("size-directories", 0))
782             output += "size: %d\n" % total
783         return output
784
785     def json(self, ctx):
786         status = {"finished": self.monitor.is_finished(),
787                   "size": self.monitor.get_status(),
788                   }
789         return simplejson.dumps(status)
790
791 class DeepStatsResults(rend.Page):
792     def __init__(self, monitor):
793         self.monitor = monitor
794
795     def renderHTTP(self, ctx):
796         # JSON only
797         inevow.IRequest(ctx).setHeader("content-type", "text/plain")
798         s = self.monitor.get_status().copy()
799         s["finished"] = self.monitor.is_finished()
800         return simplejson.dumps(s, indent=1)