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