From: Brian Warner <warner@lothar.com>
Date: Mon, 4 Dec 2006 11:06:09 +0000 (-0700)
Subject: add a (read-only) web frontend. Put a 'webport' file in your base directory to activa... 
X-Git-Tag: tahoe_v0.1.0-0-UNSTABLE~465
X-Git-Url: https://git.rkrishnan.org/%5B/frontends/CLI.rst?a=commitdiff_plain;h=a3700cc582342d013efbec2d63a03e68e54ac1f9;p=tahoe-lafs%2Ftahoe-lafs.git

add a (read-only) web frontend. Put a 'webport' file in your base directory to activate it.
---

diff --git a/allmydata/client.py b/allmydata/client.py
index 8afc3ac2..f0ecd6e6 100644
--- a/allmydata/client.py
+++ b/allmydata/client.py
@@ -14,6 +14,7 @@ from allmydata.storageserver import StorageServer
 from allmydata.upload import Uploader
 from allmydata.download import Downloader
 from allmydata.vdrive import VDrive
+from allmydata.webish import WebishServer
 
 class Client(node.Node, Referenceable):
     implements(RIClient)
@@ -21,6 +22,7 @@ class Client(node.Node, Referenceable):
     PORTNUMFILE = "client.port"
     STOREDIR = 'storage'
     NODETYPE = "client"
+    WEBPORTFILE = "webport"
 
     def __init__(self, basedir="."):
         node.Node.__init__(self, basedir)
@@ -31,6 +33,12 @@ class Client(node.Node, Referenceable):
         self.add_service(Uploader())
         self.add_service(Downloader())
         self.add_service(VDrive())
+        WEBPORTFILE = os.path.join(self.basedir, self.WEBPORTFILE)
+        if os.path.exists(WEBPORTFILE):
+            f = open(WEBPORTFILE, "r")
+            webport = int(f.read())
+            f.close()
+            self.add_service(WebishServer(webport))
         self.queen_pburl = None
         self.queen_connector = None
 
@@ -73,6 +81,8 @@ class Client(node.Node, Referenceable):
 
     def _got_vdrive_root(self, root):
         self.getServiceNamed("vdrive").set_root(root)
+        if "webish" in self.namedServices:
+            self.getServiceNamed("webish").set_root_dirnode(root)
 
     def _lost_queen(self):
         self.log("lost connection to queen")
diff --git a/allmydata/download.py b/allmydata/download.py
index 58b8b4b2..a344d039 100644
--- a/allmydata/download.py
+++ b/allmydata/download.py
@@ -195,10 +195,14 @@ class FileHandle:
     def finish(self):
         pass
 
+class IDownloader(Interface):
+    def download(verifierid, target):
+        pass
 
 class Downloader(service.MultiService):
     """I am a service that allows file downloading.
     """
+    implements(IDownloader)
     name = "downloader"
 
     def download(self, verifierid, t):
