import simplejson
import urllib
-import time
+from zope.interface import implements
from twisted.internet import defer
+from twisted.internet.interfaces import IPushProducer
from twisted.python.failure import Failure
-from twisted.web import http, html
+from twisted.web import http
from nevow import url, rend, inevow, tags as T
from nevow.inevow import IRequest
-from foolscap.eventual import fireEventually
+from foolscap.api import fireEventually
from allmydata.util import base32
+from allmydata.util.encodingutil import to_str
from allmydata.uri import from_string_dirnode
-from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \
- ExistingChildError, NoSuchChildError
-from allmydata.monitor import Monitor
+from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
+ IImmutableFileNode, IMutableFileNode, ExistingChildError, \
+ NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
+from allmydata.blacklist import ProhibitedNode
+from allmydata.monitor import Monitor, OperationCancelledError
+from allmydata import dirnode
from allmydata.web.common import text_plain, WebError, \
- IClient, IOpHandleTable, NeedOperationHandleError, \
- boolean_of_arg, get_arg, get_root, \
+ IOpHandleTable, NeedOperationHandleError, \
+ boolean_of_arg, get_arg, get_root, parse_replace_arg, \
should_create_intermediate_directories, \
- getxmlfile, RenderMixin
+ getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
+ get_format, get_mutable_type, get_filenode_metadata, render_time
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
-from allmydata.web.checker_results import CheckerResults, \
- CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults
+from allmydata.web.check_results import CheckResultsRenderer, \
+ CheckAndRepairResultsRenderer, DeepCheckResultsRenderer, \
+ DeepCheckAndRepairResultsRenderer, LiteralCheckResultsRenderer
from allmydata.web.info import MoreInfo
from allmydata.web.operations import ReloadMixin
+from allmydata.web.check_results import json_check_results, \
+ json_check_and_repair_results
class BlockingFileError(Exception):
# TODO: catch and transform
"""We cannot auto-create a parent directory, because there is a file in
the way"""
-def make_handler_for(node, parentnode=None, name=None):
+def make_handler_for(node, client, parentnode=None, name=None):
if parentnode:
assert IDirectoryNode.providedBy(parentnode)
- if IMutableFileNode.providedBy(node):
- return FileNodeHandler(node, parentnode, name)
if IFileNode.providedBy(node):
- return FileNodeHandler(node, parentnode, name)
+ return FileNodeHandler(client, node, parentnode, name)
if IDirectoryNode.providedBy(node):
- return DirectoryNodeHandler(node, parentnode, name)
- raise WebError("Cannot provide handler for '%s'" % node)
+ return DirectoryNodeHandler(client, node, parentnode, name)
+ return UnknownNodeHandler(client, node, parentnode, name)
class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
addSlash = True
- def __init__(self, node, parentnode=None, name=None):
+ def __init__(self, client, node, parentnode=None, name=None):
rend.Page.__init__(self)
+ self.client = client
assert node
self.node = node
self.parentnode = parentnode
self.name = name
def childFactory(self, ctx, name):
- req = IRequest(ctx)
name = name.decode("utf-8")
+ if not name:
+ raise EmptyPathnameComponentError()
d = self.node.get(name)
d.addBoth(self.got_child, ctx, name)
# got_child returns a handler resource: FileNodeHandler or
if should_create_intermediate_directories(req):
# create intermediate directories
if DEBUG: print " making intermediate directory"
- d = self.node.create_empty_directory(name)
- d.addCallback(make_handler_for, self.node, name)
+ d = self.node.create_subdirectory(name)
+ d.addCallback(make_handler_for,
+ self.client, self.node, name)
return d
else:
if DEBUG: print " terminal"
# terminal node
- if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir") ]:
+ if (method,t) in [ ("POST","mkdir"), ("PUT","mkdir"),
+ ("POST", "mkdir-with-children"),
+ ("POST", "mkdir-immutable") ]:
if DEBUG: print " making final directory"
# final directory
- d = self.node.create_empty_directory(name)
- d.addCallback(make_handler_for, self.node, name)
+ kids = {}
+ if t in ("mkdir-with-children", "mkdir-immutable"):
+ req.content.seek(0)
+ kids_json = req.content.read()
+ kids = convert_children_json(self.client.nodemaker,
+ kids_json)
+ file_format = get_format(req, None)
+ mutable = True
+ mt = get_mutable_type(file_format)
+ if t == "mkdir-immutable":
+ mutable = False
+
+ d = self.node.create_subdirectory(name, kids,
+ mutable=mutable,
+ mutable_version=mt)
+ d.addCallback(make_handler_for,
+ self.client, self.node, name)
return d
if (method,t) in ( ("PUT",""), ("PUT","uri"), ):
if DEBUG: print " PUT, making leaf placeholder"
# since that's the leaf node that we're about to create.
# We make a dummy one, which will respond to the PUT
# request by replacing itself.
- return PlaceHolderNodeHandler(self.node, name)
+ return PlaceHolderNodeHandler(self.client, self.node, name)
if DEBUG: print " 404"
# otherwise, we just return a no-such-child error
- return rend.FourOhFour()
+ return f
node = node_or_failure
if nonterminal and should_create_intermediate_directories(req):
"a file was in the way" % name,
http.CONFLICT)
if DEBUG: print "good child"
- return make_handler_for(node, self.node, name)
+ return make_handler_for(node, self.client, self.node, name)
def render_DELETE(self, ctx):
assert self.parentnode and self.name
return d
def render_GET(self, ctx):
- client = IClient(ctx)
req = IRequest(ctx)
# This is where all of the directory-related ?t=* code goes.
t = get_arg(req, "t", "").strip()
+
+ # t=info contains variable ophandles, t=rename-form contains the name
+ # of the child being renamed. Neither is allowed an ETag.
+ FIXED_OUTPUT_TYPES = ["", "json", "uri", "readonly-uri"]
+ if not self.node.is_mutable() and t in FIXED_OUTPUT_TYPES:
+ si = self.node.get_storage_index()
+ if si and req.setETag('DIR:%s-%s' % (base32.b2a(si), t or "")):
+ return ""
+
if not t:
# render the directory as HTML, using the docFactory and Nevow's
# whole templating thing.
- return DirectoryAsHTML(self.node)
+ return DirectoryAsHTML(self.node,
+ self.client.mutable_file_default)
if t == "json":
return DirectoryJSONMetadata(ctx, self.node)
def render_PUT(self, ctx):
req = IRequest(ctx)
t = get_arg(req, "t", "").strip()
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
+
if t == "mkdir":
# our job was done by the traversal/create-intermediate-directory
# process that got us here.
# they're trying to set_uri and that name is already occupied
# (by us).
raise ExistingChildError()
- d = self.replace_me_with_a_childcap(ctx, replace)
+ d = self.replace_me_with_a_childcap(req, self.client, replace)
# TODO: results
return d
if t == "mkdir":
d = self._POST_mkdir(req)
- elif t == "mkdir-p":
- # TODO: docs, tests
- d = self._POST_mkdir_p(req)
+ elif t == "mkdir-with-children":
+ d = self._POST_mkdir_with_children(req)
+ elif t == "mkdir-immutable":
+ d = self._POST_mkdir_immutable(req)
elif t == "upload":
d = self._POST_upload(ctx) # this one needs the context
elif t == "uri":
d = self._POST_uri(req)
- elif t == "delete":
- d = self._POST_delete(req)
+ elif t == "delete" or t == "unlink":
+ d = self._POST_unlink(req)
elif t == "rename":
d = self._POST_rename(req)
+ elif t == "relink":
+ d = self._POST_relink(req)
elif t == "check":
d = self._POST_check(req)
elif t == "start-deep-check":
d = self._POST_start_deep_check(ctx)
+ elif t == "stream-deep-check":
+ d = self._POST_stream_deep_check(ctx)
elif t == "start-manifest":
d = self._POST_start_manifest(ctx)
elif t == "start-deep-size":
d = self._POST_start_deep_size(ctx)
elif t == "start-deep-stats":
d = self._POST_start_deep_stats(ctx)
- elif t == "set_children":
- # TODO: docs
+ elif t == "stream-manifest":
+ d = self._POST_stream_manifest(ctx)
+ elif t == "set_children" or t == "set-children":
d = self._POST_set_children(req)
else:
raise WebError("POST to a directory with bad t=%s" % t)
return defer.succeed(self.node.get_uri()) # TODO: urlencode
name = name.decode("utf-8")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.create_empty_directory(name, overwrite=replace)
+ kids = {}
+ mt = get_mutable_type(get_format(req, None))
+ d = self.node.create_subdirectory(name, kids, overwrite=replace,
+ mutable_version=mt)
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
- def _POST_mkdir_p(self, req):
- path = get_arg(req, "path")
- if not path:
- raise WebError("mkdir-p requires a path")
- path_ = tuple([seg.decode("utf-8") for seg in path.split('/') if seg ])
- # TODO: replace
- d = self._get_or_create_directories(self.node, path_)
- d.addCallback(lambda node: node.get_uri())
+ def _POST_mkdir_with_children(self, req):
+ name = get_arg(req, "name", "")
+ if not name:
+ # our job is done, it was handled by the code in got_child
+ # which created the final directory (i.e. us)
+ return defer.succeed(self.node.get_uri()) # TODO: urlencode
+ name = name.decode("utf-8")
+ # TODO: decide on replace= behavior, see #903
+ #replace = boolean_of_arg(get_arg(req, "replace", "false"))
+ req.content.seek(0)
+ kids_json = req.content.read()
+ kids = convert_children_json(self.client.nodemaker, kids_json)
+ mt = get_mutable_type(get_format(req, None))
+ d = self.node.create_subdirectory(name, kids, overwrite=False,
+ mutable_version=mt)
+ d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
- def _get_or_create_directories(self, node, path):
- if not IDirectoryNode.providedBy(node):
- # unfortunately it is too late to provide the name of the
- # blocking directory in the error message.
- raise BlockingFileError("cannot create directory because there "
- "is a file in the way")
- if not path:
- return defer.succeed(node)
- d = node.get(path[0])
- def _maybe_create(f):
- f.trap(NoSuchChildError)
- return node.create_empty_directory(path[0])
- d.addErrback(_maybe_create)
- d.addCallback(self._get_or_create_directories, path[1:])
+ def _POST_mkdir_immutable(self, req):
+ name = get_arg(req, "name", "")
+ if not name:
+ # our job is done, it was handled by the code in got_child
+ # which created the final directory (i.e. us)
+ return defer.succeed(self.node.get_uri()) # TODO: urlencode
+ name = name.decode("utf-8")
+ # TODO: decide on replace= behavior, see #903
+ #replace = boolean_of_arg(get_arg(req, "replace", "false"))
+ req.content.seek(0)
+ kids_json = req.content.read()
+ kids = convert_children_json(self.client.nodemaker, kids_json)
+ d = self.node.create_subdirectory(name, kids, overwrite=False, mutable=False)
+ d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
return d
def _POST_upload(self, ctx):
f = node_or_failure
f.trap(NoSuchChildError)
# create a placeholder which will see POST t=upload
- return PlaceHolderNodeHandler(self.node, name)
+ return PlaceHolderNodeHandler(self.client, self.node, name)
else:
node = node_or_failure
- return make_handler_for(node, self.node, name)
+ return make_handler_for(node, self.client, self.node, name)
d.addBoth(_maybe_got_node)
# now we have a placeholder or a filenodehandler, and we can just
# delegate to it. We could return the resource back out of
raise WebError("set-uri requires a name")
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.set_uri(name, childcap, overwrite=replace)
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
+
+ # We mustn't pass childcap for the readcap argument because we don't
+ # know whether it is a read cap. Passing a read cap as the writecap
+ # argument will work (it ends up calling NodeMaker.create_from_cap,
+ # which derives a readcap if necessary and possible).
+ d = self.node.set_uri(name, childcap, None, overwrite=replace)
d.addCallback(lambda res: childcap)
return d
- def _POST_delete(self, req):
+ def _POST_unlink(self, req):
name = get_arg(req, "name")
if name is None:
# apparently an <input type="hidden" name="name" value="">
# won't show up in the resulting encoded form.. the 'name'
- # field is completely missing. So to allow deletion of an
- # empty file, we have to pretend that None means ''. The only
- # downide of this is a slightly confusing error message if
- # someone does a POST without a name= field. For our own HTML
- # thisn't a big deal, because we create the 'delete' POST
- # buttons ourselves.
+ # field is completely missing. So to allow unlinking of a
+ # child with a name that is the empty string, we have to
+ # pretend that None means ''. The only downside of this is
+ # a slightly confusing error message if someone does a POST
+ # without a name= field. For our own HTML this isn't a big
+ # deal, because we create the 'unlink' POST buttons ourselves.
name = ''
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
d = self.node.delete(name)
- d.addCallback(lambda res: "thing deleted")
+ d.addCallback(lambda res: "thing unlinked")
return d
def _POST_rename(self, req):
+ # rename is identical to relink, but to_dir is not allowed
+ # and to_name is required.
+ if get_arg(req, "to_dir") is not None:
+ raise WebError("to_dir= is not valid for rename")
+ if get_arg(req, "to_name") is None:
+ raise WebError("to_name= is required for rename")
+ return self._POST_relink(req)
+
+ def _POST_relink(self, req):
charset = get_arg(req, "_charset", "utf-8")
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
+
from_name = get_arg(req, "from_name")
if from_name is not None:
from_name = from_name.strip()
from_name = from_name.decode(charset)
assert isinstance(from_name, unicode)
+ else:
+ raise WebError("from_name= is required")
+
to_name = get_arg(req, "to_name")
if to_name is not None:
to_name = to_name.strip()
to_name = to_name.decode(charset)
assert isinstance(to_name, unicode)
- if not from_name or not to_name:
- raise WebError("rename requires from_name and to_name")
- if from_name == to_name:
- return defer.succeed("redundant rename")
-
- # allow from_name to contain slashes, so they can fix names that were
- # accidentally created with them. But disallow them in to_name, to
- # discourage the practice.
+ else:
+ to_name = from_name
+
+ # Disallow slashes in both from_name and to_name, that would only
+ # cause confusion.
+ if "/" in from_name:
+ raise WebError("from_name= may not contain a slash",
+ http.BAD_REQUEST)
if "/" in to_name:
- raise WebError("to_name= may not contain a slash", http.BAD_REQUEST)
+ raise WebError("to_name= may not contain a slash",
+ http.BAD_REQUEST)
+
+ to_dir = get_arg(req, "to_dir")
+ if to_dir is not None and to_dir != self.node.get_write_uri():
+ to_dir = to_dir.strip()
+ to_dir = to_dir.decode(charset)
+ assert isinstance(to_dir, unicode)
+ to_path = to_dir.split(u"/")
+ to_root = self.client.nodemaker.create_from_cap(to_str(to_path[0]))
+ if not IDirectoryNode.providedBy(to_root):
+ raise WebError("to_dir is not a directory", http.BAD_REQUEST)
+ d = to_root.get_child_at_path(to_path[1:])
+ else:
+ d = defer.succeed(self.node)
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
- d = self.node.move_child_to(from_name, self.node, to_name, replace)
- d.addCallback(lambda res: "thing renamed")
+ def _got_new_parent(new_parent):
+ if not IDirectoryNode.providedBy(new_parent):
+ raise WebError("to_dir is not a directory", http.BAD_REQUEST)
+
+ return self.node.move_child_to(from_name, new_parent,
+ to_name, replace)
+ d.addCallback(_got_new_parent)
+ d.addCallback(lambda res: "thing moved")
return d
+ def _maybe_literal(self, res, Results_Class):
+ if res:
+ return Results_Class(self.client, res)
+ return LiteralCheckResultsRenderer(self.client)
+
def _POST_check(self, req):
# check this directory
verify = boolean_of_arg(get_arg(req, "verify", "false"))
repair = boolean_of_arg(get_arg(req, "repair", "false"))
+ add_lease = boolean_of_arg(get_arg(req, "add-lease", "false"))
if repair:
- d = self.node.check_and_repair(Monitor(), verify)
- d.addCallback(lambda res: CheckAndRepairResults(res))
+ d = self.node.check_and_repair(Monitor(), verify, add_lease)
+ d.addCallback(self._maybe_literal, CheckAndRepairResultsRenderer)
else:
- d = self.node.check(Monitor(), verify)
- d.addCallback(lambda res: CheckerResults(res))
+ d = self.node.check(Monitor(), verify, add_lease)
+ d.addCallback(self._maybe_literal, CheckResultsRenderer)
return d
def _start_operation(self, monitor, renderer, ctx):
raise NeedOperationHandleError("slow operation requires ophandle=")
verify = boolean_of_arg(get_arg(ctx, "verify", "false"))
repair = boolean_of_arg(get_arg(ctx, "repair", "false"))
+ add_lease = boolean_of_arg(get_arg(ctx, "add-lease", "false"))
if repair:
- monitor = self.node.start_deep_check_and_repair(verify)
- renderer = DeepCheckAndRepairResults(monitor)
+ monitor = self.node.start_deep_check_and_repair(verify, add_lease)
+ renderer = DeepCheckAndRepairResultsRenderer(self.client, monitor)
else:
- monitor = self.node.start_deep_check(verify)
- renderer = DeepCheckResults(monitor)
+ monitor = self.node.start_deep_check(verify, add_lease)
+ renderer = DeepCheckResultsRenderer(self.client, monitor)
return self._start_operation(monitor, renderer, ctx)
+ def _POST_stream_deep_check(self, ctx):
+ verify = boolean_of_arg(get_arg(ctx, "verify", "false"))
+ repair = boolean_of_arg(get_arg(ctx, "repair", "false"))
+ add_lease = boolean_of_arg(get_arg(ctx, "add-lease", "false"))
+ walker = DeepCheckStreamer(ctx, self.node, verify, repair, add_lease)
+ monitor = self.node.deep_traverse(walker)
+ walker.setMonitor(monitor)
+ # register to hear stopProducing. The walker ignores pauseProducing.
+ IRequest(ctx).registerProducer(walker, True)
+ d = monitor.when_done()
+ def _done(res):
+ IRequest(ctx).unregisterProducer()
+ return res
+ d.addBoth(_done)
+ def _cancelled(f):
+ f.trap(OperationCancelledError)
+ return "Operation Cancelled"
+ d.addErrback(_cancelled)
+ def _error(f):
+ # signal the error as a non-JSON "ERROR:" line, plus exception
+ msg = "ERROR: %s(%s)\n" % (f.value.__class__.__name__,
+ ", ".join([str(a) for a in f.value.args]))
+ msg += str(f)
+ return msg
+ d.addErrback(_error)
+ return d
+
def _POST_start_manifest(self, ctx):
if not get_arg(ctx, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.build_manifest()
- renderer = ManifestResults(monitor)
+ renderer = ManifestResults(self.client, monitor)
return self._start_operation(monitor, renderer, ctx)
def _POST_start_deep_size(self, ctx):
if not get_arg(ctx, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.start_deep_stats()
- renderer = DeepSizeResults(monitor)
+ renderer = DeepSizeResults(self.client, monitor)
return self._start_operation(monitor, renderer, ctx)
def _POST_start_deep_stats(self, ctx):
if not get_arg(ctx, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.start_deep_stats()
- renderer = DeepStatsResults(monitor)
+ renderer = DeepStatsResults(self.client, monitor)
return self._start_operation(monitor, renderer, ctx)
+ def _POST_stream_manifest(self, ctx):
+ walker = ManifestStreamer(ctx, self.node)
+ monitor = self.node.deep_traverse(walker)
+ walker.setMonitor(monitor)
+ # register to hear stopProducing. The walker ignores pauseProducing.
+ IRequest(ctx).registerProducer(walker, True)
+ d = monitor.when_done()
+ def _done(res):
+ IRequest(ctx).unregisterProducer()
+ return res
+ d.addBoth(_done)
+ def _cancelled(f):
+ f.trap(OperationCancelledError)
+ return "Operation Cancelled"
+ d.addErrback(_cancelled)
+ def _error(f):
+ # signal the error as a non-JSON "ERROR:" line, plus exception
+ msg = "ERROR: %s(%s)\n" % (f.value.__class__.__name__,
+ ", ".join([str(a) for a in f.value.args]))
+ msg += str(f)
+ return msg
+ d.addErrback(_error)
+ return d
+
def _POST_set_children(self, req):
- replace = boolean_of_arg(get_arg(req, "replace", "true"))
+ replace = parse_replace_arg(get_arg(req, "replace", "true"))
req.content.seek(0)
body = req.content.read()
try:
le.args = tuple(le.args + (body,))
# TODO test handling of bad JSON
raise
- cs = []
+ cs = {}
for name, (file_or_dir, mddict) in children.iteritems():
name = unicode(name) # simplejson-2.0.1 returns str *or* unicode
- cap = str(mddict.get('rw_uri') or mddict.get('ro_uri'))
- cs.append((name, cap, mddict.get('metadata')))
+ writecap = mddict.get('rw_uri')
+ if writecap is not None:
+ writecap = str(writecap)
+ readcap = mddict.get('ro_uri')
+ if readcap is not None:
+ readcap = str(readcap)
+ cs[name] = (writecap, readcap, mddict.get('metadata'))
d = self.node.set_children(cs, replace)
d.addCallback(lambda res: "Okay so I did it.")
# TODO: results
def abbreviated_dirnode(dirnode):
u = from_string_dirnode(dirnode.get_uri())
- return u.abbrev()
+ return u.abbrev_si()
+
+SPACE = u"\u00A0"*2
class DirectoryAsHTML(rend.Page):
# The remainder of this class is to render the directory into
docFactory = getxmlfile("directory.xhtml")
addSlash = True
- def __init__(self, node):
+ def __init__(self, node, default_mutable_format):
rend.Page.__init__(self)
self.node = node
- def render_title(self, ctx, data):
- si_s = abbreviated_dirnode(self.node)
- header = ["Directory SI=%s" % si_s]
- return ctx.tag[header]
-
- def render_header(self, ctx, data):
- si_s = abbreviated_dirnode(self.node)
- header = ["Directory SI=%s" % si_s]
- if self.node.is_readonly():
- header.append(" (readonly)")
- return ctx.tag[header]
+ assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
+ self.default_mutable_format = default_mutable_format
- def render_welcome(self, ctx, data):
- link = get_root(ctx)
- return T.div[T.a(href=link)["Return to Welcome page"]]
-
- def data_children(self, ctx, data):
+ def beforeRender(self, ctx):
+ # attempt to get the dirnode's children, stashing them (or the
+ # failure that results) for later use
d = self.node.list()
- d.addCallback(lambda dict: sorted(dict.items()))
- def _stall_some(items):
+ def _good(children):
# Deferreds don't optimize out tail recursion, and the way
# Nevow's flattener handles Deferreds doesn't take this into
# account. As a result, large lists of Deferreds that fire in the
# inefficient. This addresses ticket #237, for which I was never
# able to create a failing unit test.
output = []
- for i,item in enumerate(items):
+ for i,item in enumerate(sorted(children.items())):
if i % 100 == 0:
output.append(fireEventually(item))
else:
output.append(item)
- return output
- d.addCallback(_stall_some)
+ self.dirnode_children = output
+ return ctx
+ def _bad(f):
+ text, code = humanize_failure(f)
+ self.dirnode_children = None
+ self.dirnode_children_error = text
+ return ctx
+ d.addCallbacks(_good, _bad)
return d
+ def render_title(self, ctx, data):
+ si_s = abbreviated_dirnode(self.node)
+ header = ["Tahoe-LAFS - Directory SI=%s" % si_s]
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
+ header.append(" (read-only)")
+ else:
+ header.append(" (modifiable)")
+ return ctx.tag[header]
+
+ def render_header(self, ctx, data):
+ si_s = abbreviated_dirnode(self.node)
+ header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]]
+ if self.node.is_unknown():
+ header.append(" (unknown)")
+ elif not self.node.is_mutable():
+ header.append(" (immutable)")
+ elif self.node.is_readonly():
+ header.append(" (read-only)")
+ return ctx.tag[header]
+
+ def render_welcome(self, ctx, data):
+ link = get_root(ctx)
+ return ctx.tag[T.a(href=link)["Return to Welcome page"]]
+
+ def render_show_readonly(self, ctx, data):
+ if self.node.is_unknown() or self.node.is_readonly():
+ return ""
+ rocap = self.node.get_readonly_uri()
+ root = get_root(ctx)
+ uri_link = "%s/uri/%s/" % (root, urllib.quote(rocap))
+ return ctx.tag[T.a(href=uri_link)["Read-Only Version"]]
+
+ def render_try_children(self, ctx, data):
+ # if the dirnode can be retrived, render a table of children.
+ # Otherwise, render an apologetic error message.
+ if self.dirnode_children is not None:
+ return ctx.tag
+ else:
+ return T.div[T.p["Error reading directory:"],
+ T.p[self.dirnode_children_error]]
+
+ def data_children(self, ctx, data):
+ return self.dirnode_children
+
def render_row(self, ctx, data):
name, (target, metadata) = data
name = name.encode("utf-8")
root = get_root(ctx)
here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri()))
- if self.node.is_readonly():
- delete = "-"
+ if self.node.is_unknown() or self.node.is_readonly():
+ unlink = "-"
rename = "-"
else:
- # this creates a button which will cause our child__delete method
- # to be invoked, which deletes the file and then redirects the
+ # this creates a button which will cause our _POST_unlink method
+ # to be invoked, which unlinks the file and then redirects the
# browser back to this directory
- delete = T.form(action=here, method="post")[
- T.input(type='hidden', name='t', value='delete'),
+ unlink = T.form(action=here, method="post")[
+ T.input(type='hidden', name='t', value='unlink'),
T.input(type='hidden', name='name', value=name),
T.input(type='hidden', name='when_done', value="."),
- T.input(type='submit', value='del', name="del"),
+ T.input(type='submit', _class='btn', value='unlink', name="unlink"),
]
rename = T.form(action=here, method="get")[
T.input(type='hidden', name='t', value='rename-form'),
T.input(type='hidden', name='name', value=name),
T.input(type='hidden', name='when_done', value="."),
- T.input(type='submit', value='rename', name="rename"),
+ T.input(type='submit', _class='btn', value='rename/relink', name="rename"),
]
- ctx.fillSlots("delete", delete)
+ ctx.fillSlots("unlink", unlink)
ctx.fillSlots("rename", rename)
times = []
- TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
- if "ctime" in metadata:
- ctime = time.strftime(TIME_FORMAT,
- time.localtime(metadata["ctime"]))
- times.append("c: " + ctime)
- if "mtime" in metadata:
- mtime = time.strftime(TIME_FORMAT,
- time.localtime(metadata["mtime"]))
+ linkcrtime = metadata.get('tahoe', {}).get("linkcrtime")
+ if linkcrtime is not None:
+ times.append("lcr: " + render_time(linkcrtime))
+ else:
+ # For backwards-compatibility with links last modified by Tahoe < 1.4.0:
+ if "ctime" in metadata:
+ ctime = render_time(metadata["ctime"])
+ times.append("c: " + ctime)
+ linkmotime = metadata.get('tahoe', {}).get("linkmotime")
+ if linkmotime is not None:
if times:
times.append(T.br())
+ times.append("lmo: " + render_time(linkmotime))
+ else:
+ # For backwards-compatibility with links last modified by Tahoe < 1.4.0:
+ if "mtime" in metadata:
+ mtime = render_time(metadata["mtime"])
+ if times:
+ times.append(T.br())
times.append("m: " + mtime)
ctx.fillSlots("times", times)
- assert (IFileNode.providedBy(target)
- or IDirectoryNode.providedBy(target)
- or IMutableFileNode.providedBy(target)), target
-
- quoted_uri = urllib.quote(target.get_uri())
+ assert IFilesystemNode.providedBy(target), target
+ target_uri = target.get_uri() or ""
+ quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too
if IMutableFileNode.providedBy(target):
# to prevent javascript in displayed .html files from stealing a
# page that doesn't know about the directory at all
dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
- ctx.fillSlots("filename",
- T.a(href=dlurl)[html.escape(name)])
+ ctx.fillSlots("filename", T.a(href=dlurl)[name])
ctx.fillSlots("type", "SSK")
ctx.fillSlots("size", "?")
info_link = "%s/uri/%s?t=info" % (root, quoted_uri)
- elif IFileNode.providedBy(target):
+ elif IImmutableFileNode.providedBy(target):
dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
- ctx.fillSlots("filename",
- T.a(href=dlurl)[html.escape(name)])
+ ctx.fillSlots("filename", T.a(href=dlurl)[name])
ctx.fillSlots("type", "FILE")
ctx.fillSlots("size", target.get_size())
elif IDirectoryNode.providedBy(target):
# directory
- uri_link = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
- ctx.fillSlots("filename",
- T.a(href=uri_link)[html.escape(name)])
- if target.is_readonly():
+ uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
+ ctx.fillSlots("filename", T.a(href=uri_link)[name])
+ if not target.is_mutable():
+ dirtype = "DIR-IMM"
+ elif target.is_readonly():
dirtype = "DIR-RO"
else:
dirtype = "DIR"
ctx.fillSlots("size", "-")
info_link = "%s/uri/%s/?t=info" % (root, quoted_uri)
- ctx.fillSlots("info", T.a(href=info_link)["More Info"])
+ elif isinstance(target, ProhibitedNode):
+ ctx.fillSlots("filename", T.strike[name])
+ if IDirectoryNode.providedBy(target.wrapped_node):
+ blacklisted_type = "DIR-BLACKLISTED"
+ else:
+ blacklisted_type = "BLACKLISTED"
+ ctx.fillSlots("type", blacklisted_type)
+ ctx.fillSlots("size", "-")
+ info_link = None
+ ctx.fillSlots("info", ["Access Prohibited:", T.br, target.reason])
+
+ else:
+ # unknown
+ ctx.fillSlots("filename", name)
+ if target.get_write_uri() is not None:
+ unknowntype = "?"
+ elif not self.node.is_mutable() or target.is_alleged_immutable():
+ unknowntype = "?-IMM"
+ else:
+ unknowntype = "?-RO"
+ ctx.fillSlots("type", unknowntype)
+ ctx.fillSlots("size", "-")
+ # use a directory-relative info link, so we can extract both the
+ # writecap and the readcap
+ info_link = "%s?t=info" % urllib.quote(name)
+
+ if info_link:
+ ctx.fillSlots("info", T.a(href=info_link)["More Info"])
return ctx.tag
+ # XXX: similar to render_upload_form and render_mkdir_form in root.py.
def render_forms(self, ctx, data):
forms = []
if self.node.is_readonly():
- forms.append(T.div["No upload forms: directory is read-only"])
- return forms
-
- mkdir = T.form(action=".", method="post",
- enctype="multipart/form-data")[
+ return T.div["No upload forms: directory is read-only"]
+ if self.dirnode_children is None:
+ return T.div["No upload forms: directory is unreadable"]
+
+ mkdir_sdmf = T.input(type='radio', name='format',
+ value='sdmf', id='mkdir-sdmf',
+ checked='checked')
+ mkdir_mdmf = T.input(type='radio', name='format',
+ value='mdmf', id='mkdir-mdmf')
+
+ mkdir_form = T.form(action=".", method="post",
+ enctype="multipart/form-data")[
T.fieldset[
T.input(type="hidden", name="t", value="mkdir"),
T.input(type="hidden", name="when_done", value="."),
- T.legend(class_="freeform-form-label")["Create a new directory"],
- "New directory name: ",
- T.input(type="text", name="name"), " ",
- T.input(type="submit", value="Create"),
+ T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
+ "New directory name:"+SPACE, T.br,
+ T.input(type="text", name="name"), SPACE,
+ T.div(class_="form-inline")[
+ mkdir_sdmf, T.label(for_='mutable-directory-sdmf')[SPACE, "SDMF"], SPACE*2,
+ mkdir_mdmf, T.label(for_='mutable-directory-mdmf')[SPACE, "MDMF (experimental)"]
+ ],
+ T.input(type="submit", class_="btn", value="Create")
]]
- forms.append(T.div(class_="freeform-form")[mkdir])
-
- upload = T.form(action=".", method="post",
- enctype="multipart/form-data")[
+ forms.append(T.div(class_="freeform-form")[mkdir_form])
+
+ upload_chk = T.input(type='radio', name='format',
+ value='chk', id='upload-chk',
+ checked='checked')
+ upload_sdmf = T.input(type='radio', name='format',
+ value='sdmf', id='upload-sdmf')
+ upload_mdmf = T.input(type='radio', name='format',
+ value='mdmf', id='upload-mdmf')
+
+ upload_form = T.form(action=".", method="post",
+ enctype="multipart/form-data")[
T.fieldset[
T.input(type="hidden", name="t", value="upload"),
T.input(type="hidden", name="when_done", value="."),
T.legend(class_="freeform-form-label")["Upload a file to this directory"],
- "Choose a file to upload: ",
- T.input(type="file", name="file", class_="freeform-input-file"),
- " ",
- T.input(type="submit", value="Upload"),
- " Mutable?:",
- T.input(type="checkbox", name="mutable"),
+ "Choose a file to upload:"+SPACE,
+ T.input(type="file", name="file", class_="freeform-input-file"), SPACE,
+ T.div(class_="form-inline")[
+ upload_chk, T.label(for_="upload-chk") [SPACE, "Immutable"], SPACE*2,
+ upload_sdmf, T.label(for_="upload-sdmf")[SPACE, "SDMF"], SPACE*2,
+ upload_mdmf, T.label(for_="upload-mdmf")[SPACE, "MDMF (experimental)"]
+ ],
+ T.input(type="submit", class_="btn", value="Upload"), SPACE*2,
]]
- forms.append(T.div(class_="freeform-form")[upload])
-
- mount = T.form(action=".", method="post",
- enctype="multipart/form-data")[
- T.fieldset[
- T.input(type="hidden", name="t", value="uri"),
- T.input(type="hidden", name="when_done", value="."),
- T.legend(class_="freeform-form-label")["Attach a file or directory"
- " (by URI) to this"
- " directory"],
- "New child name: ",
- T.input(type="text", name="name"), " ",
- "URI of new child: ",
- T.input(type="text", name="uri"), " ",
- T.input(type="submit", value="Attach"),
- ]]
- forms.append(T.div(class_="freeform-form")[mount])
+ forms.append(T.div(class_="freeform-form")[upload_form])
+
+ attach_form = T.form(action=".", method="post",
+ enctype="multipart/form-data")[
+ T.fieldset[ T.div(class_="form-inline")[
+ T.input(type="hidden", name="t", value="uri"),
+ T.input(type="hidden", name="when_done", value="."),
+ T.legend(class_="freeform-form-label")["Add a link to a file or directory which is already in Tahoe-LAFS."],
+ "New child name:"+SPACE,
+ T.input(type="text", name="name"), SPACE*2, T.br,
+ "URI of new child:"+SPACE,
+ T.input(type="text", name="uri"), SPACE,
+ T.input(type="submit", class_="btn", value="Attach"),
+ ]]]
+ forms.append(T.div(class_="freeform-form")[attach_form])
return forms
def render_results(self, ctx, data):
req = IRequest(ctx)
return get_arg(req, "results", "")
-
def DirectoryJSONMetadata(ctx, dirnode):
d = dirnode.list()
def _got(children):
kids = {}
for name, (childnode, metadata) in children.iteritems():
- if childnode.is_readonly():
- rw_uri = None
- ro_uri = childnode.get_uri()
- else:
- rw_uri = childnode.get_uri()
- ro_uri = childnode.get_readonly_uri()
+ assert IFilesystemNode.providedBy(childnode), childnode
+ rw_uri = childnode.get_write_uri()
+ ro_uri = childnode.get_readonly_uri()
if IFileNode.providedBy(childnode):
- kiddata = ("filenode", {'size': childnode.get_size(),
- 'metadata': metadata,
- })
+ kiddata = ("filenode", get_filenode_metadata(childnode))
+ elif IDirectoryNode.providedBy(childnode):
+ kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
else:
- assert IDirectoryNode.providedBy(childnode), (childnode,
- children,)
- kiddata = ("dirnode", {'metadata': metadata})
- if ro_uri:
- kiddata[1]["ro_uri"] = ro_uri
+ kiddata = ("unknown", {})
+
+ kiddata[1]["metadata"] = metadata
if rw_uri:
kiddata[1]["rw_uri"] = rw_uri
- kiddata[1]['mutable'] = childnode.is_mutable()
+ if ro_uri:
+ kiddata[1]["ro_uri"] = ro_uri
+ verifycap = childnode.get_verify_cap()
+ if verifycap:
+ kiddata[1]['verify_uri'] = verifycap.to_string()
+
kids[name] = kiddata
- if dirnode.is_readonly():
- drw_uri = None
- dro_uri = dirnode.get_uri()
- else:
- drw_uri = dirnode.get_uri()
- dro_uri = dirnode.get_readonly_uri()
+
+ drw_uri = dirnode.get_write_uri()
+ dro_uri = dirnode.get_readonly_uri()
contents = { 'children': kids }
if dro_uri:
contents['ro_uri'] = dro_uri
if drw_uri:
contents['rw_uri'] = drw_uri
+ verifycap = dirnode.get_verify_cap()
+ if verifycap:
+ contents['verify_uri'] = verifycap.to_string()
contents['mutable'] = dirnode.is_mutable()
data = ("dirnode", contents)
- return simplejson.dumps(data, indent=1) + "\n"
+ json = simplejson.dumps(data, indent=1) + "\n"
+ return json
d.addCallback(_got)
d.addCallback(text_plain, ctx)
return d
-
def DirectoryURI(ctx, dirnode):
return text_plain(dirnode.get_uri(), ctx)
ctx.tag.attributes['value'] = name
return ctx.tag
-
class ManifestResults(rend.Page, ReloadMixin):
docFactory = getxmlfile("manifest.xhtml")
- def __init__(self, monitor):
+ def __init__(self, client, monitor):
+ self.client = client
self.monitor = monitor
def renderHTTP(self, ctx):
- output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
+ req = inevow.IRequest(ctx)
+ output = get_arg(req, "output", "html").lower()
if output == "text":
- return self.text(ctx)
+ return self.text(req)
if output == "json":
- return self.json(ctx)
+ return self.json(req)
return rend.Page.renderHTTP(self, ctx)
def slashify_path(self, path):
return ""
return "/".join([p.encode("utf-8") for p in path])
- def text(self, ctx):
- inevow.IRequest(ctx).setHeader("content-type", "text/plain")
+ def text(self, req):
+ req.setHeader("content-type", "text/plain")
lines = []
is_finished = self.monitor.is_finished()
lines.append("finished: " + {True: "yes", False: "no"}[is_finished])
lines.append(self.slashify_path(path) + " " + cap)
return "\n".join(lines) + "\n"
- def json(self, ctx):
- inevow.IRequest(ctx).setHeader("content-type", "text/plain")
+ def json(self, req):
+ req.setHeader("content-type", "text/plain")
m = self.monitor
s = m.get_status()
- status = {"manifest": s["manifest"],
- "verifycaps": list(s["verifycaps"]),
- "storage-index": list(s["storage-index"]),
- "stats": s["stats"],
- "finished": m.is_finished(),
- "origin": base32.b2a(m.origin_si),
- }
+
+ if m.origin_si:
+ origin_base32 = base32.b2a(m.origin_si)
+ else:
+ origin_base32 = ""
+ status = { "stats": s["stats"],
+ "finished": m.is_finished(),
+ "origin": origin_base32,
+ }
+ if m.is_finished():
+ # don't return manifest/verifycaps/SIs unless the operation is
+ # done, to save on CPU/memory (both here and in the HTTP client
+ # who has to unpack the JSON). Tests show that the ManifestWalker
+ # needs about 1092 bytes per item, the JSON we generate here
+ # requires about 503 bytes per item, and some internal overhead
+ # (perhaps transport-layer buffers in twisted.web?) requires an
+ # additional 1047 bytes per item.
+ status.update({ "manifest": s["manifest"],
+ "verifycaps": [i for i in s["verifycaps"]],
+ "storage-index": [i for i in s["storage-index"]],
+ })
+ # simplejson doesn't know how to serialize a set. We use a
+ # generator that walks the set rather than list(setofthing) to
+ # save a small amount of memory (4B*len) and a moderate amount of
+ # CPU.
return simplejson.dumps(status, indent=1)
def _si_abbrev(self):
- return base32.b2a(self.monitor.origin_si)[:6]
+ si = self.monitor.origin_si
+ if not si:
+ return "<LIT>"
+ return base32.b2a(si)[:6]
def render_title(self, ctx):
return T.title["Manifest of SI=%s" % self._si_abbrev()]
ctx.fillSlots("path", self.slashify_path(path))
root = get_root(ctx)
# TODO: we need a clean consistent way to get the type of a cap string
- if cap.startswith("URI:CHK") or cap.startswith("URI:SSK"):
- nameurl = urllib.quote(path[-1].encode("utf-8"))
- uri_link = "%s/file/%s/@@named=/%s" % (root, urllib.quote(cap),
- nameurl)
+ if cap:
+ if cap.startswith("URI:CHK") or cap.startswith("URI:SSK"):
+ nameurl = urllib.quote(path[-1].encode("utf-8"))
+ uri_link = "%s/file/%s/@@named=/%s" % (root, urllib.quote(cap),
+ nameurl)
+ else:
+ uri_link = "%s/uri/%s" % (root, urllib.quote(cap, safe=""))
+ ctx.fillSlots("cap", T.a(href=uri_link)[cap])
else:
- uri_link = "%s/uri/%s" % (root, urllib.quote(cap))
- ctx.fillSlots("cap", T.a(href=uri_link)[cap])
+ ctx.fillSlots("cap", "")
return ctx.tag
class DeepSizeResults(rend.Page):
- def __init__(self, monitor):
+ def __init__(self, client, monitor):
+ self.client = client
self.monitor = monitor
def renderHTTP(self, ctx):
- output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
- inevow.IRequest(ctx).setHeader("content-type", "text/plain")
+ req = inevow.IRequest(ctx)
+ output = get_arg(req, "output", "html").lower()
+ req.setHeader("content-type", "text/plain")
if output == "json":
- return self.json(ctx)
+ return self.json(req)
# plain text
is_finished = self.monitor.is_finished()
output = "finished: " + {True: "yes", False: "no"}[is_finished] + "\n"
output += "size: %d\n" % total
return output
- def json(self, ctx):
+ def json(self, req):
status = {"finished": self.monitor.is_finished(),
"size": self.monitor.get_status(),
}
return simplejson.dumps(status)
class DeepStatsResults(rend.Page):
- def __init__(self, monitor):
+ def __init__(self, client, monitor):
+ self.client = client
self.monitor = monitor
def renderHTTP(self, ctx):
s = self.monitor.get_status().copy()
s["finished"] = self.monitor.is_finished()
return simplejson.dumps(s, indent=1)
+
+class ManifestStreamer(dirnode.DeepStats):
+ implements(IPushProducer)
+
+ def __init__(self, ctx, origin):
+ dirnode.DeepStats.__init__(self, origin)
+ self.req = IRequest(ctx)
+
+ def setMonitor(self, monitor):
+ self.monitor = monitor
+ def pauseProducing(self):
+ pass
+ def resumeProducing(self):
+ pass
+ def stopProducing(self):
+ self.monitor.cancel()
+
+ def add_node(self, node, path):
+ dirnode.DeepStats.add_node(self, node, path)
+ d = {"path": path,
+ "cap": node.get_uri()}
+
+ if IDirectoryNode.providedBy(node):
+ d["type"] = "directory"
+ elif IFileNode.providedBy(node):
+ d["type"] = "file"
+ else:
+ d["type"] = "unknown"
+
+ v = node.get_verify_cap()
+ if v:
+ v = v.to_string()
+ d["verifycap"] = v or ""
+
+ r = node.get_repair_cap()
+ if r:
+ r = r.to_string()
+ d["repaircap"] = r or ""
+
+ si = node.get_storage_index()
+ if si:
+ si = base32.b2a(si)
+ d["storage-index"] = si or ""
+
+ j = simplejson.dumps(d, ensure_ascii=True)
+ assert "\n" not in j
+ self.req.write(j+"\n")
+
+ def finish(self):
+ stats = dirnode.DeepStats.get_results(self)
+ d = {"type": "stats",
+ "stats": stats,
+ }
+ j = simplejson.dumps(d, ensure_ascii=True)
+ assert "\n" not in j
+ self.req.write(j+"\n")
+ return ""
+
+class DeepCheckStreamer(dirnode.DeepStats):
+ implements(IPushProducer)
+
+ def __init__(self, ctx, origin, verify, repair, add_lease):
+ dirnode.DeepStats.__init__(self, origin)
+ self.req = IRequest(ctx)
+ self.verify = verify
+ self.repair = repair
+ self.add_lease = add_lease
+
+ def setMonitor(self, monitor):
+ self.monitor = monitor
+ def pauseProducing(self):
+ pass
+ def resumeProducing(self):
+ pass
+ def stopProducing(self):
+ self.monitor.cancel()
+
+ def add_node(self, node, path):
+ dirnode.DeepStats.add_node(self, node, path)
+ data = {"path": path,
+ "cap": node.get_uri()}
+
+ if IDirectoryNode.providedBy(node):
+ data["type"] = "directory"
+ elif IFileNode.providedBy(node):
+ data["type"] = "file"
+ else:
+ data["type"] = "unknown"
+
+ v = node.get_verify_cap()
+ if v:
+ v = v.to_string()
+ data["verifycap"] = v or ""
+
+ r = node.get_repair_cap()
+ if r:
+ r = r.to_string()
+ data["repaircap"] = r or ""
+
+ si = node.get_storage_index()
+ if si:
+ si = base32.b2a(si)
+ data["storage-index"] = si or ""
+
+ if self.repair:
+ d = node.check_and_repair(self.monitor, self.verify, self.add_lease)
+ d.addCallback(self.add_check_and_repair, data)
+ else:
+ d = node.check(self.monitor, self.verify, self.add_lease)
+ d.addCallback(self.add_check, data)
+ d.addCallback(self.write_line)
+ return d
+
+ def add_check_and_repair(self, crr, data):
+ data["check-and-repair-results"] = json_check_and_repair_results(crr)
+ return data
+
+ def add_check(self, cr, data):
+ data["check-results"] = json_check_results(cr)
+ return data
+
+ def write_line(self, data):
+ j = simplejson.dumps(data, ensure_ascii=True)
+ assert "\n" not in j
+ self.req.write(j+"\n")
+
+ def finish(self):
+ stats = dirnode.DeepStats.get_results(self)
+ d = {"type": "stats",
+ "stats": stats,
+ }
+ j = simplejson.dumps(d, ensure_ascii=True)
+ assert "\n" not in j
+ self.req.write(j+"\n")
+ return ""
+
+
+class UnknownNodeHandler(RenderMixin, rend.Page):
+ def __init__(self, client, node, parentnode=None, name=None):
+ rend.Page.__init__(self)
+ assert node
+ self.node = node
+ self.parentnode = parentnode
+ self.name = name
+
+ def render_GET(self, ctx):
+ req = IRequest(ctx)
+ t = get_arg(req, "t", "").strip()
+ if t == "info":
+ return MoreInfo(self.node)
+ if t == "json":
+ is_parent_known_immutable = self.parentnode and not self.parentnode.is_mutable()
+ if self.parentnode and self.name:
+ d = self.parentnode.get_metadata_for(self.name)
+ else:
+ d = defer.succeed(None)
+ d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md, is_parent_known_immutable))
+ return d
+ raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n"
+ "Using a webapi server that supports a later version of Tahoe "
+ "may help." % t)
+
+def UnknownJSONMetadata(ctx, node, edge_metadata, is_parent_known_immutable):
+ rw_uri = node.get_write_uri()
+ ro_uri = node.get_readonly_uri()
+ data = ("unknown", {})
+ if ro_uri:
+ data[1]['ro_uri'] = ro_uri
+ if rw_uri:
+ data[1]['rw_uri'] = rw_uri
+ data[1]['mutable'] = True
+ elif is_parent_known_immutable or node.is_alleged_immutable():
+ data[1]['mutable'] = False
+ # else we don't know whether it is mutable.
+
+ if edge_metadata is not None:
+ data[1]['metadata'] = edge_metadata
+ return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)