import simplejson
import urllib
-import time
from zope.interface import implements
from twisted.internet import defer
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 import base32, time_format
from allmydata.uri import from_string_dirnode
-from allmydata.interfaces import IDirectoryNode, IFileNode, IMutableFileNode, \
- ExistingChildError, NoSuchChildError
+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
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
from allmydata.web.check_results import CheckResults, \
- CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults
+ CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults, \
+ LiteralCheckResults
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()
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-with-children":
+ d = self._POST_mkdir_with_children(req)
+ elif t == "mkdir-immutable":
+ d = self._POST_mkdir_immutable(req)
elif t == "mkdir-p":
# TODO: docs, tests
d = self._POST_mkdir_p(req)
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 == "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_stats(ctx)
elif t == "stream-manifest":
d = self._POST_stream_manifest(ctx)
- elif t == "set_children":
- # TODO: docs
+ 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_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 _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
d = node.get(path[0])
def _maybe_create(f):
f.trap(NoSuchChildError)
- return node.create_empty_directory(path[0])
+ return node.create_subdirectory(path[0])
d.addErrback(_maybe_create)
d.addCallback(self._get_or_create_directories, path[1:])
return d
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
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)
+
+ # 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):
d.addCallback(lambda res: "thing renamed")
return d
+ def _maybe_literal(self, res, Results_Class):
+ if res:
+ return Results_Class(self.client, res)
+ return LiteralCheckResults(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, CheckAndRepairResults)
else:
- d = self.node.check(Monitor(), verify)
- d.addCallback(lambda res: CheckResults(res))
+ d = self.node.check(Monitor(), verify, add_lease)
+ d.addCallback(self._maybe_literal, CheckResults)
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 = DeepCheckAndRepairResults(self.client, monitor)
else:
- monitor = self.node.start_deep_check(verify)
- renderer = DeepCheckResults(monitor)
+ monitor = self.node.start_deep_check(verify, add_lease)
+ renderer = DeepCheckResults(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):
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):
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]
-
- def render_welcome(self, ctx, data):
- link = get_root(ctx)
- return T.div[T.a(href=link)["Return to Welcome page"]]
+ assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
+ self.default_mutable_format = default_mutable_format
- 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', value='unlink', name="unlink"),
]
rename = T.form(action=here, method="get")[
T.input(type='submit', value='rename', 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: " + time_format.iso_local(linkcrtime))
+ else:
+ # For backwards-compatibility with links last modified by Tahoe < 1.4.0:
+ if "ctime" in metadata:
+ ctime = time_format.iso_local(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: " + time_format.iso_local(linkmotime))
+ else:
+ # For backwards-compatibility with links last modified by Tahoe < 1.4.0:
+ if "mtime" in metadata:
+ mtime = time_format.iso_local(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
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",
elif IDirectoryNode.providedBy(target):
# directory
- uri_link = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
+ uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
ctx.fillSlots("filename",
T.a(href=uri_link)[html.escape(name)])
- if target.is_readonly():
+ 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", html.escape(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.input(type="text", name="name"), SPACE,
+ T.input(type="submit", value="Create"), SPACE*2,
+ mkdir_sdmf, T.label(for_='mutable-directory-sdmf')[" SDMF"], SPACE,
+ mkdir_mdmf, T.label(for_='mutable-directory-mdmf')[" MDMF (experimental)"],
]]
- 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.input(type="submit", value="Upload"), SPACE*2,
+ upload_chk, T.label(for_="upload-chk") [" Immutable"], SPACE,
+ upload_sdmf, T.label(for_="upload-sdmf")[" SDMF"], SPACE,
+ upload_mdmf, T.label(for_="upload-mdmf")[" MDMF (experimental)"],
]]
- forms.append(T.div(class_="freeform-form")[upload])
+ forms.append(T.div(class_="freeform-form")[upload_form])
- mount = T.form(action=".", method="post",
- enctype="multipart/form-data")[
+ attach_form = 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.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,
+ "URI of new child:"+SPACE,
+ T.input(type="text", name="uri"), SPACE,
T.input(type="submit", value="Attach"),
]]
- forms.append(T.div(class_="freeform-form")[mount])
+ forms.append(T.div(class_="freeform-form")[attach_form])
return forms
def render_results(self, ctx, data):
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,
+ 'mutable': childnode.is_mutable(),
})
+ if childnode.is_mutable():
+ mutable_type = childnode.get_version()
+ assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
+ if mutable_type == MDMF_VERSION:
+ file_format = "mdmf"
+ else:
+ file_format = "sdmf"
+ else:
+ file_format = "chk"
+ kiddata[1]['format'] = file_format
+
+ 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)
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()
+ if m.origin_si:
+ origin_base32 = base32.b2a(m.origin_si)
+ else:
+ origin_base32 = ""
status = { "stats": s["stats"],
"finished": m.is_finished(),
- "origin": base32.b2a(m.origin_si),
+ "origin": origin_base32,
}
if m.is_finished():
# don't return manifest/verifycaps/SIs unless the operation is
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):
if IDirectoryNode.providedBy(node):
d["type"] = "directory"
- else:
+ 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
+ d["verifycap"] = v or ""
r = node.get_repair_cap()
if r:
r = r.to_string()
- d["repaircap"] = r
+ d["repaircap"] = r or ""
si = node.get_storage_index()
if si:
si = base32.b2a(si)
- d["storage-index"] = si
+ d["storage-index"] = si or ""
j = simplejson.dumps(d, ensure_ascii=True)
assert "\n" not in j
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)