diff --git a/allmydata/web/directory.xhtml b/allmydata/web/directory.xhtml
new file mode 100644
index 00000000..1e68ca3d
--- /dev/null
+++ b/allmydata/web/directory.xhtml
@@ -0,0 +1,36 @@
+<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"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  </head>
+  <body>
+
+<h1><p n:render="header"></p></h1>
+
+<div><a href=".">Refresh this view</a></div>
+<div><a href="..">Parent Directory</a></div>
+
+<table n:render="sequence" n:data="children" border="1">
+  <tr n:pattern="header">
+    <td>Filename</td>
+    <td>Type</td>
+    <td>fileid</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="fileid"/></td>
+  </tr>
+
+  <tr n:pattern="empty"><td>directory is empty!</td></tr>
+
+</table>
+
+<!-- <div n:render="forms"/> -->
+
+<!-- <div class="results" n:render="results"/> -->
+
+  </body>
+</html>
diff --git a/allmydata/web/welcome.xhtml b/allmydata/web/welcome.xhtml
new file mode 100644
index 00000000..8ff2a8ba
--- /dev/null
+++ b/allmydata/web/welcome.xhtml
@@ -0,0 +1,15 @@
+<html xmlns:n="http://nevow.com/ns/nevow/0.1">
+  <head>
+    <title>Welcome To AllMyData (tahoe2)</title>
+    <link href="http://www.allmydata.com/common/css/styles.css"
+          rel="stylesheet" type="text/css"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  </head>
+  <body>
+
+<h1>Welcome To AllMyData!</h1>
+
+<p>To view the global shared filestore, <a href="vdrive">Click Here!</a></p>
+
+  </body>
+</html>
diff --git a/allmydata/webish.py b/allmydata/webish.py
new file mode 100644
index 00000000..499f4051
--- /dev/null
+++ b/allmydata/webish.py
@@ -0,0 +1,145 @@
+
+from twisted.application import service, internet
+from twisted.web import static, resource, server
+from twisted.python import util
+from nevow import inevow, rend, loaders, appserver, tags as T
+from allmydata.util import idlib
+from allmydata.download import IDownloadTarget#, IDownloader
+from zope.interface import implements
+import urllib
+
+def getxmlfile(name):
+    return loaders.xmlfile(util.sibpath(__file__, "web/%s" % name))
+
+class WebishServer(service.MultiService):
+    name = "webish"
+    WEBPORTFILE = "webport"
+
+    def __init__(self, webport):
+        service.MultiService.__init__(self)
+        self.root = root = static.Data("root", "text/plain")
+        w = Welcome()
+        root.putChild("", w)
+        root.putChild("vdrive",
+                      static.Data("sorry, still initializing", "text/plain"))
+        self.site = site = appserver.NevowSite(root)
+        internet.TCPServer(webport, site).setServiceParent(self)
+
+    def set_root_dirnode(self, dirnode):
+        dl = self.parent.getServiceNamed("downloader")
+        self.root.putChild("vdrive", Directory(dirnode, "/", dl))
+        #print "REMEMBERING", self.site, dl, IDownloader
+        #self.site.remember(dl, IDownloader)
+
+
+class Welcome(rend.Page):
+    addSlash = True
+    docFactory = getxmlfile("welcome.xhtml")
+
+class Directory(rend.Page):
+    addSlash = True
+    docFactory = getxmlfile("directory.xhtml")
+
+    def __init__(self, dirnode, dirname, downloader):
+        self._dirnode = dirnode
+        self._dirname = dirname
+        self._downloader = downloader
+
+    def childFactory(self, ctx, name):
+        if name.startswith("freeform"): # ick
+            return None
+        if name == "_download":
+            args = inevow.IRequest(ctx).args
+            filename = args["filename"][0]
+            verifierid = args["verifierid"][0]
+            return Downloader(self._downloader,
+                              self._dirname, filename, idlib.a2b(verifierid))
+        if self._dirname == "/":
+            dirname = "/" + name
+        else:
+            dirname = self._dirname + "/" + name
+        d = self._dirnode.callRemote("get", name)
+        d.addCallback(lambda newnode:
+                      Directory(newnode, dirname, self._downloader))
+        return d
+
+    def render_title(self, ctx, data):
+        return ctx.tag["Directory of '%s':" % self._dirname]
+
+    def render_header(self, ctx, data):
+        return "Directory of '%s':" % self._dirname
+
+    def data_children(self, ctx, data):
+        d = self._dirnode.callRemote("list")
+        return d
+
+    def render_row(self, ctx, data):
+        name, target = data
+        if isinstance(target, str):
+            # file
+            args = {'verifierid': idlib.b2a(target),
+                    'filename': name,
+                    }
+            dlurl = "_download?%s" % urllib.urlencode(args)
+            ctx.fillSlots("filename", T.a(href=dlurl)[name])
+            ctx.fillSlots("type", "FILE")
+            ctx.fillSlots("fileid", idlib.b2a(target))
+        else:
+            # directory
+            ctx.fillSlots("filename", T.a(href=name)[name])
+            ctx.fillSlots("type", "DIR")
+            ctx.fillSlots("fileid", "-")
+        return ctx.tag
+
+class WebDownloadTarget:
+    implements(IDownloadTarget)
+    def __init__(self, req):
+        self._req = req
+    def open(self):
+        pass
+    def write(self, data):
+        self._req.write(data)
+    def close(self):
+        self._req.finish()
+    def fail(self):
+        self._req.finish()
+    def register_canceller(self, cb):
+        pass
+    def finish(self):
+        pass
+
+class TypedFile(static.File):
+    # serve data from a named file, but using a Content-Type derived from a
+    # different filename
+    isLeaf = True
+    def __init__(self, path, requested_filename):
+        static.File.__init__(self, path)
+        gte = static.getTypeAndEncoding
+        self.type, self.encoding = gte(requested_filename,
+                                       self.contentTypes,
+                                       self.contentEncodings,
+                                       self.defaultType)
+
+class Downloader(resource.Resource):
+    def __init__(self, downloader, dirname, name, verifierid):
+        self._downloader = downloader
+        self._dirname = dirname
+        self._name = name
+        self._verifierid = verifierid
+
+    def render(self, ctx):
+        req = inevow.IRequest(ctx)
+        gte = static.getTypeAndEncoding
+        type, encoding = gte(self._name,
+                             static.File.contentTypes,
+                             static.File.contentEncodings,
+                             defaultType="text/plain")
+        req.setHeader("content-type", type)
+        if encoding:
+            req.setHeader('content-encoding', encoding)
+
+        t = WebDownloadTarget(req)
+        #dl = IDownloader(ctx)
+        dl = self._downloader
+        dl.download(self._verifierid, t)
+        return server.NOT_DONE_YET
diff --git a/roadmap.txt b/roadmap.txt
index 14ee9d64..0f595eaf 100644
--- a/roadmap.txt
+++ b/roadmap.txt
@@ -28,6 +28,9 @@ storage: RobK
      leases never expire
  v2: leases expire, delete expired data on demand, multiple owners per share
 
+UI:
+ webish? webfront? PB + CLI tool? FUSE?
+ v1:
 
 back pocket ideas:
  when nodes are unable to reach storage servers, make a note of it, inform