tahoe-lafs/src/allmydata/web/directory.py

1486 lines
54 KiB
Python
Raw Normal View History

2017-01-19 22:39:53 +00:00
import json
import urllib
2019-11-17 02:23:20 +00:00
from datetime import timedelta
from zope.interface import implementer
from twisted.internet import defer
from twisted.internet.interfaces import IPushProducer
from twisted.python.failure import Failure
from twisted.web import http
2019-10-25 12:06:01 +00:00
from twisted.web.resource import ErrorPage
2019-09-03 23:26:11 +00:00
from twisted.web.resource import Resource
2019-11-05 06:34:27 +00:00
from twisted.web.template import (
Element,
XMLFile,
renderElement,
renderer,
tags,
)
2019-11-17 02:23:44 +00:00
from hyperlink import URL
2019-11-05 06:34:27 +00:00
from twisted.python.filepath import FilePath
2016-01-04 16:00:59 +00:00
from allmydata.util import base32
from allmydata.util.encodingutil import to_str
2019-12-21 23:01:23 +00:00
from allmydata.uri import (
from_string_dirnode,
from_string,
CHKFileURI,
WriteableSSKFileURI,
ReadonlySSKFileURI,
)
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
2009-01-23 05:01:36 +00:00
from allmydata import dirnode
2019-11-05 06:34:27 +00:00
from allmydata.web.common import (
text_plain,
WebError,
NeedOperationHandleError,
boolean_of_arg,
get_arg,
get_root,
parse_replace_arg,
should_create_intermediate_directories,
humanize_failure,
convert_children_json,
get_format,
get_mutable_type,
get_filenode_metadata,
render_time,
MultiFormatPage,
MultiFormatResource,
SlotsSequenceElement,
)
from allmydata.web.filenode import ReplaceMeMixin, \
FileNodeHandler, PlaceHolderNodeHandler
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, client, parentnode=None, name=None):
if parentnode:
assert IDirectoryNode.providedBy(parentnode)
if IFileNode.providedBy(node):
return FileNodeHandler(client, node, parentnode, name)
if IDirectoryNode.providedBy(node):
return DirectoryNodeHandler(client, node, parentnode, name)
return UnknownNodeHandler(client, node, parentnode, name)
2019-09-03 23:59:45 +00:00
class DirectoryNodeHandler(ReplaceMeMixin, Resource, object):
def __init__(self, client, node, parentnode=None, name=None):
super(DirectoryNodeHandler, self).__init__()
self.client = client
assert node
self.node = node
self.parentnode = parentnode
self.name = name
self._operations = client.get_web_service().get_operations()
def getChild(self, name, req):
"""
Dynamically create a child for the given request and name
"""
# trying to replicate what I have observed as Nevow behavior
# for these nodes, which is that a URI like
# "/uri/URI%3ADIR2%3Aj...vq/" (that is, with a trailing slash
# or no further children) renders "this" page
2019-09-24 04:37:11 +00:00
name = name.decode('utf8')
if not name:
# replicating Nevow behavior that complains about "empty
# path segments" .. but twisted.web sends in "name=None"
# for a URL like "/foo/bar/" as well as "/foo//bar"
# (i.e. a trailing slash means "name=None" as well)
if b'//' in req.path:
raise EmptyPathnameComponentError(
u"The webapi does not allow empty pathname components, i.e. a double slash",
)
return self
2019-09-24 04:37:11 +00:00
d = self.node.get(name)
d.addBoth(self._got_child, req, name)
return d
def _got_child(self, node_or_failure, req, name):
"""
Callback when self.node.get has returned
"""
2019-11-18 23:56:04 +00:00
# we used to determine if a request was 'terminal' by
# examining req.postpath .. but that's a Nevow-ism, so we
# determine if this is a "terminal" request using only Twisted
# Web APIs, which means finding where "name" is in the path
u = URL.from_text(unicode(req.path)).to_iri()
assert name in u.path, "Can't find our child-name in the path"
nonterminal = (u.path[-1] != name)
t = get_arg(req, "t", "").strip() # XXX looking like MultiFormatResource..
if isinstance(node_or_failure, Failure):
f = node_or_failure
f.trap(NoSuchChildError)
# No child by this name. What should we do about it?
if nonterminal:
if should_create_intermediate_directories(req):
# create intermediate directories
d = self.node.create_subdirectory(name)
d.addCallback(make_handler_for,
self.client, self.node, name)
return d
else:
# terminal node
terminal_requests = (
("POST", "mkdir"),
("PUT", "mkdir"),
("POST", "mkdir-with-children"),
("POST", "mkdir-immutable")
)
2019-11-18 23:56:04 +00:00
if (req.method, t) in terminal_requests:
# final directory
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
leaf_requests = (
("PUT",""),
("PUT","uri"),
)
2019-11-18 23:56:04 +00:00
if (req.method, t) in leaf_requests:
# we were trying to find the leaf filenode (to put a new
# file in its place), and it didn't exist. That's ok,
# 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.client, self.node, name)
# otherwise, we just return a no-such-child error
return f
node = node_or_failure
if nonterminal and should_create_intermediate_directories(req):
if not IDirectoryNode.providedBy(node):
# we would have put a new directory here, but there was a
# file in the way.
2019-10-25 12:06:01 +00:00
return ErrorPage(
http.CONFLICT,
"Unable to create directory '{}': a file was in the way".format(name),
"no details",
)
return make_handler_for(node, self.client, self.node, name)
def render_DELETE(self, req):
assert self.parentnode and self.name
d = self.parentnode.delete(self.name)
d.addCallback(lambda res: self.node.get_uri())
return d
def render_GET(self, req):
# 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:
2019-12-28 08:05:31 +00:00
# render the directory as HTML
2019-09-11 19:23:35 +00:00
return renderElement(
req,
DirectoryAsHTML(
self.node,
self.client.mutable_file_default,
)
2019-09-03 23:59:45 +00:00
)
if t == "json":
return DirectoryJSONMetadata(req, self.node)
if t == "info":
return MoreInfo(self.node)
if t == "uri":
return DirectoryURI(req, self.node)
if t == "readonly-uri":
return DirectoryReadonlyURI(req, self.node)
if t == 'rename-form':
2019-09-11 19:46:18 +00:00
return renderElement(
req,
RenameForm(self.node),
)
raise WebError("GET directory: bad t=%s" % t)
def render_PUT(self, req):
t = get_arg(req, "t", "").strip()
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.
return text_plain(self.node.get_uri(), req) # TODO: urlencode
if t == "uri":
if not replace:
# they're trying to set_uri and that name is already occupied
# (by us).
raise ExistingChildError()
d = self.replace_me_with_a_childcap(req, self.client, replace)
# TODO: results
return d
raise WebError("PUT to a directory")
def render_POST(self, req):
t = get_arg(req, "t", "").strip()
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 == "upload":
d = self._POST_upload(req) # this one needs the context
elif t == "uri":
d = self._POST_uri(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(req)
elif t == "stream-deep-check":
d = self._POST_stream_deep_check(req)
elif t == "start-manifest":
d = self._POST_start_manifest(req)
elif t == "start-deep-size":
d = self._POST_start_deep_size(req)
elif t == "start-deep-stats":
d = self._POST_start_deep_stats(req)
2009-01-23 05:01:36 +00:00
elif t == "stream-manifest":
d = self._POST_stream_manifest(req)
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)
when_done = get_arg(req, "when_done", None)
if when_done:
2019-09-10 23:52:20 +00:00
def done(res):
req.redirect(when_done)
return res
d.addCallback(done)
return d
def _POST_mkdir(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")
replace = boolean_of_arg(get_arg(req, "replace", "true"))
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
def _POST_upload(self, req):
charset = get_arg(req, "_charset", "utf-8")
contents = req.fields["file"]
assert contents.filename is None or isinstance(contents.filename, str)
name = get_arg(req, "name")
name = name or contents.filename
if name is not None:
name = name.strip()
if not name:
# this prohibts empty, missing, and all-whitespace filenames
raise WebError("upload requires a name")
assert isinstance(name, str)
name = name.decode(charset)
if "/" in name:
raise WebError("name= may not contain a slash", http.BAD_REQUEST)
assert isinstance(name, unicode)
# since POST /uri/path/file?t=upload is equivalent to
# POST /uri/path/dir?t=upload&name=foo, just do the same thing that
# childFactory would do. Things are cleaner if we only do a subset of
# them, though, so we don't do: d = self.childFactory(req, name)
d = self.node.get(name)
def _maybe_got_node(node_or_failure):
if isinstance(node_or_failure, Failure):
f = node_or_failure
f.trap(NoSuchChildError)
# create a placeholder which will see POST t=upload
return PlaceHolderNodeHandler(self.client, self.node, name)
else:
node = node_or_failure
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
# DirectoryNodeHandler.renderHTTP, and nevow would recurse into it,
# but the addCallback() that handles when_done= would break.
2019-09-10 23:52:20 +00:00
d.addCallback(lambda child: child.render(req))
return d
def _POST_uri(self, req):
childcap = get_arg(req, "uri")
if not childcap:
raise WebError("set-uri requires a uri")
name = get_arg(req, "name")
if not name:
raise WebError("set-uri requires a name")
charset = get_arg(req, "_charset", "utf-8")
name = name.decode(charset)
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_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 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 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)
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)
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)
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, add_lease)
d.addCallback(self._maybe_literal, CheckAndRepairResultsRenderer)
else:
d = self.node.check(Monitor(), verify, add_lease)
d.addCallback(self._maybe_literal, CheckResultsRenderer)
return d
def _start_operation(self, monitor, renderer, ctx):
self._operations.add_monitor(ctx, monitor, renderer)
return self._operations.redirect_to(ctx)
def _POST_start_deep_check(self, req):
# check this directory and everything reachable from it
if not get_arg(req, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
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:
monitor = self.node.start_deep_check_and_repair(verify, add_lease)
renderer = DeepCheckAndRepairResultsRenderer(self.client, monitor)
else:
monitor = self.node.start_deep_check(verify, add_lease)
renderer = DeepCheckResultsRenderer(self.client, monitor)
return self._start_operation(monitor, renderer, req)
def _POST_stream_deep_check(self, req):
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"))
walker = DeepCheckStreamer(req, self.node, verify, repair, add_lease)
monitor = self.node.deep_traverse(walker)
walker.setMonitor(monitor)
# register to hear stopProducing. The walker ignores pauseProducing.
req.registerProducer(walker, True)
d = monitor.when_done()
def _done(res):
req.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, req):
if not get_arg(req, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.build_manifest()
renderer = ManifestResults(self.client, monitor)
return self._start_operation(monitor, renderer, req)
def _POST_start_deep_size(self, req):
if not get_arg(req, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.start_deep_stats()
renderer = DeepSizeResults(self.client, monitor)
return self._start_operation(monitor, renderer, req)
def _POST_start_deep_stats(self, req):
if not get_arg(req, "ophandle"):
raise NeedOperationHandleError("slow operation requires ophandle=")
monitor = self.node.start_deep_stats()
renderer = DeepStatsResults(self.client, monitor)
return self._start_operation(monitor, renderer, req)
def _POST_stream_manifest(self, req):
walker = ManifestStreamer(req, self.node)
2009-01-23 05:01:36 +00:00
monitor = self.node.deep_traverse(walker)
walker.setMonitor(monitor)
# register to hear stopProducing. The walker ignores pauseProducing.
req.registerProducer(walker, True)
d = monitor.when_done()
def _done(res):
req.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
2009-01-23 05:01:36 +00:00
def _POST_set_children(self, req):
replace = parse_replace_arg(get_arg(req, "replace", "true"))
req.content.seek(0)
body = req.content.read()
try:
2017-01-19 22:39:53 +00:00
children = json.loads(body)
except ValueError as le:
le.args = tuple(le.args + (body,))
# TODO test handling of bad JSON
raise
cs = {}
for name, (file_or_dir, mddict) in children.iteritems():
name = unicode(name) # json returns str *or* unicode
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
return d
def abbreviated_dirnode(dirnode):
u = from_string_dirnode(dirnode.get_uri())
return u.abbrev_si()
SPACE = u"\u00A0"*2
class DirectoryAsHTML(Element):
# The remainder of this class is to render the directory into
# human+browser -oriented HTML.
loader = XMLFile(FilePath(__file__).sibling("directory.xhtml"))
def __init__(self, node, default_mutable_format):
super(DirectoryAsHTML, self).__init__()
self.node = node
if default_mutable_format not in (MDMF_VERSION, SDMF_VERSION):
raise ValueError(
2019-11-18 23:56:58 +00:00
"Uknown mutable format '{}'".format(default_mutable_format)
)
self.default_mutable_format = default_mutable_format
2019-09-03 23:59:45 +00:00
@defer.inlineCallbacks
def render(self, request):
"""
Override Element.render .. we have async work to do before we flatten our template
"""
2019-10-05 05:28:26 +00:00
# this could be improved; doing a more-straightforward port
2019-09-03 23:59:45 +00:00
# here, which used to be in "beforeRender" .. so lots of the
# renderers don't yield/wait properly, they expect the self.*
# state that is set up here to "just be there" :/
yield self._get_children(request)
template = Element.render(self, request)
defer.returnValue(template)
2019-09-03 23:59:45 +00:00
@defer.inlineCallbacks
def _get_children(self, req):
# attempt to get the dirnode's children, stashing them (or the
# failure that results) for later use
2019-09-03 23:59:45 +00:00
try:
children = yield self.node.list()
except Exception as e:
text, code = humanize_failure(Failure(e))
2019-10-05 05:28:15 +00:00
children = None
self.dirnode_children_error = text
2019-09-03 23:59:45 +00:00
self.dirnode_children = children
defer.returnValue(self.dirnode_children)
@renderer
def children(self, req, tag):
2019-09-10 23:52:20 +00:00
return SlotsSequenceElement(
tag,
[
self._child_slots(req, fname, data[0], data[1])
for fname, data in self.dirnode_children.items()
]
2019-09-03 23:59:45 +00:00
)
@renderer
def title(self, req, tag):
si_s = abbreviated_dirnode(self.node)
2009-10-29 02:50:50 +00:00
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)")
2019-09-03 23:59:45 +00:00
return tag(header)
2019-09-03 23:59:45 +00:00
@renderer
def header(self, req, tag):
si_s = abbreviated_dirnode(self.node)
2019-09-03 23:59:45 +00:00
header = ["Tahoe-LAFS Directory SI=", tags.span(si_s, class_="data-chars")]
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)")
2019-09-03 23:59:45 +00:00
return tag(header)
2019-09-03 23:59:45 +00:00
@renderer
def welcome(self, req, tag):
link = get_root(req)
2019-09-03 23:59:45 +00:00
return tag(tags.a("Return to Welcome page", href=link))
2019-09-03 23:59:45 +00:00
@renderer
def show_readonly(self, req, tag):
if self.node.is_unknown() or self.node.is_readonly():
return ""
rocap = self.node.get_readonly_uri()
root = get_root(req)
uri_link = "%s/uri/%s/" % (root, urllib.quote(rocap))
2019-09-03 23:59:45 +00:00
return tag(tags.a("Read-Only Version", href=uri_link))
2019-09-03 23:59:45 +00:00
@renderer
def try_children(self, req, tag):
# if the dirnode can be retrived, render a table of children.
# Otherwise, render an apologetic error message.
if self.dirnode_children is not None:
2019-09-03 23:59:45 +00:00
return tag
else:
2019-09-03 23:59:45 +00:00
return tags.div(
tags.p("Error reading directory:"),
tags.p(self.dirnode_children_error),
)
2019-09-10 23:52:20 +00:00
def _child_slots(self, req, name, target, metadata):
"""
:returns: a dict of key/values to give to each item in the table
of sub-items that directory.xhtml defines (this method is
called by the 'children' renderer)
"""
name = name.encode("utf-8")
nameurl = urllib.quote(name, safe="") # encode any slashes too
root = get_root(req)
2019-09-10 23:52:20 +00:00
here = "{}/uri/{}/".format(root, urllib.quote(self.node.get_uri()))
if self.node.is_unknown() or self.node.is_readonly():
unlink = "-"
rename = "-"
else:
# 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
2019-09-03 23:59:45 +00:00
unlink = tags.form(
[
tags.input(type='hidden', name='t', value='unlink'),
tags.input(type='hidden', name='name', value=name),
tags.input(type='hidden', name='when_done', value="."),
2019-09-10 23:52:20 +00:00
tags.input(type='submit', class_='btn', value='unlink', name="unlink"),
2019-09-03 23:59:45 +00:00
],
action=here, method="post"
)
rename = tags.form(
[
tags.input(type='hidden', name='t', value='rename-form'),
tags.input(type='hidden', name='name', value=name),
tags.input(type='hidden', name='when_done', value="."),
2019-09-10 23:52:20 +00:00
tags.input(type='submit', class_='btn', value='rename/relink', name="rename"),
2019-09-03 23:59:45 +00:00
],
action=here, method="get",
)
2019-09-10 23:52:20 +00:00
slots = {
"unlink": unlink,
"rename": rename,
}
times = []
linkcrtime = metadata.get('tahoe', {}).get("linkcrtime")
if linkcrtime is not None:
2016-01-04 16:00:59 +00:00
times.append("lcr: " + render_time(linkcrtime))
else:
# For backwards-compatibility with links last modified by Tahoe < 1.4.0:
if "ctime" in metadata:
2016-01-04 16:00:59 +00:00
ctime = render_time(metadata["ctime"])
times.append("c: " + ctime)
linkmotime = metadata.get('tahoe', {}).get("linkmotime")
if linkmotime is not None:
if times:
2019-09-03 23:59:45 +00:00
times.append(tags.br())
2016-01-04 16:00:59 +00:00
times.append("lmo: " + render_time(linkmotime))
else:
# For backwards-compatibility with links last modified by Tahoe < 1.4.0:
if "mtime" in metadata:
2016-01-04 16:00:59 +00:00
mtime = render_time(metadata["mtime"])
if times:
2019-09-03 23:59:45 +00:00
times.append(tags.br())
times.append("m: " + mtime)
2019-09-10 23:52:20 +00:00
slots["times"] = times
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
# secret directory URI from the URL, send the browser to a URI-based
# page that doesn't know about the directory at all
dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
2019-09-10 23:52:20 +00:00
slots["filename"] = tags.a(name, href=dlurl, rel="noreferrer")
slots["type"] = "SSK"
slots["size"] = "?"
info_link = "{}/uri/{}?t=info".format(root, quoted_uri)
elif IImmutableFileNode.providedBy(target):
dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, nameurl)
2019-09-10 23:52:20 +00:00
slots["filename"] = tags.a(name, href=dlurl, rel="noreferrer")
slots["type"] = "FILE"
slots["size"] = str(target.get_size())
info_link = "{}/uri/{}?t=info".format(root, quoted_uri)
elif IDirectoryNode.providedBy(target):
# directory
uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri))
2019-09-10 23:52:20 +00:00
slots["filename"] = tags.a(name, href=uri_link)
if not target.is_mutable():
dirtype = "DIR-IMM"
elif target.is_readonly():
dirtype = "DIR-RO"
else:
dirtype = "DIR"
2019-09-10 23:52:20 +00:00
slots["type"] = dirtype
slots["size"] = "-"
info_link = "%s/uri/%s/?t=info" % (root, quoted_uri)
elif isinstance(target, ProhibitedNode):
if IDirectoryNode.providedBy(target.wrapped_node):
blacklisted_type = "DIR-BLACKLISTED"
else:
blacklisted_type = "BLACKLISTED"
2019-09-10 23:52:20 +00:00
slots["type"] = blacklisted_type
slots["size"] = "-"
slots["info"] = ["Access Prohibited:", tags.br, target.reason]
2019-09-24 04:53:13 +00:00
slots["filename"] = tags.strike(name)
info_link = None
else:
# unknown
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"
2019-09-10 23:52:20 +00:00
slots["filename"] = name
slots["type"] = unknowntype
slots["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:
2019-09-10 23:52:20 +00:00
slots["info"] = tags.a("More Info", href=info_link)
2019-09-10 23:52:20 +00:00
return slots
# XXX: similar to render_upload_form and render_mkdir_form in root.py.
2019-09-10 23:52:20 +00:00
# XXX: also, is generating so much HTML in code a great idea? -> templates?
2019-09-03 23:59:45 +00:00
@renderer
def forms(self, req, data):
forms = []
2019-09-11 19:46:18 +00:00
# making this consistent with the other forms, and also
# because action="." doesn't get us back to the dir page (but
# instead /uri itself)
root = get_root(req)
here = "{}/uri/{}/".format(root, urllib.quote(self.node.get_uri()))
if self.node.is_readonly():
2019-09-03 23:59:45 +00:00
return tags.div("No upload forms: directory is read-only")
if self.dirnode_children is None:
2019-09-03 23:59:45 +00:00
return tags.div("No upload forms: directory is unreadable")
mkdir_sdmf = tags.input(
type='radio',
name='format',
value='sdmf',
id='mkdir-sdmf',
checked='checked',
)
mkdir_mdmf = tags.input(
type='radio',
name='format',
value='mdmf',
id='mkdir-mdmf',
)
2019-09-11 19:28:24 +00:00
mkdir_form = tags.form(
[
tags.fieldset([
tags.input(type="hidden", name="t", value="mkdir"),
tags.input(type="hidden", name="when_done", value="."),
tags.legend("Create a new directory in this directory", class_="freeform-form-label"),
"New directory name:"+SPACE, tags.br,
tags.input(type="text", name="name"), SPACE,
tags.div(
[
mkdir_sdmf, tags.label(SPACE, "SDMF", for_='mutable-directory-sdmf'), SPACE*2,
mkdir_mdmf, tags.label(SPACE, "MDMF (experimental)", for_='mutable-directory-mdmf'),
],
class_="form-inline",
),
tags.input(
type="submit",
class_="btn",
value="Create",
),
])
2013-08-30 14:03:58 +00:00
],
2019-09-11 19:46:18 +00:00
action=here,
2019-09-03 23:59:45 +00:00
method="post",
enctype="multipart/form-data",
)
forms.append(tags.div(mkdir_form, class_="freeform-form"))
upload_chk = tags.input(
type='radio',
name='format',
value='chk',
id='upload-chk',
checked='checked',
)
upload_sdmf = tags.input(
type='radio',
name='format',
value='sdmf',
id='upload-sdmf',
)
upload_mdmf = tags.input(
type='radio',
name='format',
value='mdmf',
id='upload-mdmf',
)
upload_form = tags.form(
tags.fieldset([
tags.input(type="hidden", name="t", value="upload"),
2019-09-10 23:52:20 +00:00
tags.input(type="hidden", name="when_done", value=req.uri),
2019-09-03 23:59:45 +00:00
tags.legend("Upload a file to this directory", class_="freeform-form-label"),
"Choose a file to upload:"+SPACE,
tags.input(type="file", name="file", class_="freeform-input-file"), SPACE,
tags.div([
upload_chk, tags.label(SPACE, "Immutable", for_="upload-chk"), SPACE*2,
upload_sdmf, tags.label(SPACE, "SDMF", for_="upload-sdmf"), SPACE*2,
upload_mdmf, tags.label(SPACE, "MDMF (experimental)", for_="upload-mdmf"),
], class_="form-inline"),
tags.input(type="submit", class_="btn", value="Upload"), SPACE*2,
]),
2019-09-10 23:52:20 +00:00
action=req.uri,
2019-09-03 23:59:45 +00:00
method="post",
enctype="multipart/form-data",
)
forms.append(tags.div(upload_form, class_="freeform-form"))
attach_form = tags.form(
tags.fieldset(
tags.div([
tags.input(type="hidden", name="t", value="uri"),
tags.input(type="hidden", name="when_done", value="."),
tags.legend("Add a link to a file or directory which is already in Tahoe-LAFS.", class_="freeform-form-label"),
"New child name:"+SPACE,
tags.input(type="text", name="name"), SPACE*2, tags.br,
"URI of new child:"+SPACE,
tags.input(type="text", name="uri"), SPACE,
tags.input(type="submit", class_="btn", value="Attach"),
], class_="form-inline"),
),
2019-09-11 19:46:18 +00:00
action=here,
2019-09-03 23:59:45 +00:00
method="post",
enctype="multipart/form-data",
)
forms.append(tags.div(attach_form, class_="freeform-form"))
return forms
2019-09-03 23:59:45 +00:00
@renderer
def results(self, req, tag):
return get_arg(req, "results", "")
def DirectoryJSONMetadata(req, dirnode):
d = dirnode.list()
def _got(children):
kids = {}
for name, (childnode, metadata) in children.iteritems():
assert IFilesystemNode.providedBy(childnode), childnode
rw_uri = childnode.get_write_uri()
ro_uri = childnode.get_readonly_uri()
if IFileNode.providedBy(childnode):
kiddata = ("filenode", get_filenode_metadata(childnode))
elif IDirectoryNode.providedBy(childnode):
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
else:
kiddata = ("unknown", {})
kiddata[1]["metadata"] = metadata
if rw_uri:
kiddata[1]["rw_uri"] = rw_uri
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
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)
2017-01-19 22:39:53 +00:00
return json.dumps(data, indent=1) + "\n"
d.addCallback(_got)
d.addCallback(text_plain, req)
def error(f):
message, code = humanize_failure(f)
req.setResponseCode(code)
2017-01-19 22:39:53 +00:00
return json.dumps({
"error": message,
})
d.addErrback(error)
return d
def DirectoryURI(req, dirnode):
return text_plain(dirnode.get_uri(), req)
def DirectoryReadonlyURI(req, dirnode):
return text_plain(dirnode.get_readonly_uri(), req)
2019-09-11 19:46:18 +00:00
class RenameForm(Element, object):
addSlash = True
2019-09-11 19:46:18 +00:00
loader = XMLFile(FilePath(__file__).sibling("rename-form.xhtml"))
2019-09-11 19:46:18 +00:00
def __init__(self, original):
self.original = original
super(RenameForm, self).__init__()
2019-09-11 19:46:18 +00:00
@renderer
def title(self, req, tag):
return tag("Directory SI={}".format(abbreviated_dirnode(self.original)))
@renderer
def header(self, req, tag):
header = [
"Rename "
"in directory SI=%s" % abbreviated_dirnode(self.original),
]
if self.original.is_readonly():
header.append(" (readonly!)")
header.append(":")
2019-09-11 19:46:18 +00:00
return tag(header)
2019-09-11 19:46:18 +00:00
@renderer
def when_done(self, req, tag):
2019-09-03 23:59:45 +00:00
return tags.input(type="hidden", name="when_done", value=".")
2019-09-11 19:46:18 +00:00
@renderer
def get_name(self, req, tag):
name = get_arg(req, "name", "")
2019-09-11 19:46:18 +00:00
tag.attributes['value'] = name
return tag
2019-11-05 06:38:43 +00:00
2019-11-17 02:23:58 +00:00
class ReloadableMonitorElement(Element):
2019-11-05 06:38:43 +00:00
"""
Like 'ReloadMixin', but for twisted.web.template style. This
provides renderers for "reload" and "refesh" and a self.monitor
2019-11-17 02:23:58 +00:00
attribute (which is an instance of IMonitor)
2019-11-05 06:38:43 +00:00
"""
2019-11-17 02:23:20 +00:00
refresh_time = timedelta(seconds=60)
2019-11-05 06:38:43 +00:00
def __init__(self, monitor):
self.monitor = monitor
super(ReloadableMonitorElement, self).__init__()
@renderer
def refresh(self, req, tag):
if self.monitor.is_finished():
2019-11-17 02:23:44 +00:00
return u""
tag.attributes[u"http-equiv"] = u"refresh"
tag.attributes[u"content"] = u"{}".format(self.refresh_time.seconds)
2019-11-05 06:38:43 +00:00
return tag
@renderer
def reload(self, req, tag):
if self.monitor.is_finished():
2019-11-17 02:23:44 +00:00
return u""
reload_url = URL.from_text(u"{}".format(req.path))
2019-11-05 06:38:43 +00:00
cancel_button = tags.form(
[
2019-11-17 02:23:44 +00:00
tags.input(type=u"submit", value=u"Cancel"),
2019-11-05 06:38:43 +00:00
],
2019-11-17 02:23:44 +00:00
action=reload_url.replace(query={u"t": u"cancel"}).to_uri().to_text(),
method=u"POST",
enctype=u"multipart/form-data",
2019-11-05 06:38:43 +00:00
)
return tag([
2019-11-17 02:23:44 +00:00
u"Operation still running: ",
tags.a(
u"Reload",
href=reload_url.replace(query={u"output": u"html"}).to_uri().to_text(),
),
2019-11-05 06:38:43 +00:00
cancel_button,
])
def _slashify_path(path):
"""
Converts a tuple from a 'manifest' path into a string with slashes
in it
"""
if not path:
return ""
return "/".join([p.encode("utf-8") for p in path])
def _cap_to_link(root, path, cap):
"""
Turns a capability-string into a WebAPI link tag
2019-11-17 02:23:44 +00:00
:param text root: the root piece of the URI
2019-11-05 06:38:43 +00:00
2019-11-17 02:23:44 +00:00
:param text cap: the capability-string
2019-11-05 06:38:43 +00:00
2019-11-17 02:23:44 +00:00
:returns: something suitable for `IRenderable`, specifically
either a valid local link (tags.a instance) to the capability
or an empty string.
2019-11-05 06:38:43 +00:00
"""
if cap:
2019-11-17 02:23:44 +00:00
root_url = URL.from_text(u"{}".format(root))
2019-12-21 23:01:23 +00:00
cap_obj = from_string(cap)
if isinstance(cap_obj, (CHKFileURI, WriteableSSKFileURI, ReadonlySSKFileURI)):
2019-11-17 02:23:44 +00:00
uri_link = root_url.child(
u"file",
u"{}".format(urllib.quote(cap)),
u"{}".format(urllib.quote(path[-1])),
)
2019-11-05 06:38:43 +00:00
else:
2019-11-17 02:23:44 +00:00
uri_link = root_url.child(
u"uri",
u"{}".format(urllib.quote(cap, safe="")),
)
return tags.a(cap, href=uri_link.to_text())
2019-11-05 06:38:43 +00:00
else:
2019-11-17 02:23:44 +00:00
return u""
2019-11-05 06:38:43 +00:00
class ManifestElement(ReloadableMonitorElement):
loader = XMLFile(FilePath(__file__).sibling("manifest.xhtml"))
def _si_abbrev(self):
si = self.monitor.origin_si
if not si:
return "<LIT>"
return base32.b2a(si)[:6]
@renderer
def title(self, req, tag):
return tag(
"Manifest of SI={}".format(self._si_abbrev())
)
@renderer
def header(self, req, tag):
return tag(
"Manifest of SI={}".format(self._si_abbrev())
)
@renderer
def items(self, req, tag):
manifest = self.monitor.get_status()["manifest"]
root = get_root(req)
rows = [
{
"path": _slashify_path(path),
"cap": _cap_to_link(root, path, cap),
}
for path, cap in manifest
]
return SlotsSequenceElement(tag, rows)
class ManifestResults(MultiFormatResource, ReloadMixin):
# Control MultiFormatPage
formatArgument = "output"
formatDefault = "html"
def __init__(self, client, monitor):
self.client = client
self.monitor = monitor
2019-11-05 06:38:43 +00:00
def render_HTML(self, req):
return renderElement(
req,
ManifestElement(self.monitor)
)
def render_TEXT(self, req):
req.setHeader("content-type", "text/plain")
lines = []
2008-10-22 00:52:56 +00:00
is_finished = self.monitor.is_finished()
lines.append("finished: " + {True: "yes", False: "no"}[is_finished])
2019-11-05 06:38:43 +00:00
for path, cap in self.monitor.get_status()["manifest"]:
lines.append(_slashify_path(path) + " " + cap)
return "\n".join(lines) + "\n"
def render_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": 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.
2017-01-19 22:39:53 +00:00
return json.dumps(status, indent=1)
class DeepSizeResults(MultiFormatPage):
# Control MultiFormatPage
formatArgument = "output"
formatDefault = "html"
def __init__(self, client, monitor):
self.client = client
self.monitor = monitor
def render_HTML(self, req):
2008-10-22 00:52:56 +00:00
is_finished = self.monitor.is_finished()
output = "finished: " + {True: "yes", False: "no"}[is_finished] + "\n"
if is_finished:
stats = self.monitor.get_status()
total = (stats.get("size-immutable-files", 0)
+ stats.get("size-mutable-files", 0)
+ stats.get("size-directories", 0))
output += "size: %d\n" % total
return output
render_TEXT = render_HTML
def render_JSON(self, req):
req.setHeader("content-type", "text/plain")
status = {"finished": self.monitor.is_finished(),
"size": self.monitor.get_status(),
}
2017-01-19 22:39:53 +00:00
return json.dumps(status)
2019-09-11 22:09:11 +00:00
2019-09-24 04:39:59 +00:00
class DeepStatsResults(Resource, object):
2019-09-11 22:09:11 +00:00
"""
Renders the results of a 'deep-stats' operation on a directory
capability.
"""
def __init__(self, client, monitor):
self.client = client
self.monitor = monitor
2019-09-11 22:09:11 +00:00
def render(self, req):
# JSON only
req.setHeader("content-type", "text/plain")
s = self.monitor.get_status().copy()
s["finished"] = self.monitor.is_finished()
2017-01-19 22:39:53 +00:00
return json.dumps(s, indent=1)
2009-01-23 05:01:36 +00:00
2019-09-11 22:09:11 +00:00
@implementer(IPushProducer)
2009-01-23 05:01:36 +00:00
class ManifestStreamer(dirnode.DeepStats):
def __init__(self, req, origin):
2009-01-23 05:01:36 +00:00
dirnode.DeepStats.__init__(self, origin)
self.req = req
2009-01-23 05:01:36 +00:00
def setMonitor(self, monitor):
self.monitor = monitor
def pauseProducing(self):
pass
def resumeProducing(self):
pass
def stopProducing(self):
self.monitor.cancel()
2009-01-23 05:01:36 +00:00
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):
2009-01-23 05:01:36 +00:00
d["type"] = "file"
else:
d["type"] = "unknown"
2009-01-23 05:01:36 +00:00
v = node.get_verify_cap()
if v:
v = v.to_string()
d["verifycap"] = v or ""
2009-01-23 05:01:36 +00:00
r = node.get_repair_cap()
if r:
r = r.to_string()
d["repaircap"] = r or ""
2009-01-23 05:01:36 +00:00
si = node.get_storage_index()
if si:
si = base32.b2a(si)
d["storage-index"] = si or ""
2009-01-23 05:01:36 +00:00
2017-01-19 22:39:53 +00:00
j = json.dumps(d, ensure_ascii=True)
2009-01-23 05:01:36 +00:00
assert "\n" not in j
self.req.write(j+"\n")
def finish(self):
stats = dirnode.DeepStats.get_results(self)
d = {"type": "stats",
"stats": stats,
}
2017-01-19 22:39:53 +00:00
j = json.dumps(d, ensure_ascii=True)
2009-01-23 05:01:36 +00:00
assert "\n" not in j
self.req.write(j+"\n")
return ""
@implementer(IPushProducer)
class DeepCheckStreamer(dirnode.DeepStats):
def __init__(self, req, origin, verify, repair, add_lease):
dirnode.DeepStats.__init__(self, origin)
self.req = req
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):
2017-01-19 22:39:53 +00:00
j = json.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,
}
2017-01-19 22:39:53 +00:00
j = json.dumps(d, ensure_ascii=True)
assert "\n" not in j
self.req.write(j+"\n")
return ""
class UnknownNodeHandler(Resource):
def __init__(self, client, node, parentnode=None, name=None):
super(UnknownNodeHandler, self).__init__()
assert node
self.node = node
self.parentnode = parentnode
self.name = name
def render_GET(self, req):
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(req, 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(req, 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(json.dumps(data, indent=1) + "\n", req)