source file: /home/buildslave/tahoe/edgy/build/src/allmydata/web/root.py
file stats: 198 lines, 191 executed: 96.5% covered
   1. 
   2. import time
   3. 
   4. from twisted.internet import address
   5. from twisted.web import http
   6. from nevow import rend, url, tags as T
   7. from nevow.inevow import IRequest
   8. from nevow.static import File as nevow_File # TODO: merge with static.File?
   9. from nevow.util import resource_filename
  10. from formless import webform
  11. 
  12. import allmydata # to display import path
  13. from allmydata import get_package_versions_string
  14. from allmydata import provisioning
  15. from allmydata.util import idlib, log
  16. from allmydata.interfaces import IFileNode
  17. from allmydata.web import filenode, directory, unlinked, status
  18. from allmydata.web.common import abbreviate_size, IClient, getxmlfile, \
  19.      WebError, get_arg, RenderMixin
  20. 
  21. 
  22. 
  23. class URIHandler(RenderMixin, rend.Page):
  24.     # I live at /uri . There are several operations defined on /uri itself,
  25.     # mostly involved with creation of unlinked files and directories.
  26. 
  27.     def render_GET(self, ctx):
  28.         req = IRequest(ctx)
  29.         uri = get_arg(req, "uri", None)
  30.         if uri is None:
  31.             raise WebError("GET /uri requires uri=")
  32.         there = url.URL.fromContext(ctx)
  33.         there = there.clear("uri")
  34.         # I thought about escaping the childcap that we attach to the URL
  35.         # here, but it seems that nevow does that for us.
  36.         there = there.child(uri)
  37.         return there
  38. 
  39.     def render_PUT(self, ctx):
  40.         req = IRequest(ctx)
  41.         # either "PUT /uri" to create an unlinked file, or
  42.         # "PUT /uri?t=mkdir" to create an unlinked directory
  43.         t = get_arg(req, "t", "").strip()
  44.         if t == "":
  45.             mutable = bool(get_arg(req, "mutable", "").strip())
  46.             if mutable:
  47.                 return unlinked.PUTUnlinkedSSK(ctx)
  48.             else:
  49.                 return unlinked.PUTUnlinkedCHK(ctx)
  50.         if t == "mkdir":
  51.             return unlinked.PUTUnlinkedCreateDirectory(ctx)
  52.         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
  53.                   "and POST?t=mkdir")
  54.         raise WebError(errmsg, http.BAD_REQUEST)
  55. 
  56.     def render_POST(self, ctx):
  57.         # "POST /uri?t=upload&file=newfile" to upload an
  58.         # unlinked file or "POST /uri?t=mkdir" to create a
  59.         # new directory
  60.         req = IRequest(ctx)
  61.         t = get_arg(req, "t", "").strip()
  62.         if t in ("", "upload"):
  63.             mutable = bool(get_arg(req, "mutable", "").strip())
  64.             if mutable:
  65.                 return unlinked.POSTUnlinkedSSK(ctx)
  66.             else:
  67.                 return unlinked.POSTUnlinkedCHK(ctx)
  68.         if t == "mkdir":
  69.             return unlinked.POSTUnlinkedCreateDirectory(ctx)
  70.         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
  71.                   "and POST?t=mkdir")
  72.         raise WebError(errmsg, http.BAD_REQUEST)
  73. 
  74.     def childFactory(self, ctx, name):
  75.         # 'name' is expected to be a URI
  76.         client = IClient(ctx)
  77.         try:
  78.             node = client.create_node_from_uri(name)
  79.             return directory.make_handler_for(node)
  80.         except (TypeError, AssertionError):
  81.             raise WebError("'%s' is not a valid file- or directory- cap"
  82.                            % name)
  83. 
  84. class FileHandler(rend.Page):
  85.     # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
  86.     # file can be downloaded correctly by tools like "wget".
  87. 
  88.     def childFactory(self, ctx, name):
  89.         req = IRequest(ctx)
  90.         if req.method not in ("GET", "HEAD"):
  91.             raise WebError("/file can only be used with GET or HEAD")
  92.         # 'name' must be a file URI
  93.         client = IClient(ctx)
  94.         try:
  95.             node = client.create_node_from_uri(name)
  96.         except (TypeError, AssertionError):
  97.             raise WebError("'%s' is not a valid file- or directory- cap"
  98.                            % name)
  99.         if not IFileNode.providedBy(node):
 100.             raise WebError("'%s' is not a file-cap" % name)
 101.         return filenode.FileNodeDownloadHandler(node)
 102. 
 103.     def renderHTTP(self, ctx):
 104.         raise WebError("/file must be followed by a file-cap and a name",
 105.                        http.NOT_FOUND)
 106. 
 107. class IncidentReporter(RenderMixin, rend.Page):
 108.     def render_POST(self, ctx):
 109.         req = IRequest(ctx)
 110.         log.msg(format="User reports incident through web page: %(details)s",
 111.                 details=get_arg(req, "details", ""),
 112.                 level=log.WEIRD, umid="LkD9Pw")
 113.         req.setHeader("content-type", "text/plain")
 114.         return "Thank you for your report!"
 115. 
 116. class Root(rend.Page):
 117. 
 118.     addSlash = True
 119.     docFactory = getxmlfile("welcome.xhtml")
 120. 
 121.     child_uri = URIHandler()
 122.     child_cap = URIHandler()
 123.     child_file = FileHandler()
 124.     child_named = FileHandler()
 125. 
 126.     child_webform_css = webform.defaultCSS
 127.     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
 128. 
 129.     child_provisioning = provisioning.ProvisioningTool()
 130.     child_status = status.Status()
 131.     child_helper_status = status.HelperStatus()
 132.     child_statistics = status.Statistics()
 133. 
 134.     child_report_incident = IncidentReporter()
 135. 
 136.     def data_version(self, ctx, data):
 137.         return get_package_versions_string()
 138.     def data_import_path(self, ctx, data):
 139.         return str(allmydata)
 140.     def data_my_nodeid(self, ctx, data):
 141.         return idlib.nodeid_b2a(IClient(ctx).nodeid)
 142.     def data_my_nickname(self, ctx, data):
 143.         return IClient(ctx).nickname
 144. 
 145.     def render_services(self, ctx, data):
 146.         ul = T.ul()
 147.         client = IClient(ctx)
 148.         try:
 149.             ss = client.getServiceNamed("storage")
 150.             allocated_s = abbreviate_size(ss.allocated_size())
 151.             allocated = "about %s allocated" % allocated_s
 152.             sizelimit = "no size limit"
 153.             if ss.sizelimit is not None:
 154.                 sizelimit = "size limit is %s" % abbreviate_size(ss.sizelimit)
 155.             ul[T.li["Storage Server: %s, %s" % (allocated, sizelimit)]]
 156.         except KeyError:
 157.             ul[T.li["Not running storage server"]]
 158. 
 159.         try:
 160.             h = client.getServiceNamed("helper")
 161.             stats = h.get_stats()
 162.             active_uploads = stats["chk_upload_helper.active_uploads"]
 163.             ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
 164.         except KeyError:
 165.             ul[T.li["Not running helper"]]
 166. 
 167.         return ctx.tag[ul]
 168. 
 169.     def data_introducer_furl(self, ctx, data):
 170.         return IClient(ctx).introducer_furl
 171.     def data_connected_to_introducer(self, ctx, data):
 172.         if IClient(ctx).connected_to_introducer():
 173.             return "yes"
 174.         return "no"
 175. 
 176.     def data_helper_furl(self, ctx, data):
 177.         try:
 178.             uploader = IClient(ctx).getServiceNamed("uploader")
 179.         except KeyError:
 180.             return None
 181.         furl, connected = uploader.get_helper_info()
 182.         return furl
 183.     def data_connected_to_helper(self, ctx, data):
 184.         try:
 185.             uploader = IClient(ctx).getServiceNamed("uploader")
 186.         except KeyError:
 187.             return "no" # we don't even have an Uploader
 188.         furl, connected = uploader.get_helper_info()
 189.         if connected:
 190.             return "yes"
 191.         return "no"
 192. 
 193.     def data_known_storage_servers(self, ctx, data):
 194.         ic = IClient(ctx).introducer_client
 195.         servers = [c
 196.                    for c in ic.get_all_connectors().values()
 197.                    if c.service_name == "storage"]
 198.         return len(servers)
 199. 
 200.     def data_connected_storage_servers(self, ctx, data):
 201.         ic = IClient(ctx).introducer_client
 202.         return len(ic.get_all_connections_for("storage"))
 203. 
 204.     def data_services(self, ctx, data):
 205.         ic = IClient(ctx).introducer_client
 206.         c = [ (service_name, nodeid, rsc)
 207.               for (nodeid, service_name), rsc
 208.               in ic.get_all_connectors().items() ]
 209.         c.sort()
 210.         return c
 211. 
 212.     def render_service_row(self, ctx, data):
 213.         (service_name, nodeid, rsc) = data
 214.         ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
 215.         ctx.fillSlots("nickname", rsc.nickname)
 216.         if rsc.rref:
 217.             rhost = rsc.remote_host
 218.             if nodeid == IClient(ctx).nodeid:
 219.                 rhost_s = "(loopback)"
 220.             elif isinstance(rhost, address.IPv4Address):
 221.                 rhost_s = "%s:%d" % (rhost.host, rhost.port)
 222.             else:
 223.                 rhost_s = str(rhost)
 224.             connected = "Yes: to " + rhost_s
 225.             since = rsc.last_connect_time
 226.         else:
 227.             connected = "No"
 228.             since = rsc.last_loss_time
 229. 
 230.         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
 231.         ctx.fillSlots("connected", connected)
 232.         ctx.fillSlots("since", time.strftime(TIME_FORMAT, time.localtime(since)))
 233.         ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
 234.                                                  time.localtime(rsc.announcement_time)))
 235.         ctx.fillSlots("version", rsc.version)
 236.         ctx.fillSlots("service_name", rsc.service_name)
 237. 
 238.         return ctx.tag
 239. 
 240.     def render_download_form(self, ctx, data):
 241.         # this is a form where users can download files by URI
 242.         form = T.form(action="uri", method="get",
 243.                       enctype="multipart/form-data")[
 244.             T.fieldset[
 245.             T.legend(class_="freeform-form-label")["Download a file"],
 246.             "URI to download: ",
 247.             T.input(type="text", name="uri"), " ",
 248.             "Filename to download as: ",
 249.             T.input(type="text", name="filename"), " ",
 250.             T.input(type="submit", value="Download!"),
 251.             ]]
 252.         return T.div[form]
 253. 
 254.     def render_view_form(self, ctx, data):
 255.         # this is a form where users can download files by URI, or jump to a
 256.         # named directory
 257.         form = T.form(action="uri", method="get",
 258.                       enctype="multipart/form-data")[
 259.             T.fieldset[
 260.             T.legend(class_="freeform-form-label")["View a file or directory"],
 261.             "URI to view: ",
 262.             T.input(type="text", name="uri"), " ",
 263.             T.input(type="submit", value="View!"),
 264.             ]]
 265.         return T.div[form]
 266. 
 267.     def render_upload_form(self, ctx, data):
 268.         # this is a form where users can upload unlinked files
 269.         form = T.form(action="uri", method="post",
 270.                       enctype="multipart/form-data")[
 271.             T.fieldset[
 272.             T.legend(class_="freeform-form-label")["Upload a file"],
 273.             "Choose a file: ",
 274.             T.input(type="file", name="file", class_="freeform-input-file"),
 275.             T.input(type="hidden", name="t", value="upload"),
 276.             " Mutable?:", T.input(type="checkbox", name="mutable"),
 277.             T.input(type="submit", value="Upload!"),
 278.             ]]
 279.         return T.div[form]
 280. 
 281.     def render_mkdir_form(self, ctx, data):
 282.         # this is a form where users can create new directories
 283.         form = T.form(action="uri", method="post",
 284.                       enctype="multipart/form-data")[
 285.             T.fieldset[
 286.             T.legend(class_="freeform-form-label")["Create a directory"],
 287.             T.input(type="hidden", name="t", value="mkdir"),
 288.             T.input(type="hidden", name="redirect_to_result", value="true"),
 289.             T.input(type="submit", value="Create Directory!"),
 290.             ]]
 291.         return T.div[form]
 292. 
 293.     def render_incident_button(self, ctx, data):
 294.         # this button triggers a foolscap-logging "incident"
 295.         form = T.form(action="report_incident", method="post",
 296.                       enctype="multipart/form-data")[
 297.             T.fieldset[
 298.             T.legend(class_="freeform-form-label")["Report an Incident"],
 299.             T.input(type="hidden", name="t", value="report-incident"),
 300.             "What went wrong?: ",
 301.             T.input(type="text", name="details"), " ",
 302.             T.input(type="submit", value="Report!"),
 303.             ]]
 304.         return T.div[form]