web: change t=manifest to return a list of (path,read/writecap) tuples, instead of a list of verifycaps. Add output=html,text,json.

This commit is contained in:
Brian Warner
2008-10-06 21:36:18 -07:00
parent 8e6d122ecf
commit 3ffaded809
9 changed files with 98 additions and 27 deletions

View File

@ -871,7 +871,17 @@ POST $URL?t=deep-check&repair=true
GET $DIRURL?t=manifest GET $DIRURL?t=manifest
Return an HTML-formatted manifest of the given directory, for debugging. Return an HTML-formatted manifest of the given directory, for debugging.
This is a table of verifier-caps. This is a table of (path, filecap/dircap), for every object reachable from
the starting directory. The path will be slash-joined, and the
filecap/dircap will contain a link to the object in question. This page
gives immediate access to every object in the virtual filesystem subtree.
If output=text is added to the query args, the results will be a text/plain
list, with one file/dir per line, slash-separated, with the filecap/dircap
separated by a space.
If output=JSON is added to the queryargs, then the results will be a
JSON-formatted list of (path, cap) tuples, where path is a list of strings.
GET $DIRURL?t=deep-size GET $DIRURL?t=deep-size

View File

@ -503,8 +503,8 @@ class NewDirectoryNode:
def build_manifest(self): def build_manifest(self):
"""Return a frozenset of verifier-capability strings for all nodes """Return a list of (path, cap) tuples, for all nodes (directories
(directories and files) reachable from this one.""" and files) reachable from this one."""
return self.deep_traverse(ManifestWalker()) return self.deep_traverse(ManifestWalker())
def deep_stats(self): def deep_stats(self):
@ -521,17 +521,13 @@ class NewDirectoryNode:
class ManifestWalker: class ManifestWalker:
def __init__(self): def __init__(self):
self.manifest = set() self.manifest = []
def add_node(self, node, path): def add_node(self, node, path):
v = node.get_verifier() self.manifest.append( (tuple(path), node.get_uri()) )
# LIT files have no verify-cap, so don't add them
if v:
assert not isinstance(v, str), "ICK: %s %s" % (v, node)
self.manifest.add(v.to_string())
def enter_directory(self, parent, children): def enter_directory(self, parent, children):
pass pass
def finish(self): def finish(self):
return frozenset(self.manifest) return self.manifest
class DeepStats: class DeepStats:

View File

@ -793,9 +793,10 @@ class IDirectoryNode(IMutableFilesystemNode):
operation finishes. The child name must be a unicode string.""" operation finishes. The child name must be a unicode string."""
def build_manifest(): def build_manifest():
"""Return a Deferred that fires with a frozenset of """Return a Deferred that fires with a list of (path, cap) tuples for
verifier-capability strings for all nodes (directories and files) nodes (directories and files) reachable from this one. 'path' will be
reachable from this one.""" a tuple of unicode strings. The origin dirnode will be represented by
an empty path tuple."""
def deep_stats(): def deep_stats():
"""Return a Deferred that fires with a dictionary of statistics """Return a Deferred that fires with a dictionary of statistics

View File

@ -280,7 +280,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro) self.failUnless(u_ro.startswith("URI:DIR2-RO:"), u_ro)
u_v = n.get_verifier().to_string() u_v = n.get_verifier().to_string()
self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v) self.failUnless(u_v.startswith("URI:DIR2-Verifier:"), u_v)
self.expected_manifest.append(u_v) self.expected_manifest.append( ((), u) )
expected_si = n._uri._filenode_uri.storage_index expected_si = n._uri._filenode_uri.storage_index
self.failUnlessEqual(n.get_storage_index(), expected_si) self.failUnlessEqual(n.get_storage_index(), expected_si)
@ -292,7 +292,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
other_file_uri = make_mutable_file_uri() other_file_uri = make_mutable_file_uri()
m = Marker(fake_file_uri) m = Marker(fake_file_uri)
ffu_v = m.get_verifier().to_string() ffu_v = m.get_verifier().to_string()
self.expected_manifest.append(ffu_v) self.expected_manifest.append( ((u"child",) , m.get_uri()) )
d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri)) d.addCallback(lambda res: n.set_uri(u"child", fake_file_uri))
d.addCallback(lambda res: d.addCallback(lambda res:
self.shouldFail(ExistingChildError, "set_uri-no", self.shouldFail(ExistingChildError, "set_uri-no",
@ -312,7 +312,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin, testutil.StallMixin):
self.subdir = subdir self.subdir = subdir
new_v = subdir.get_verifier().to_string() new_v = subdir.get_verifier().to_string()
assert isinstance(new_v, str) assert isinstance(new_v, str)
self.expected_manifest.append(new_v) self.expected_manifest.append( ((u"subdir",), subdir.get_uri()) )
d.addCallback(_created) d.addCallback(_created)
d.addCallback(lambda res: d.addCallback(lambda res:

View File

@ -928,13 +928,14 @@ class SystemTest(SystemTestMixin, unittest.TestCase):
d1.addCallback(lambda res: home.build_manifest()) d1.addCallback(lambda res: home.build_manifest())
d1.addCallback(self.log, "manifest") d1.addCallback(self.log, "manifest")
# four items: # five items:
# P/
# P/personal/ # P/personal/
# P/personal/sekrit data # P/personal/sekrit data
# P/s2-rw (same as P/s2-ro) # P/s2-rw (same as P/s2-ro)
# P/s2-rw/mydata992 (same as P/s2-rw/mydata992) # P/s2-rw/mydata992 (same as P/s2-rw/mydata992)
d1.addCallback(lambda manifest: d1.addCallback(lambda manifest:
self.failUnlessEqual(len(manifest), 4)) self.failUnlessEqual(len(manifest), 5))
d1.addCallback(lambda res: home.deep_stats()) d1.addCallback(lambda res: home.deep_stats())
def _check_stats(stats): def _check_stats(stats):
expected = {"count-immutable-files": 1, expected = {"count-immutable-files": 1,

View File

@ -831,10 +831,33 @@ class Web(WebMixin, unittest.TestCase):
return d return d
def test_GET_DIRURL_manifest(self): def test_GET_DIRURL_manifest(self):
d = self.GET(self.public_url + "/foo?t=manifest", followRedirect=True) def getman(ignored, suffix, followRedirect=False):
def _got(manifest): return self.GET(self.public_url + "/foo" + suffix,
self.failUnless("Refresh Capabilities" in manifest) followRedirect=followRedirect)
d.addCallback(_got) d = defer.succeed(None)
d.addCallback(getman, "?t=manifest", followRedirect=True)
def _got_html(manifest):
self.failUnless("Manifest of SI=" in manifest)
self.failUnless("<td>sub</td>" in manifest)
self.failUnless(self._sub_uri in manifest)
self.failUnless("<td>sub/baz.txt</td>" in manifest)
d.addCallback(_got_html)
d.addCallback(getman, "/?t=manifest")
d.addCallback(_got_html)
d.addCallback(getman, "/?t=manifest&output=text")
def _got_text(manifest):
self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
d.addCallback(_got_text)
d.addCallback(getman, "/?t=manifest&output=JSON")
def _got_json(manifest):
data = simplejson.loads(manifest)
got = {}
for (path_list, cap) in data:
got[tuple(path_list)] = cap
self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
self.failUnless((u"sub",u"baz.txt") in got)
d.addCallback(_got_json)
return d return d
def test_GET_DIRURL_deepsize(self): def test_GET_DIRURL_deepsize(self):

View File

@ -6,7 +6,7 @@ import time
from twisted.internet import defer from twisted.internet import defer
from twisted.python.failure import Failure from twisted.python.failure import Failure
from twisted.web import http, html from twisted.web import http, html
from nevow import url, rend, tags as T from nevow import url, rend, inevow, tags as T
from nevow.inevow import IRequest from nevow.inevow import IRequest
from foolscap.eventual import fireEventually from foolscap.eventual import fireEventually
@ -676,6 +676,36 @@ class RenameForm(rend.Page):
class Manifest(rend.Page): class Manifest(rend.Page):
docFactory = getxmlfile("manifest.xhtml") docFactory = getxmlfile("manifest.xhtml")
def renderHTTP(self, ctx):
output = get_arg(inevow.IRequest(ctx), "output", "html").lower()
if output == "text":
return self.text(ctx)
if output == "json":
return self.json(ctx)
return rend.Page.renderHTTP(self, ctx)
def slashify_path(self, path):
if not path:
return ""
return "/".join([p.encode("utf-8") for p in path])
def text(self, ctx):
inevow.IRequest(ctx).setHeader("content-type", "text/plain")
d = self.original.build_manifest()
def _render_text(manifest):
lines = []
for (path, cap) in manifest:
lines.append(self.slashify_path(path) + " " + cap)
return "\n".join(lines) + "\n"
d.addCallback(_render_text)
return d
def json(self, ctx):
inevow.IRequest(ctx).setHeader("content-type", "text/plain")
d = self.original.build_manifest()
d.addCallback(lambda manifest: simplejson.dumps(manifest))
return d
def render_title(self, ctx): def render_title(self, ctx):
return T.title["Manifest of SI=%s" % abbreviated_dirnode(self.original)] return T.title["Manifest of SI=%s" % abbreviated_dirnode(self.original)]
@ -685,8 +715,9 @@ class Manifest(rend.Page):
def data_items(self, ctx, data): def data_items(self, ctx, data):
return self.original.build_manifest() return self.original.build_manifest()
def render_row(self, ctx, refresh_cap): def render_row(self, ctx, (path, cap)):
ctx.fillSlots("refresh_capability", refresh_cap) ctx.fillSlots("path", self.slashify_path(path))
ctx.fillSlots("cap", cap)
return ctx.tag return ctx.tag
def DeepSize(ctx, dirnode): def DeepSize(ctx, dirnode):

View File

@ -230,6 +230,13 @@ class MoreInfo(rend.Page):
T.fieldset[ T.fieldset[
T.input(type="hidden", name="t", value="manifest"), T.input(type="hidden", name="t", value="manifest"),
T.legend(class_="freeform-form-label")["Run a manifest operation (EXPENSIVE)"], T.legend(class_="freeform-form-label")["Run a manifest operation (EXPENSIVE)"],
T.div["Output Format: ",
T.select(name="output")
[ T.option(value="html", selected="true")["HTML"],
T.option(value="text")["text"],
T.option(value="json")["JSON"],
],
],
T.input(type="submit", value="Manifest"), T.input(type="submit", value="Manifest"),
]] ]]
return ctx.tag[manifest] return ctx.tag[manifest]

View File

@ -13,10 +13,12 @@
<table n:render="sequence" n:data="items" border="1"> <table n:render="sequence" n:data="items" border="1">
<tr n:pattern="header"> <tr n:pattern="header">
<td>Refresh Capabilities</td> <td>Path</td>
<td>cap</td>
</tr> </tr>
<tr n:pattern="item" n:render="row"> <tr n:pattern="item" n:render="row">
<td><n:slot name="refresh_capability"/></td> <td><n:slot name="path"/></td>
<td><n:slot name="cap"/></td>
</tr> </tr>
<tr n:pattern="empty"><td>no items in the manifest!</td></tr> <tr n:pattern="empty"><td>no items in the manifest!</td></tr>