this form can *only* be used with file caps; it is an error to use a
directory cap after the /named/ prefix.
+=== Get Information About A File Or Directory (as HTML) ===
+
+GET /uri/$FILECAP?t=info
+GET /uri/$DIRCAP/?t=info
+GET /uri/$DIRCAP/[SUBDIRS../]SUBDIR/?t=info
+GET /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=info
+
+ This returns a human-oriented HTML page with more detail about the selected
+ file or directory object. This page contains the following items:
+
+ object size
+ storage index
+ JSON representation
+ raw contents (text/plain)
+ access caps (URIs): verify-cap, read-cap, write-cap (for mutable objects)
+ check/verify/repair form
+ deep-check/deep-size/deep-stats/manifest (for directories)
+ replace-conents form (for mutable files)
+
=== Creating a Directory ===
POST /uri?t=mkdir
return d
-class DeepCheck(SystemTestMixin, unittest.TestCase):
+class DeepCheckWeb(SystemTestMixin, unittest.TestCase):
# construct a small directory tree (with one dir, one immutable file, one
- # mutable file, one LIT file, and a loop), and then check it in various
- # ways.
+ # mutable file, one LIT file, and a loop), and then check/examine it in
+ # various ways.
def set_up_tree(self, ignored):
# 2.9s
def web_json(self, n, **kwargs):
kwargs["output"] = "json"
- return self.web(n, "POST", **kwargs)
+ d = self.web(n, "POST", **kwargs)
+ d.addCallback(self.decode_json)
+ return d
+
+ def decode_json(self, (s,url)):
+ try:
+ data = simplejson.loads(s)
+ except ValueError:
+ self.fail("%s: not JSON: '%s'" % (url, s))
+ return data
def web(self, n, method="GET", **kwargs):
+ # returns (data, url)
url = (self.webish_url + "uri/%s" % urllib.quote(n.get_uri())
+ "?" + "&".join(["%s=%s" % (k,v) for (k,v) in kwargs.items()]))
d = getPage(url, method=method)
- def _decode(s):
- try:
- data = simplejson.loads(s)
- except ValueError:
- self.fail("%s: not JSON: '%s'" % (url, s))
- return data
- d.addCallback(_decode)
+ d.addCallback(lambda data: (data,url))
return d
def json_check_is_healthy(self, data, n, where, incomplete=False):
# stats
d.addCallback(lambda ign: self.web(self.root, t="deep-stats"))
+ d.addCallback(self.decode_json)
d.addCallback(self.json_check_stats, "deep-stats")
# check, no verify
self.web_json(self.root, t="deep-check", verify="true", repair="true"))
d.addCallback(self.json_full_deepcheck_and_repair_is_healthy, self.root, "root")
+ # now look at t=info
+ d.addCallback(lambda ign: self.web(self.root, t="info"))
+ # TODO: examine the output
+ d.addCallback(lambda ign: self.web(self.mutable, t="info"))
+ d.addCallback(lambda ign: self.web(self.large, t="info"))
+ d.addCallback(lambda ign: self.web(self.small, t="info"))
+
return d
NEW2_CONTENTS))
# finally list the directory, since mutable files are displayed
- # differently
+ # slightly differently
d.addCallback(lambda res:
self.GET(self.public_url + "/foo/",
followRedirect=True))
def _check_page(res):
# TODO: assert more about the contents
- self.failUnless("Overwrite" in res)
- self.failUnless("Choose new file:" in res)
+ self.failUnless("SSK" in res)
return res
d.addCallback(_check_page)
- # test that clicking on the "overwrite" button works
- EVEN_NEWER_CONTENTS = NEWER_CONTENTS + "even newer\n"
- def _parse_overwrite_form_and_submit(res):
-
- OVERWRITE_FORM_RE=re.compile('<form action="([^"]*)" method="post" .*<input type="hidden" name="t" value="upload" /><input type="hidden" name="when_done" value="([^"]*)" />', re.I)
- mo = OVERWRITE_FORM_RE.search(res)
- self.failUnless(mo, "overwrite form not found in '" + res +
- "', in which the overwrite form was not found")
- formaction=mo.group(1)
- formwhendone=mo.group(2)
-
- fileurl = "../../../uri/" + urllib.quote(self._mutable_uri)
- self.failUnlessEqual(formaction, fileurl)
- # to POST, we need to absoluteify the URL
- new_formaction = "/uri/%s" % urllib.quote(self._mutable_uri)
- self.failUnlessEqual(formwhendone,
- "../uri/%s/" % urllib.quote(self._foo_uri))
- return self.POST(new_formaction,
- t="upload",
- file=("new.txt", EVEN_NEWER_CONTENTS),
- when_done=formwhendone,
- followRedirect=False)
- d.addCallback(_parse_overwrite_form_and_submit)
- # This will redirect us to ../uri/$FOOURI, rather than
- # ../uri/$PARENT/foo, but apparently twisted.web.client absolutifies
- # the redirect for us, and remember that shouldRedirect prepends
- # self.webish_url for us.
- d.addBoth(self.shouldRedirect,
- "/uri/%s/" % urllib.quote(self._foo_uri),
- which="test_POST_upload_mutable.overwrite")
- d.addCallback(lambda res:
- self.failUnlessMutableChildContentsAre(fn, u"new.txt",
- EVEN_NEWER_CONTENTS))
d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
def _got3(newnode):
self.failUnless(IMutableFileNode.providedBy(newnode))
d.addCallback(lambda res:
self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
d.addCallback(lambda res:
- self.failUnlessEqual(res, EVEN_NEWER_CONTENTS))
+ self.failUnlessEqual(res, NEW2_CONTENTS))
# and that HEAD computes the size correctly
d.addCallback(lambda res:
self.HEAD(self.public_url + "/foo/new.txt"))
def _got_headers(headers):
self.failUnlessEqual(headers["content-length"][0],
- str(len(EVEN_NEWER_CONTENTS)))
+ str(len(NEW2_CONTENTS)))
self.failUnlessEqual(headers["content-type"], ["text/plain"])
d.addCallback(_got_headers)
FileNodeHandler, PlaceHolderNodeHandler
from allmydata.web.checker_results import CheckerResults, \
CheckAndRepairResults, DeepCheckResults, DeepCheckAndRepairResults
+from allmydata.web.info import MoreInfo
class BlockingFileError(Exception):
# TODO: catch and transform
if t == "json":
return DirectoryJSONMetadata(ctx, self.node)
+ if t == "info":
+ return MoreInfo(self.node)
if t == "uri":
return DirectoryURI(ctx, self.node)
if t == "readonly-uri":
ctx.fillSlots("delete", delete)
ctx.fillSlots("rename", rename)
- if IDirectoryNode.providedBy(target):
- check_url = "%s/uri/%s/" % (root, urllib.quote(target.get_uri()))
- check_done_url = "../../uri/%s/" % urllib.quote(self.node.get_uri())
- else:
- check_url = "%s/uri/%s" % (root, urllib.quote(target.get_uri()))
- check_done_url = "../uri/%s/" % urllib.quote(self.node.get_uri())
- check = T.form(action=check_url, method="post")[
- T.input(type='hidden', name='t', value='check'),
- T.input(type='hidden', name='return_to', value=check_done_url),
- T.input(type='submit', value='check', name="check"),
- ]
- ctx.fillSlots("overwrite",
- self.build_overwrite_form(ctx, name, target))
- ctx.fillSlots("check", check)
times = []
TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
ctx.fillSlots("size", "?")
text_plain_url = "%s/file/%s/@@named=/foo.txt" % (root, quoted_uri)
- text_plain_tag = T.a(href=text_plain_url)["text/plain"]
+ info_link = "%s?t=info" % name
elif IFileNode.providedBy(target):
dlurl = "%s/file/%s/@@named=/%s" % (root, quoted_uri, urllib.quote(name))
ctx.fillSlots("size", target.get_size())
text_plain_url = "%s/file/%s/@@named=/foo.txt" % (root, quoted_uri)
- text_plain_tag = T.a(href=text_plain_url)["text/plain"]
-
+ info_link = "%s?t=info" % name
elif IDirectoryNode.providedBy(target):
# directory
dirtype = "DIR"
ctx.fillSlots("type", dirtype)
ctx.fillSlots("size", "-")
- text_plain_tag = None
-
- childdata = [T.a(href="%s?t=json" % name)["JSON"], ", ",
- T.a(href="%s?t=uri" % name)["URI"], ", ",
- T.a(href="%s?t=readonly-uri" % name)["readonly-URI"],
- ]
- if text_plain_tag:
- childdata.extend([", ", text_plain_tag])
+ info_link = "%s/?t=info" % name
- ctx.fillSlots("data", childdata)
-
- results = "--"
- # TODO: include a link to see more results, including timestamps
- # TODO: use a sparkline
- ctx.fillSlots("checker_results", results)
+ ctx.fillSlots("info", T.a(href=info_link)["More Info"])
return ctx.tag
def render_forms(self, ctx, data):
forms = []
- deep_check = T.form(action=".", method="post",
- enctype="multipart/form-data")[
- T.fieldset[
- T.input(type="hidden", name="t", value="deep-check"),
- T.input(type="hidden", name="return_to", value="."),
- T.legend(class_="freeform-form-label")["Run a deep-check operation (EXPENSIVE)"],
- T.div[
- "Verify every bit? (EVEN MORE EXPENSIVE):",
- T.input(type="checkbox", name="verify"),
- ],
- T.div["Repair any problems?: ",
- T.input(type="checkbox", name="repair")],
- T.div["Emit results in JSON format?: ",
- T.input(type="checkbox", name="output", value="JSON")],
-
- T.input(type="submit", value="Deep-Check"),
-
- ]]
- forms.append(T.div(class_="freeform-form")[deep_check])
if self.node.is_readonly():
forms.append(T.div["No upload forms: directory is read-only"])
forms.append(T.div(class_="freeform-form")[mount])
return forms
- def build_overwrite_form(self, ctx, name, target):
- if IMutableFileNode.providedBy(target) and not target.is_readonly():
- root = self.get_root(ctx)
- action = "%s/uri/%s" % (root, urllib.quote(target.get_uri()))
- done_url = "../uri/%s/" % urllib.quote(self.node.get_uri())
- overwrite = T.form(action=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=done_url),
- T.legend(class_="freeform-form-label")["Overwrite"],
- "Choose new file: ",
- T.input(type="file", name="file", class_="freeform-input-file"),
- " ",
- T.input(type="submit", value="Overwrite")
- ]]
- return [T.div(class_="freeform-form")[overwrite],]
- else:
- return []
-
def render_results(self, ctx, data):
req = IRequest(ctx)
return get_arg(req, "results", "")
d.addCallback(text_plain, ctx)
return d
+
+
def DirectoryURI(ctx, dirnode):
return text_plain(dirnode.get_uri(), ctx)
<div><a href=".">Refresh this view</a></div>
<div n:render="welcome" />
-<div>Other representations of this directory:
-<a href="?t=manifest">manifest</a>,
-<a href="?t=deep-size">total size</a>,
-<a href="?t=uri">URI</a>,
-<a href="?t=readonly-uri">read-only URI</a>,
-<a href="?t=json">JSON</a>
-</div>
+<div><a href="?t=info">More info on this directory</a></div>
<div>
<table n:render="sequence" n:data="children" border="1">
<td>Type</td>
<td>Size</td>
<td>Times</td>
- <td>other representations</td>
- <td></td>
<td></td>
<td></td>
<td></td>
- <td>Checker Results</td>
</tr>
<tr n:pattern="item" n:render="row">
<td><n:slot name="filename"/></td>
<td><n:slot name="type"/></td>
<td><n:slot name="size"/></td>
<td><n:slot name="times"/></td>
- <td><n:slot name="data"/></td>
<td><n:slot name="delete"/></td>
- <td><n:slot name="overwrite"/></td>
<td><n:slot name="rename"/></td>
-
- <td><n:slot name="check"/></td>
- <td><n:slot name="checker_results"/></td>
+ <td><n:slot name="info"/></td>
</tr>
<tr n:pattern="empty"><td>directory is empty!</td></tr>
boolean_of_arg, get_arg, should_create_intermediate_directories
from allmydata.web.checker_results import CheckerResults, \
CheckAndRepairResults, LiteralCheckerResults
+from allmydata.web.info import MoreInfo
class ReplaceMeMixin:
return FileDownloader(self.node, filename, save_to_file)
if t == "json":
return FileJSONMetadata(ctx, self.node)
+ if t == "info":
+ return MoreInfo(self.node)
if t == "uri":
return FileURI(ctx, self.node)
if t == "readonly-uri":
--- /dev/null
+
+import urllib
+
+from twisted.internet import defer
+from nevow import rend, tags as T
+from nevow.inevow import IRequest
+
+from allmydata.util import base32
+from allmydata.interfaces import IDirectoryNode
+from allmydata.web.common import getxmlfile
+
+class MoreInfo(rend.Page):
+ addSlash = False
+ docFactory = getxmlfile("info.xhtml")
+
+ def abbrev(self, storage_index_or_none):
+ if storage_index_or_none:
+ return base32.b2a(storage_index_or_none)[:6]
+ return "LIT file"
+
+ def get_type(self):
+ node = self.original
+ si = node.get_storage_index()
+ if IDirectoryNode.providedBy(node):
+ return "directory"
+ if si:
+ if node.is_mutable():
+ return "mutable file"
+ return "immutable file"
+ return "LIT file"
+
+ def render_title(self, ctx, data):
+ node = self.original
+ si = node.get_storage_index()
+ t = "More Info for %s" % self.get_type()
+ if si:
+ t += " (SI=%s)" % self.abbrev(si)
+ return ctx.tag[t]
+
+ def render_header(self, ctx, data):
+ return self.render_title(ctx, data)
+
+ def render_type(self, ctx, data):
+ return ctx.tag[self.get_type()]
+
+ def render_si(self, ctx, data):
+ si = self.original.get_storage_index()
+ if not si:
+ return "None"
+ return ctx.tag[base32.b2a(si)]
+
+ def render_size(self, ctx, data):
+ node = self.original
+ si = node.get_storage_index()
+ if IDirectoryNode.providedBy(node):
+ d = node._node.get_size_of_best_version()
+ elif node.is_mutable():
+ d = node.get_size_of_best_version()
+ else:
+ # for immutable files and LIT files, we get the size from the URI
+ d = defer.succeed(node.get_size())
+ d.addCallback(lambda size: ctx.tag[size])
+ return d
+
+ def render_directory_writecap(self, ctx, data):
+ node = self.original
+ if node.is_readonly():
+ return ""
+ if not IDirectoryNode.providedBy(node):
+ return ""
+ return ctx.tag[node.get_uri()]
+
+ def render_directory_readcap(self, ctx, data):
+ node = self.original
+ if not IDirectoryNode.providedBy(node):
+ return ""
+ return ctx.tag[node.get_readonly_uri()]
+
+ def render_directory_verifycap(self, ctx, data):
+ node = self.original
+ if not IDirectoryNode.providedBy(node):
+ return ""
+ return ctx.tag[node.get_verifier().to_string()]
+
+
+ def render_file_writecap(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ node = node._node
+ if node.is_readonly():
+ return ""
+ return ctx.tag[node.get_uri()]
+
+ def render_file_readcap(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ node = node._node
+ return ctx.tag[node.get_readonly_uri()]
+
+ def render_file_verifycap(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ node = node._node
+ verifier = node.get_verifier()
+ if verifier:
+ return ctx.tag[node.get_verifier().to_string()]
+ return ""
+
+ def get_root(self, ctx):
+ req = IRequest(ctx)
+ # the addSlash=True gives us one extra (empty) segment
+ depth = len(req.prepath) + len(req.postpath) - 1
+ link = "/".join([".."] * depth)
+ return link
+
+ def render_raw_link(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ node = node._node
+ root = self.get_root(ctx)
+ quoted_uri = urllib.quote(node.get_uri())
+ text_plain_url = "%s/file/%s/@@named=/raw.txt" % (root, quoted_uri)
+ return ctx.tag[text_plain_url]
+
+ def render_is_checkable(self, ctx, data):
+ node = self.original
+ si = node.get_storage_index()
+ if si:
+ return ctx.tag
+ # don't show checker button for LIT files
+ return ""
+
+ def render_check_form(self, ctx, data):
+ check = T.form(action=".", method="post",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.input(type="hidden", name="t", value="check"),
+ T.input(type="hidden", name="return_to", value="."),
+ T.legend(class_="freeform-form-label")["Check on this object"],
+ T.div[
+ "Verify every bit? (EXPENSIVE):",
+ T.input(type="checkbox", name="verify"),
+ ],
+ T.div["Repair any problems?: ",
+ T.input(type="checkbox", name="repair")],
+ T.div["Emit results in JSON format?: ",
+ T.input(type="checkbox", name="output", value="JSON")],
+
+ T.input(type="submit", value="Check"),
+
+ ]]
+ return ctx.tag[check]
+
+ def render_is_mutable_file(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ return ""
+ if node.is_mutable() and not node.is_readonly():
+ return ctx.tag
+ return ""
+
+ def render_overwrite_form(self, ctx, data):
+ node = self.original
+ root = self.get_root(ctx)
+ action = "%s/uri/%s" % (root, urllib.quote(node.get_uri()))
+ done_url = "%s/uri/%s?t=info" % (root, urllib.quote(node.get_uri()))
+ overwrite = T.form(action=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=done_url),
+ T.legend(class_="freeform-form-label")["Overwrite"],
+ "Upload new contents: ",
+ T.input(type="file", name="file"),
+ " ",
+ T.input(type="submit", value="Replace Contents")
+ ]]
+ return ctx.tag[overwrite]
+
+ def render_is_directory(self, ctx, data):
+ node = self.original
+ if IDirectoryNode.providedBy(node):
+ return ctx.tag
+ return ""
+
+ def render_deep_check_form(self, ctx, data):
+ deep_check = T.form(action=".", method="post",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.input(type="hidden", name="t", value="deep-check"),
+ T.input(type="hidden", name="return_to", value="."),
+ T.legend(class_="freeform-form-label")["Run a deep-check operation (EXPENSIVE)"],
+ T.div[
+ "Verify every bit? (EVEN MORE EXPENSIVE):",
+ T.input(type="checkbox", name="verify"),
+ ],
+ T.div["Repair any problems?: ",
+ T.input(type="checkbox", name="repair")],
+ T.div["Emit results in JSON format?: ",
+ T.input(type="checkbox", name="output", value="JSON")],
+
+ T.input(type="submit", value="Deep-Check"),
+
+ ]]
+ return ctx.tag[deep_check]
+
+ def render_deep_size_form(self, ctx, data):
+ deep_size = T.form(action=".", method="get",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.input(type="hidden", name="t", value="deep-size"),
+ T.legend(class_="freeform-form-label")["Run a deep-size operation (EXPENSIVE)"],
+ T.input(type="submit", value="Deep-Size"),
+ ]]
+ return ctx.tag[deep_size]
+
+ def render_deep_stats_form(self, ctx, data):
+ deep_stats = T.form(action=".", method="get",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.input(type="hidden", name="t", value="deep-stats"),
+ T.legend(class_="freeform-form-label")["Run a deep-stats operation (EXPENSIVE)"],
+ T.input(type="submit", value="Deep-Stats"),
+ ]]
+ return ctx.tag[deep_stats]
+
+ def render_manifest_form(self, ctx, data):
+ manifest = T.form(action=".", method="get",
+ enctype="multipart/form-data")[
+ T.fieldset[
+ T.input(type="hidden", name="t", value="manifest"),
+ T.legend(class_="freeform-form-label")["Run a manifest operation (EXPENSIVE)"],
+ T.input(type="submit", value="Manifest"),
+ ]]
+ return ctx.tag[manifest]
+
+
+# TODO: edge metadata
--- /dev/null
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+ <head>
+ <title n:render="title"></title>
+ <!-- <link href="http://www.allmydata.com/common/css/styles.css"
+ rel="stylesheet" type="text/css"/> -->
+ <link href="/webform_css" rel="stylesheet" type="text/css"/>
+ <link href="/tahoe_css" rel="stylesheet" type="text/css"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ </head>
+ <body>
+
+<h2 n:render="header"></h2>
+
+<ul>
+ <li>Object Type: <span n:render="type" /></li>
+ <li>Storage Index: <tt n:render="si" /></li>
+ <li>Object Size: <span n:render="size" /></li>
+ <li>Access Caps (URIs):
+ <table border="1">
+ <span n:render="is_directory">
+ <tr>
+ <td>Directory writecap</td>
+ <td><tt n:render="directory_writecap" /></td>
+ </tr>
+ <tr>
+ <td>Directory readcap</td>
+ <td><tt n:render="directory_readcap" /></td>
+ </tr>
+ <tr>
+ <td>Directory verifycap</td>
+ <td><tt n:render="directory_verifycap" /></td>
+ </tr>
+ </span>
+
+ <tr>
+ <td>File writecap</td>
+ <td><tt n:render="file_writecap" /></td>
+ </tr>
+ <tr>
+ <td>File readcap</td>
+ <td><tt n:render="file_readcap" /></td>
+ </tr>
+ <tr>
+ <td>File verifycap</td>
+ <td><tt n:render="file_verifycap" /></td>
+ </tr>
+ </table></li>
+ <li><a href="?t=json">JSON</a></li>
+ <li>Raw data as <a><n:attr name="href" n:render="raw_link" />text/plain</a></li>
+</ul>
+
+<div n:render="is_checkable">
+ <h2>Checker Operations</h2>
+ <div n:render="check_form" />
+</div>
+
+<div n:render="is_mutable_file">
+ <h2>Mutable File Operations</h2>
+ <div n:render="overwrite_form" />
+</div>
+
+<div n:render="is_directory">
+ <h2>Directory Operations</h2>
+ <div n:render="deep_check_form" />
+ <div n:render="deep_size_form" />
+ <div n:render="deep_stats_form" />
+ <div n:render="manifest_form" />
+</div>
+
+
+ </body>
+</html>