mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-02 19:26:44 +00:00
add a (read-only) web frontend. Put a 'webport' file in your base directory to activate it.
This commit is contained in:
parent
faf0b13e3c
commit
a3700cc582
@ -14,6 +14,7 @@ from allmydata.storageserver import StorageServer
|
|||||||
from allmydata.upload import Uploader
|
from allmydata.upload import Uploader
|
||||||
from allmydata.download import Downloader
|
from allmydata.download import Downloader
|
||||||
from allmydata.vdrive import VDrive
|
from allmydata.vdrive import VDrive
|
||||||
|
from allmydata.webish import WebishServer
|
||||||
|
|
||||||
class Client(node.Node, Referenceable):
|
class Client(node.Node, Referenceable):
|
||||||
implements(RIClient)
|
implements(RIClient)
|
||||||
@ -21,6 +22,7 @@ class Client(node.Node, Referenceable):
|
|||||||
PORTNUMFILE = "client.port"
|
PORTNUMFILE = "client.port"
|
||||||
STOREDIR = 'storage'
|
STOREDIR = 'storage'
|
||||||
NODETYPE = "client"
|
NODETYPE = "client"
|
||||||
|
WEBPORTFILE = "webport"
|
||||||
|
|
||||||
def __init__(self, basedir="."):
|
def __init__(self, basedir="."):
|
||||||
node.Node.__init__(self, basedir)
|
node.Node.__init__(self, basedir)
|
||||||
@ -31,6 +33,12 @@ class Client(node.Node, Referenceable):
|
|||||||
self.add_service(Uploader())
|
self.add_service(Uploader())
|
||||||
self.add_service(Downloader())
|
self.add_service(Downloader())
|
||||||
self.add_service(VDrive())
|
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_pburl = None
|
||||||
self.queen_connector = None
|
self.queen_connector = None
|
||||||
|
|
||||||
@ -73,6 +81,8 @@ class Client(node.Node, Referenceable):
|
|||||||
|
|
||||||
def _got_vdrive_root(self, root):
|
def _got_vdrive_root(self, root):
|
||||||
self.getServiceNamed("vdrive").set_root(root)
|
self.getServiceNamed("vdrive").set_root(root)
|
||||||
|
if "webish" in self.namedServices:
|
||||||
|
self.getServiceNamed("webish").set_root_dirnode(root)
|
||||||
|
|
||||||
def _lost_queen(self):
|
def _lost_queen(self):
|
||||||
self.log("lost connection to queen")
|
self.log("lost connection to queen")
|
||||||
|
@ -195,10 +195,14 @@ class FileHandle:
|
|||||||
def finish(self):
|
def finish(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class IDownloader(Interface):
|
||||||
|
def download(verifierid, target):
|
||||||
|
pass
|
||||||
|
|
||||||
class Downloader(service.MultiService):
|
class Downloader(service.MultiService):
|
||||||
"""I am a service that allows file downloading.
|
"""I am a service that allows file downloading.
|
||||||
"""
|
"""
|
||||||
|
implements(IDownloader)
|
||||||
name = "downloader"
|
name = "downloader"
|
||||||
|
|
||||||
def download(self, verifierid, t):
|
def download(self, verifierid, t):
|
||||||
|
36
allmydata/web/directory.xhtml
Normal file
36
allmydata/web/directory.xhtml
Normal file
@ -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>
|
15
allmydata/web/welcome.xhtml
Normal file
15
allmydata/web/welcome.xhtml
Normal file
@ -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>
|
145
allmydata/webish.py
Normal file
145
allmydata/webish.py
Normal file
@ -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
|
@ -28,6 +28,9 @@ storage: RobK
|
|||||||
leases never expire
|
leases never expire
|
||||||
v2: leases expire, delete expired data on demand, multiple owners per share
|
v2: leases expire, delete expired data on demand, multiple owners per share
|
||||||
|
|
||||||
|
UI:
|
||||||
|
webish? webfront? PB + CLI tool? FUSE?
|
||||||
|
v1:
|
||||||
|
|
||||||
back pocket ideas:
|
back pocket ideas:
|
||||||
when nodes are unable to reach storage servers, make a note of it, inform
|
when nodes are unable to reach storage servers, make a note of it, inform
|
||||||
|
Loading…
Reference in New Issue
Block a user