2006-12-04 11:06:09 +00:00
|
|
|
|
2007-08-12 17:29:38 +00:00
|
|
|
from base64 import b32decode, b32encode
|
2007-07-07 07:16:36 +00:00
|
|
|
import os.path
|
2006-12-07 19:47:40 +00:00
|
|
|
from twisted.application import service, strports
|
2007-07-03 20:47:37 +00:00
|
|
|
from twisted.web import static, resource, server, html, http
|
2006-12-04 12:15:36 +00:00
|
|
|
from twisted.python import util, log
|
2007-07-07 02:43:55 +00:00
|
|
|
from twisted.internet import defer
|
2006-12-04 12:15:36 +00:00
|
|
|
from nevow import inevow, rend, loaders, appserver, url, tags as T
|
2007-06-15 08:32:20 +00:00
|
|
|
from nevow.static import File as nevow_File # TODO: merge with static.File?
|
2007-07-07 07:16:36 +00:00
|
|
|
from allmydata.util import idlib, fileutil
|
2007-07-10 22:49:32 +00:00
|
|
|
import simplejson
|
2007-06-25 20:23:51 +00:00
|
|
|
from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
|
2007-07-21 22:40:36 +00:00
|
|
|
from allmydata import upload, download
|
2006-12-05 02:54:35 +00:00
|
|
|
from zope.interface import implements, Interface
|
2006-12-04 11:06:09 +00:00
|
|
|
import urllib
|
2007-07-08 05:06:22 +00:00
|
|
|
from formless import webform
|
2006-12-04 11:06:09 +00:00
|
|
|
|
|
|
|
def getxmlfile(name):
|
|
|
|
return loaders.xmlfile(util.sibpath(__file__, "web/%s" % name))
|
|
|
|
|
2006-12-05 02:54:35 +00:00
|
|
|
class IClient(Interface):
|
|
|
|
pass
|
2007-08-11 01:21:22 +00:00
|
|
|
class ILocalAccess(Interface):
|
|
|
|
def allow_local_access():
|
|
|
|
pass
|
2006-12-05 02:54:35 +00:00
|
|
|
|
2007-08-11 00:25:33 +00:00
|
|
|
|
|
|
|
# we must override twisted.web.http.Request.requestReceived with a version
|
|
|
|
# that doesn't use cgi.parse_multipart() . Since we actually use Nevow, we
|
|
|
|
# override the nevow-specific subclass, nevow.appserver.NevowRequest . This
|
|
|
|
# is an exact copy of twisted.web.http.Request (from SVN HEAD on 10-Aug-2007)
|
|
|
|
# that modifies the way form arguments are parsed. Note that this sort of
|
|
|
|
# surgery may induce a dependency upon a particular version of twisted.web
|
|
|
|
|
|
|
|
parse_qs = http.parse_qs
|
|
|
|
class MyRequest(appserver.NevowRequest):
|
|
|
|
def requestReceived(self, command, path, version):
|
|
|
|
"""Called by channel when all data has been received.
|
|
|
|
|
|
|
|
This method is not intended for users.
|
|
|
|
"""
|
|
|
|
self.content.seek(0,0)
|
|
|
|
self.args = {}
|
|
|
|
self.stack = []
|
|
|
|
|
|
|
|
self.method, self.uri = command, path
|
|
|
|
self.clientproto = version
|
|
|
|
x = self.uri.split('?', 1)
|
|
|
|
|
|
|
|
if len(x) == 1:
|
|
|
|
self.path = self.uri
|
|
|
|
else:
|
|
|
|
self.path, argstring = x
|
|
|
|
self.args = parse_qs(argstring, 1)
|
|
|
|
|
|
|
|
# cache the client and server information, we'll need this later to be
|
|
|
|
# serialized and sent with the request so CGIs will work remotely
|
|
|
|
self.client = self.channel.transport.getPeer()
|
|
|
|
self.host = self.channel.transport.getHost()
|
|
|
|
|
|
|
|
# Argument processing.
|
|
|
|
|
|
|
|
## The original twisted.web.http.Request.requestReceived code parsed the
|
|
|
|
## content and added the form fields it found there to self.args . It
|
|
|
|
## did this with cgi.parse_multipart, which holds the arguments in RAM
|
|
|
|
## and is thus unsuitable for large file uploads. The Nevow subclass
|
|
|
|
## (nevow.appserver.NevowRequest) uses cgi.FieldStorage instead (putting
|
|
|
|
## the results in self.fields), which is much more memory-efficient.
|
|
|
|
## Since we know we're using Nevow, we can anticipate these arguments
|
|
|
|
## appearing in self.fields instead of self.args, and thus skip the
|
|
|
|
## parse-content-into-self.args step.
|
|
|
|
|
|
|
|
## args = self.args
|
|
|
|
## ctype = self.getHeader('content-type')
|
|
|
|
## if self.method == "POST" and ctype:
|
|
|
|
## mfd = 'multipart/form-data'
|
|
|
|
## key, pdict = cgi.parse_header(ctype)
|
|
|
|
## if key == 'application/x-www-form-urlencoded':
|
|
|
|
## args.update(parse_qs(self.content.read(), 1))
|
|
|
|
## elif key == mfd:
|
|
|
|
## try:
|
|
|
|
## args.update(cgi.parse_multipart(self.content, pdict))
|
|
|
|
## except KeyError, e:
|
|
|
|
## if e.args[0] == 'content-disposition':
|
|
|
|
## # Parse_multipart can't cope with missing
|
|
|
|
## # content-dispostion headers in multipart/form-data
|
|
|
|
## # parts, so we catch the exception and tell the client
|
|
|
|
## # it was a bad request.
|
|
|
|
## self.channel.transport.write(
|
|
|
|
## "HTTP/1.1 400 Bad Request\r\n\r\n")
|
|
|
|
## self.channel.transport.loseConnection()
|
|
|
|
## return
|
|
|
|
## raise
|
|
|
|
|
|
|
|
self.process()
|
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
class Directory(rend.Page):
|
|
|
|
addSlash = True
|
|
|
|
docFactory = getxmlfile("directory.xhtml")
|
|
|
|
|
2007-07-08 04:31:02 +00:00
|
|
|
def __init__(self, rootname, dirnode, dirpath):
|
|
|
|
self._rootname = rootname
|
2006-12-04 11:06:09 +00:00
|
|
|
self._dirnode = dirnode
|
2007-07-07 18:15:31 +00:00
|
|
|
self._dirpath = dirpath
|
2006-12-05 02:54:35 +00:00
|
|
|
|
2007-07-07 18:15:31 +00:00
|
|
|
def dirpath_as_string(self):
|
|
|
|
return "/" + "/".join(self._dirpath)
|
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
def render_title(self, ctx, data):
|
2007-07-07 18:15:31 +00:00
|
|
|
return ctx.tag["Directory '%s':" % self.dirpath_as_string()]
|
2006-12-04 11:06:09 +00:00
|
|
|
|
|
|
|
def render_header(self, ctx, data):
|
2007-07-08 04:31:02 +00:00
|
|
|
parent_directories = ("<%s>" % self._rootname,) + self._dirpath
|
2007-06-29 18:17:18 +00:00
|
|
|
num_dirs = len(parent_directories)
|
|
|
|
|
|
|
|
header = ["Directory '"]
|
|
|
|
for i,d in enumerate(parent_directories):
|
2007-07-07 18:15:31 +00:00
|
|
|
upness = num_dirs - i - 1
|
|
|
|
if upness:
|
|
|
|
link = "/".join( ("..",) * upness )
|
2007-06-29 18:17:18 +00:00
|
|
|
else:
|
2007-07-07 18:15:31 +00:00
|
|
|
link = "."
|
|
|
|
header.append(T.a(href=link)[d])
|
|
|
|
if upness != 0:
|
|
|
|
header.append("/")
|
2007-06-29 18:17:18 +00:00
|
|
|
header.append("'")
|
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
if not self._dirnode.is_mutable():
|
2007-06-29 18:17:18 +00:00
|
|
|
header.append(" (readonly)")
|
|
|
|
header.append(":")
|
|
|
|
return ctx.tag[header]
|
2006-12-04 11:06:09 +00:00
|
|
|
|
2007-07-08 04:30:04 +00:00
|
|
|
def render_welcome(self, ctx, data):
|
|
|
|
depth = len(self._dirpath) + 2
|
|
|
|
link = "/".join([".."] * depth)
|
|
|
|
return T.div[T.a(href=link)["Return to Welcome page"]]
|
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
def data_children(self, ctx, data):
|
2007-06-15 07:37:32 +00:00
|
|
|
d = self._dirnode.list()
|
2007-06-25 20:23:51 +00:00
|
|
|
d.addCallback(lambda dict: sorted(dict.items()))
|
2006-12-04 11:06:09 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
def render_row(self, ctx, data):
|
|
|
|
name, target = data
|
2007-06-25 20:23:51 +00:00
|
|
|
|
|
|
|
if self._dirnode.is_mutable():
|
|
|
|
# this creates a button which will cause our child__delete method
|
|
|
|
# to be invoked, which deletes the file and then redirects the
|
|
|
|
# browser back to this directory
|
2007-07-12 23:46:54 +00:00
|
|
|
delete = T.form(action=url.here, method="post")[
|
|
|
|
T.input(type='hidden', name='t', value='delete'),
|
|
|
|
T.input(type='hidden', name='name', value=name),
|
|
|
|
T.input(type='hidden', name='when_done', value=url.here),
|
2007-06-25 20:23:51 +00:00
|
|
|
T.input(type='submit', value='del', name="del"),
|
|
|
|
]
|
2007-07-12 23:53:54 +00:00
|
|
|
|
|
|
|
rename = T.form(action=url.here, method="get")[
|
|
|
|
T.input(type='hidden', name='t', value='rename-form'),
|
|
|
|
T.input(type='hidden', name='name', value=name),
|
|
|
|
T.input(type='hidden', name='when_done', value=url.here),
|
|
|
|
T.input(type='submit', value='rename', name="rename"),
|
|
|
|
]
|
2007-06-25 20:23:51 +00:00
|
|
|
else:
|
|
|
|
delete = "-"
|
2007-07-12 23:53:54 +00:00
|
|
|
rename = "-"
|
2007-06-25 20:23:51 +00:00
|
|
|
ctx.fillSlots("delete", delete)
|
2007-07-12 23:53:54 +00:00
|
|
|
ctx.fillSlots("rename", rename)
|
2007-06-25 20:23:51 +00:00
|
|
|
|
2007-07-11 22:26:47 +00:00
|
|
|
# build the base of the uri_link link url
|
2007-07-08 05:06:52 +00:00
|
|
|
uri_link = urllib.quote(target.get_uri().replace("/", "!"))
|
2007-07-07 18:31:07 +00:00
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
if IFileNode.providedBy(target):
|
2006-12-04 11:06:09 +00:00
|
|
|
# file
|
2006-12-07 21:48:37 +00:00
|
|
|
dlurl = urllib.quote(name)
|
|
|
|
ctx.fillSlots("filename",
|
|
|
|
T.a(href=dlurl)[html.escape(name)])
|
2006-12-04 11:06:09 +00:00
|
|
|
ctx.fillSlots("type", "FILE")
|
2007-07-07 18:31:07 +00:00
|
|
|
|
2007-07-21 22:40:36 +00:00
|
|
|
ctx.fillSlots("size", target.get_size())
|
2007-05-04 20:07:32 +00:00
|
|
|
|
2007-07-14 06:45:35 +00:00
|
|
|
text_plain_link = "/uri/%s?filename=foo.txt" % uri_link
|
|
|
|
text_plain_tag = T.a(href=text_plain_link)["text/plain"]
|
|
|
|
|
2007-07-11 22:26:47 +00:00
|
|
|
# if we're a file, add the filename to the uri_link url
|
|
|
|
uri_link += '?%s' % (urllib.urlencode({'filename': name}),)
|
|
|
|
|
2007-06-25 20:23:51 +00:00
|
|
|
elif IDirectoryNode.providedBy(target):
|
2006-12-04 11:06:09 +00:00
|
|
|
# directory
|
2006-12-07 21:48:37 +00:00
|
|
|
subdir_url = urllib.quote(name)
|
|
|
|
ctx.fillSlots("filename",
|
|
|
|
T.a(href=subdir_url)[html.escape(name)])
|
2007-06-26 19:37:00 +00:00
|
|
|
if target.is_mutable():
|
|
|
|
dirtype = "DIR"
|
|
|
|
else:
|
|
|
|
dirtype = "DIR-RO"
|
|
|
|
ctx.fillSlots("type", dirtype)
|
2007-05-04 20:07:32 +00:00
|
|
|
ctx.fillSlots("size", "-")
|
2007-07-14 06:45:35 +00:00
|
|
|
text_plain_tag = None
|
2007-06-15 07:37:32 +00:00
|
|
|
else:
|
|
|
|
raise RuntimeError("unknown thing %s" % (target,))
|
2007-07-11 22:26:47 +00:00
|
|
|
|
|
|
|
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"], ", ",
|
|
|
|
T.a(href="/uri/%s" % uri_link)["URI-link"],
|
|
|
|
]
|
2007-07-14 06:45:35 +00:00
|
|
|
if text_plain_tag:
|
|
|
|
childdata.extend([", ", text_plain_tag])
|
|
|
|
|
2007-07-11 22:26:47 +00:00
|
|
|
ctx.fillSlots("data", childdata)
|
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
return ctx.tag
|
|
|
|
|
2006-12-04 12:15:36 +00:00
|
|
|
def render_forms(self, ctx, data):
|
2007-07-08 03:35:47 +00:00
|
|
|
if not self._dirnode.is_mutable():
|
|
|
|
return T.div["No upload forms: directory is immutable"]
|
|
|
|
mkdir = T.form(action=".", method="post",
|
|
|
|
enctype="multipart/form-data")[
|
|
|
|
T.fieldset[
|
|
|
|
T.input(type="hidden", name="t", value="mkdir"),
|
2007-07-08 04:17:48 +00:00
|
|
|
T.input(type="hidden", name="when_done", value=url.here),
|
2007-07-08 03:35:47 +00:00
|
|
|
T.legend(class_="freeform-form-label")["Create a new directory"],
|
|
|
|
"New directory name: ",
|
|
|
|
T.input(type="text", name="name"), " ",
|
|
|
|
T.input(type="submit", value="Create"),
|
|
|
|
]]
|
|
|
|
upload = T.form(action=".", method="post",
|
|
|
|
enctype="multipart/form-data")[
|
|
|
|
T.fieldset[
|
|
|
|
T.input(type="hidden", name="t", value="upload"),
|
2007-07-08 04:17:48 +00:00
|
|
|
T.input(type="hidden", name="when_done", value=url.here),
|
2007-07-08 03:35:47 +00:00
|
|
|
T.legend(class_="freeform-form-label")["Upload a file to this directory"],
|
|
|
|
"Choose a file to upload: ",
|
|
|
|
T.input(type="file", name="file", class_="freeform-input-file"),
|
|
|
|
" ",
|
|
|
|
T.input(type="submit", value="Upload"),
|
|
|
|
]]
|
|
|
|
mount = T.form(action=".", method="post",
|
|
|
|
enctype="multipart/form-data")[
|
|
|
|
T.fieldset[
|
|
|
|
T.input(type="hidden", name="t", value="uri"),
|
2007-07-08 04:17:48 +00:00
|
|
|
T.input(type="hidden", name="when_done", value=url.here),
|
2007-07-08 03:35:47 +00:00
|
|
|
T.legend(class_="freeform-form-label")["Attach a file or directory"
|
|
|
|
" (by URI) to this"
|
2007-07-10 17:33:19 +00:00
|
|
|
" directory"],
|
2007-07-08 03:35:47 +00:00
|
|
|
"New child name: ",
|
|
|
|
T.input(type="text", name="name"), " ",
|
|
|
|
"URI of new child: ",
|
|
|
|
T.input(type="text", name="uri"), " ",
|
|
|
|
T.input(type="submit", value="Attach"),
|
|
|
|
]]
|
|
|
|
return [T.div(class_="freeform-form")[mkdir],
|
|
|
|
T.div(class_="freeform-form")[upload],
|
|
|
|
T.div(class_="freeform-form")[mount],
|
|
|
|
]
|
2006-12-04 12:15:36 +00:00
|
|
|
|
2007-05-16 15:40:19 +00:00
|
|
|
def render_results(self, ctx, data):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
if "results" in req.args:
|
|
|
|
return req.args["results"]
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
class WebDownloadTarget:
|
|
|
|
implements(IDownloadTarget)
|
2007-07-03 20:47:37 +00:00
|
|
|
def __init__(self, req, content_type, content_encoding):
|
2006-12-04 11:06:09 +00:00
|
|
|
self._req = req
|
2007-07-03 20:47:37 +00:00
|
|
|
self._content_type = content_type
|
|
|
|
self._content_encoding = content_encoding
|
|
|
|
self._opened = False
|
|
|
|
|
2007-07-03 22:09:00 +00:00
|
|
|
def open(self, size):
|
2007-07-03 20:47:37 +00:00
|
|
|
self._opened = True
|
|
|
|
self._req.setHeader("content-type", self._content_type)
|
|
|
|
if self._content_encoding:
|
2007-07-03 22:09:00 +00:00
|
|
|
self._req.setHeader("content-encoding", self._content_encoding)
|
|
|
|
self._req.setHeader("content-length", str(size))
|
2007-07-03 20:47:37 +00:00
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
def write(self, data):
|
|
|
|
self._req.write(data)
|
|
|
|
def close(self):
|
|
|
|
self._req.finish()
|
2007-07-03 20:47:37 +00:00
|
|
|
|
2007-07-03 20:18:14 +00:00
|
|
|
def fail(self, why):
|
2007-07-03 20:47:37 +00:00
|
|
|
if self._opened:
|
|
|
|
# The content-type is already set, and the response code
|
|
|
|
# has already been sent, so we can't provide a clean error
|
|
|
|
# indication. We can emit text (which a browser might interpret
|
|
|
|
# as something else), and if we sent a Size header, they might
|
|
|
|
# notice that we've truncated the data. Keep the error message
|
|
|
|
# small to improve the chances of having our error response be
|
|
|
|
# shorter than the intended results.
|
|
|
|
#
|
|
|
|
# We don't have a lot of options, unfortunately.
|
|
|
|
self._req.write("problem during download\n")
|
|
|
|
else:
|
|
|
|
# We haven't written anything yet, so we can provide a sensible
|
|
|
|
# error message.
|
|
|
|
msg = str(why.type)
|
|
|
|
msg.replace("\n", "|")
|
2007-07-17 19:16:45 +00:00
|
|
|
self._req.setResponseCode(http.GONE, msg)
|
2007-07-03 20:47:37 +00:00
|
|
|
self._req.setHeader("content-type", "text/plain")
|
|
|
|
# TODO: HTML-formatted exception?
|
|
|
|
self._req.write(str(why))
|
2006-12-04 11:06:09 +00:00
|
|
|
self._req.finish()
|
2007-07-03 20:47:37 +00:00
|
|
|
|
2006-12-04 11:06:09 +00:00
|
|
|
def register_canceller(self, cb):
|
|
|
|
pass
|
|
|
|
def finish(self):
|
|
|
|
pass
|
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
class FileDownloader(resource.Resource):
|
2007-07-08 03:06:58 +00:00
|
|
|
def __init__(self, filenode, name):
|
2007-06-25 20:23:51 +00:00
|
|
|
IFileNode(filenode)
|
2007-06-15 07:37:32 +00:00
|
|
|
self._filenode = filenode
|
2007-07-08 03:06:58 +00:00
|
|
|
self._name = name
|
2006-12-04 11:06:09 +00:00
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
def render(self, req):
|
2006-12-04 11:06:09 +00:00
|
|
|
gte = static.getTypeAndEncoding
|
|
|
|
type, encoding = gte(self._name,
|
|
|
|
static.File.contentTypes,
|
|
|
|
static.File.contentEncodings,
|
|
|
|
defaultType="text/plain")
|
|
|
|
|
2007-07-03 20:47:37 +00:00
|
|
|
d = self._filenode.download(WebDownloadTarget(req, type, encoding))
|
2007-07-03 20:18:14 +00:00
|
|
|
# exceptions during download are handled by the WebDownloadTarget
|
|
|
|
d.addErrback(lambda why: None)
|
2006-12-04 11:06:09 +00:00
|
|
|
return server.NOT_DONE_YET
|
2006-12-04 12:15:36 +00:00
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
class BlockingFileError(Exception):
|
|
|
|
"""We cannot auto-create a parent directory, because there is a file in
|
|
|
|
the way"""
|
|
|
|
|
|
|
|
LOCALHOST = "127.0.0.1"
|
|
|
|
|
|
|
|
class NeedLocalhostError:
|
|
|
|
implements(inevow.IResource)
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
req.setResponseCode(http.FORBIDDEN)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "localfile= or localdir= requires a local connection"
|
|
|
|
|
2007-07-07 07:16:36 +00:00
|
|
|
class NeedAbsolutePathError:
|
|
|
|
implements(inevow.IResource)
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
req.setResponseCode(http.FORBIDDEN)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "localfile= or localdir= requires an absolute path"
|
|
|
|
|
2007-08-11 01:21:22 +00:00
|
|
|
class LocalAccessDisabledError:
|
|
|
|
implements(inevow.IResource)
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
req.setResponseCode(http.FORBIDDEN)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "local file access is disabled"
|
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
class LocalFileDownloader(resource.Resource):
|
|
|
|
def __init__(self, filenode, local_filename):
|
|
|
|
self._local_filename = local_filename
|
|
|
|
IFileNode(filenode)
|
|
|
|
self._filenode = filenode
|
|
|
|
|
|
|
|
def render(self, req):
|
|
|
|
target = download.FileName(self._local_filename)
|
|
|
|
d = self._filenode.download(target)
|
|
|
|
def _done(res):
|
|
|
|
req.write(self._filenode.get_uri())
|
|
|
|
req.finish()
|
|
|
|
d.addCallback(_done)
|
|
|
|
return server.NOT_DONE_YET
|
|
|
|
|
2007-07-08 07:17:11 +00:00
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
class FileJSONMetadata(rend.Page):
|
|
|
|
def __init__(self, filenode):
|
|
|
|
self._filenode = filenode
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
2007-07-07 18:15:31 +00:00
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return self.renderNode(self._filenode)
|
|
|
|
|
|
|
|
def renderNode(self, filenode):
|
|
|
|
file_uri = filenode.get_uri()
|
2007-07-08 07:17:11 +00:00
|
|
|
data = ("filenode",
|
|
|
|
{'mutable': False,
|
|
|
|
'uri': file_uri,
|
2007-07-21 22:40:36 +00:00
|
|
|
'size': filenode.get_size(),
|
2007-07-08 07:17:11 +00:00
|
|
|
})
|
2007-07-10 22:49:32 +00:00
|
|
|
return simplejson.dumps(data, indent=1)
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
class FileURI(FileJSONMetadata):
|
2007-07-07 18:15:31 +00:00
|
|
|
def renderNode(self, filenode):
|
|
|
|
file_uri = filenode.get_uri()
|
2007-07-07 02:43:55 +00:00
|
|
|
return file_uri
|
|
|
|
|
2007-07-07 07:16:36 +00:00
|
|
|
class DirnodeWalkerMixin:
|
|
|
|
"""Visit all nodes underneath (and including) the rootnode, one at a
|
|
|
|
time. For each one, call the visitor. The visitor will see the
|
|
|
|
IDirectoryNode before it sees any of the IFileNodes inside. If the
|
|
|
|
visitor returns a Deferred, I do not call the visitor again until it has
|
|
|
|
fired.
|
|
|
|
"""
|
|
|
|
|
2007-07-13 06:28:09 +00:00
|
|
|
## def _walk_if_we_could_use_generators(self, rootnode, rootpath=()):
|
|
|
|
## # this is what we'd be doing if we didn't have the Deferreds and
|
|
|
|
## # thus could use generators
|
|
|
|
## yield rootpath, rootnode
|
|
|
|
## for childname, childnode in rootnode.list().items():
|
|
|
|
## childpath = rootpath + (childname,)
|
|
|
|
## if IFileNode.providedBy(childnode):
|
|
|
|
## yield childpath, childnode
|
|
|
|
## elif IDirectoryNode.providedBy(childnode):
|
|
|
|
## for res in self._walk_if_we_could_use_generators(childnode,
|
|
|
|
## childpath):
|
|
|
|
## yield res
|
2007-07-07 07:16:36 +00:00
|
|
|
|
|
|
|
def walk(self, rootnode, visitor, rootpath=()):
|
|
|
|
d = rootnode.list()
|
|
|
|
def _listed(listing):
|
|
|
|
return listing.items()
|
|
|
|
d.addCallback(_listed)
|
|
|
|
d.addCallback(self._handle_items, visitor, rootpath)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _handle_items(self, items, visitor, rootpath):
|
|
|
|
if not items:
|
|
|
|
return
|
|
|
|
childname, childnode = items[0]
|
|
|
|
childpath = rootpath + (childname,)
|
|
|
|
d = defer.maybeDeferred(visitor, childpath, childnode)
|
|
|
|
if IDirectoryNode.providedBy(childnode):
|
|
|
|
d.addCallback(lambda res: self.walk(childnode, visitor, childpath))
|
|
|
|
d.addCallback(lambda res:
|
|
|
|
self._handle_items(items[1:], visitor, rootpath))
|
|
|
|
return d
|
|
|
|
|
|
|
|
class LocalDirectoryDownloader(resource.Resource, DirnodeWalkerMixin):
|
|
|
|
def __init__(self, dirnode, localdir):
|
2007-07-07 02:43:55 +00:00
|
|
|
self._dirnode = dirnode
|
2007-07-07 07:16:36 +00:00
|
|
|
self._localdir = localdir
|
2007-07-07 02:43:55 +00:00
|
|
|
|
2007-07-07 07:16:36 +00:00
|
|
|
def _handle(self, path, node):
|
|
|
|
localfile = os.path.join(self._localdir, os.sep.join(path))
|
|
|
|
if IDirectoryNode.providedBy(node):
|
|
|
|
fileutil.make_dirs(localfile)
|
|
|
|
elif IFileNode.providedBy(node):
|
|
|
|
target = download.FileName(localfile)
|
|
|
|
return node.download(target)
|
|
|
|
|
|
|
|
def render(self, req):
|
|
|
|
d = self.walk(self._dirnode, self._handle)
|
|
|
|
def _done(res):
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "operation complete"
|
|
|
|
d.addCallback(_done)
|
|
|
|
return d
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
class DirectoryJSONMetadata(rend.Page):
|
|
|
|
def __init__(self, dirnode):
|
|
|
|
self._dirnode = dirnode
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
2007-07-07 18:15:31 +00:00
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return self.renderNode(self._dirnode)
|
|
|
|
|
|
|
|
def renderNode(self, node):
|
|
|
|
d = node.list()
|
2007-07-08 07:17:11 +00:00
|
|
|
def _got(children):
|
|
|
|
kids = {}
|
2007-07-07 02:43:55 +00:00
|
|
|
for name, childnode in children.iteritems():
|
2007-07-08 07:17:11 +00:00
|
|
|
if IFileNode.providedBy(childnode):
|
|
|
|
kiduri = childnode.get_uri()
|
|
|
|
kiddata = ("filenode",
|
|
|
|
{'mutable': False,
|
|
|
|
'uri': kiduri,
|
2007-07-21 22:40:36 +00:00
|
|
|
'size': childnode.get_size(),
|
2007-07-08 07:17:11 +00:00
|
|
|
})
|
|
|
|
else:
|
|
|
|
assert IDirectoryNode.providedBy(childnode)
|
|
|
|
kiduri = childnode.get_uri()
|
|
|
|
kiddata = ("dirnode",
|
|
|
|
{'mutable': childnode.is_mutable(),
|
|
|
|
'uri': kiduri,
|
|
|
|
})
|
|
|
|
kids[name] = kiddata
|
|
|
|
contents = { 'children': kids,
|
|
|
|
'mutable': node.is_mutable(),
|
|
|
|
'uri': node.get_uri(),
|
|
|
|
}
|
|
|
|
data = ("dirnode", contents)
|
2007-07-10 22:49:32 +00:00
|
|
|
return simplejson.dumps(data, indent=1)
|
2007-07-08 07:17:11 +00:00
|
|
|
d.addCallback(_got)
|
2007-07-07 02:43:55 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
class DirectoryURI(DirectoryJSONMetadata):
|
2007-07-07 18:15:31 +00:00
|
|
|
def renderNode(self, node):
|
|
|
|
return node.get_uri()
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
class DirectoryReadonlyURI(DirectoryJSONMetadata):
|
2007-07-07 18:15:31 +00:00
|
|
|
def renderNode(self, node):
|
|
|
|
return node.get_immutable_uri()
|
2007-07-07 02:43:55 +00:00
|
|
|
|
2007-07-12 23:53:54 +00:00
|
|
|
class RenameForm(rend.Page):
|
|
|
|
addSlash = True
|
|
|
|
docFactory = getxmlfile("rename-form.xhtml")
|
|
|
|
|
|
|
|
def __init__(self, rootname, dirnode, dirpath):
|
|
|
|
self._rootname = rootname
|
|
|
|
self._dirnode = dirnode
|
|
|
|
self._dirpath = dirpath
|
|
|
|
|
|
|
|
def dirpath_as_string(self):
|
|
|
|
return "/" + "/".join(self._dirpath)
|
|
|
|
|
|
|
|
def render_title(self, ctx, data):
|
|
|
|
return ctx.tag["Directory '%s':" % self.dirpath_as_string()]
|
|
|
|
|
|
|
|
def render_header(self, ctx, data):
|
|
|
|
parent_directories = ("<%s>" % self._rootname,) + self._dirpath
|
|
|
|
num_dirs = len(parent_directories)
|
|
|
|
|
|
|
|
header = [ "Rename in directory '",
|
|
|
|
"<%s>/" % self._rootname,
|
|
|
|
"/".join(self._dirpath),
|
|
|
|
"':", ]
|
|
|
|
|
|
|
|
if not self._dirnode.is_mutable():
|
|
|
|
header.append(" (readonly)")
|
|
|
|
return ctx.tag[header]
|
|
|
|
|
|
|
|
def render_when_done(self, ctx, data):
|
|
|
|
return T.input(type="hidden", name="when_done", value=url.here)
|
|
|
|
|
|
|
|
def render_get_name(self, ctx, data):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
if 'name' in req.args:
|
|
|
|
name = req.args['name'][0]
|
|
|
|
else:
|
|
|
|
name = ''
|
|
|
|
ctx.tag.attributes['value'] = name
|
|
|
|
return ctx.tag
|
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
class POSTHandler(rend.Page):
|
|
|
|
def __init__(self, node):
|
|
|
|
self._node = node
|
2007-07-08 03:11:30 +00:00
|
|
|
|
2007-07-08 03:06:58 +00:00
|
|
|
def renderHTTP(self, ctx):
|
|
|
|
req = inevow.IRequest(ctx)
|
2007-07-08 03:46:30 +00:00
|
|
|
|
|
|
|
if "t" in req.args:
|
|
|
|
t = req.args["t"][0]
|
|
|
|
else:
|
|
|
|
t = req.fields["t"].value
|
|
|
|
|
|
|
|
name = None
|
|
|
|
if "name" in req.args:
|
|
|
|
name = req.args["name"][0]
|
2007-08-11 00:25:28 +00:00
|
|
|
elif "name" in req.fields:
|
2007-07-08 03:06:58 +00:00
|
|
|
name = req.fields["name"].value
|
2007-07-16 19:00:54 +00:00
|
|
|
if name and "/" in name:
|
2007-07-16 18:53:12 +00:00
|
|
|
req.setResponseCode(http.BAD_REQUEST)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "name= may not contain a slash"
|
2007-07-08 03:46:30 +00:00
|
|
|
|
2007-07-08 04:17:48 +00:00
|
|
|
when_done = None
|
|
|
|
if "when_done" in req.args:
|
|
|
|
when_done = req.args["when_done"][0]
|
|
|
|
|
2007-07-08 03:46:30 +00:00
|
|
|
if t == "mkdir":
|
|
|
|
if not name:
|
|
|
|
raise RuntimeError("mkdir requires a name")
|
2007-07-08 03:06:58 +00:00
|
|
|
d = self._node.create_empty_directory(name)
|
|
|
|
def _done(res):
|
|
|
|
return "directory created"
|
|
|
|
d.addCallback(_done)
|
|
|
|
elif t == "uri":
|
2007-07-08 03:46:30 +00:00
|
|
|
if not name:
|
|
|
|
raise RuntimeError("set-uri requires a name")
|
|
|
|
if "uri" in req.args:
|
2007-07-08 04:17:48 +00:00
|
|
|
newuri = req.args["uri"][0]
|
2007-07-08 03:46:30 +00:00
|
|
|
else:
|
2007-07-08 04:17:48 +00:00
|
|
|
newuri = req.fields["uri"].value
|
|
|
|
d = self._node.set_uri(name, newuri)
|
2007-07-08 03:06:58 +00:00
|
|
|
def _done(res):
|
2007-07-08 04:17:48 +00:00
|
|
|
return newuri
|
2007-07-08 03:06:58 +00:00
|
|
|
d.addCallback(_done)
|
|
|
|
elif t == "delete":
|
2007-07-08 03:46:30 +00:00
|
|
|
if not name:
|
|
|
|
raise RuntimeError("delete requires a name")
|
2007-07-08 03:06:58 +00:00
|
|
|
d = self._node.delete(name)
|
|
|
|
def _done(res):
|
|
|
|
return "thing deleted"
|
|
|
|
d.addCallback(_done)
|
2007-07-12 23:53:54 +00:00
|
|
|
elif t == "rename":
|
|
|
|
from_name = 'from_name' in req.fields and req.fields["from_name"].value
|
|
|
|
to_name = 'to_name' in req.fields and req.fields["to_name"].value
|
|
|
|
if not from_name or not to_name:
|
|
|
|
raise RuntimeError("rename requires from_name and to_name")
|
|
|
|
if not IDirectoryNode.providedBy(self._node):
|
|
|
|
raise RuntimeError("rename must only be called on directories")
|
2007-07-17 00:05:01 +00:00
|
|
|
for k,v in [ ('from_name', from_name), ('to_name', to_name) ]:
|
|
|
|
if v and "/" in v:
|
|
|
|
req.setResponseCode(http.BAD_REQUEST)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "%s= may not contain a slash" % (k,)
|
2007-07-12 23:53:54 +00:00
|
|
|
d = self._node.get(from_name)
|
|
|
|
def add_dest(child):
|
|
|
|
uri = child.get_uri()
|
|
|
|
# now actually do the rename
|
|
|
|
return self._node.set_uri(to_name, uri)
|
|
|
|
d.addCallback(add_dest)
|
|
|
|
def rm_src(junk):
|
|
|
|
return self._node.delete(from_name)
|
|
|
|
d.addCallback(rm_src)
|
|
|
|
def _done(res):
|
|
|
|
return "thing renamed"
|
|
|
|
d.addCallback(_done)
|
2007-07-08 03:06:58 +00:00
|
|
|
elif t == "upload":
|
|
|
|
contents = req.fields["file"]
|
2007-07-08 03:46:30 +00:00
|
|
|
name = name or contents.filename
|
2007-07-08 03:06:58 +00:00
|
|
|
uploadable = upload.FileHandle(contents.file)
|
|
|
|
d = self._node.add_file(name, uploadable)
|
|
|
|
def _done(newnode):
|
|
|
|
return newnode.get_uri()
|
|
|
|
d.addCallback(_done)
|
|
|
|
else:
|
|
|
|
print "BAD t=%s" % t
|
|
|
|
return "BAD t=%s" % t
|
2007-07-08 04:17:48 +00:00
|
|
|
if when_done:
|
|
|
|
d.addCallback(lambda res: url.URL.fromString(when_done))
|
|
|
|
return d
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
class DELETEHandler(rend.Page):
|
|
|
|
def __init__(self, node, name):
|
|
|
|
self._node = node
|
|
|
|
self._name = name
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
2007-07-07 07:16:36 +00:00
|
|
|
req = inevow.IRequest(ctx)
|
2007-07-07 02:43:55 +00:00
|
|
|
d = self._node.delete(self._name)
|
|
|
|
def _done(res):
|
|
|
|
# what should this return??
|
|
|
|
return "%s deleted" % self._name
|
|
|
|
d.addCallback(_done)
|
2007-07-07 07:16:36 +00:00
|
|
|
def _trap_missing(f):
|
|
|
|
f.trap(KeyError)
|
|
|
|
req.setResponseCode(http.NOT_FOUND)
|
|
|
|
req.setHeader("content-type", "text/plain")
|
|
|
|
return "no such child %s" % self._name
|
|
|
|
d.addErrback(_trap_missing)
|
2007-07-07 02:43:55 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
class PUTHandler(rend.Page):
|
|
|
|
def __init__(self, node, path, t, localfile, localdir):
|
|
|
|
self._node = node
|
|
|
|
self._path = path
|
|
|
|
self._t = t
|
|
|
|
self._localfile = localfile
|
|
|
|
self._localdir = localdir
|
|
|
|
|
|
|
|
def renderHTTP(self, ctx):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
t = self._t
|
|
|
|
localfile = self._localfile
|
|
|
|
localdir = self._localdir
|
|
|
|
|
|
|
|
# we must traverse the path, creating new directories as necessary
|
|
|
|
d = self._get_or_create_directories(self._node, self._path[:-1])
|
|
|
|
name = self._path[-1]
|
2007-07-10 20:24:10 +00:00
|
|
|
if t == "upload":
|
|
|
|
if localfile:
|
|
|
|
d.addCallback(self._upload_localfile, localfile, name)
|
|
|
|
elif localdir:
|
2007-07-14 05:19:16 +00:00
|
|
|
# take the last step
|
2007-07-10 20:24:10 +00:00
|
|
|
d.addCallback(self._get_or_create_directories, self._path[-1:])
|
|
|
|
d.addCallback(self._upload_localdir, localdir)
|
|
|
|
else:
|
|
|
|
raise RuntimeError("t=upload requires localfile= or localdir=")
|
2007-07-07 02:43:55 +00:00
|
|
|
elif t == "uri":
|
|
|
|
d.addCallback(self._attach_uri, req.content, name)
|
|
|
|
elif t == "mkdir":
|
|
|
|
d.addCallback(self._mkdir, name)
|
|
|
|
else:
|
|
|
|
d.addCallback(self._upload_file, req.content, name)
|
|
|
|
def _check_blocking(f):
|
|
|
|
f.trap(BlockingFileError)
|
2007-07-16 19:01:19 +00:00
|
|
|
req.setResponseCode(http.BAD_REQUEST)
|
2007-07-07 02:43:55 +00:00
|
|
|
req.setHeader("content-type", "text/plain")
|
2007-07-16 19:01:19 +00:00
|
|
|
return str(f.value)
|
2007-07-07 02:43:55 +00:00
|
|
|
d.addErrback(_check_blocking)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _get_or_create_directories(self, node, path):
|
|
|
|
if not IDirectoryNode.providedBy(node):
|
2007-07-16 19:01:19 +00:00
|
|
|
# unfortunately it is too late to provide the name of the
|
|
|
|
# blocking directory in the error message.
|
|
|
|
raise BlockingFileError("cannot create directory because there "
|
|
|
|
"is a file in the way")
|
2007-07-07 02:43:55 +00:00
|
|
|
if not path:
|
2007-07-14 02:31:52 +00:00
|
|
|
return defer.succeed(node)
|
2007-07-07 02:43:55 +00:00
|
|
|
d = node.get(path[0])
|
|
|
|
def _maybe_create(f):
|
|
|
|
f.trap(KeyError)
|
|
|
|
return node.create_empty_directory(path[0])
|
|
|
|
d.addErrback(_maybe_create)
|
|
|
|
d.addCallback(self._get_or_create_directories, path[1:])
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _mkdir(self, node, name):
|
|
|
|
d = node.create_empty_directory(name)
|
|
|
|
def _done(newnode):
|
|
|
|
return newnode.get_uri()
|
|
|
|
d.addCallback(_done)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _upload_file(self, node, contents, name):
|
|
|
|
uploadable = upload.FileHandle(contents)
|
2007-07-08 05:12:46 +00:00
|
|
|
d = node.add_file(name, uploadable)
|
|
|
|
def _done(filenode):
|
2007-07-07 02:43:55 +00:00
|
|
|
log.msg("webish upload complete")
|
2007-07-08 05:12:46 +00:00
|
|
|
return filenode.get_uri()
|
2007-07-07 02:43:55 +00:00
|
|
|
d.addCallback(_done)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _upload_localfile(self, node, localfile, name):
|
|
|
|
uploadable = upload.FileName(localfile)
|
2007-07-07 07:16:36 +00:00
|
|
|
d = node.add_file(name, uploadable)
|
|
|
|
d.addCallback(lambda filenode: filenode.get_uri())
|
2007-07-07 02:43:55 +00:00
|
|
|
return d
|
|
|
|
|
|
|
|
def _attach_uri(self, parentnode, contents, name):
|
|
|
|
newuri = contents.read().strip()
|
|
|
|
d = parentnode.set_uri(name, newuri)
|
|
|
|
def _done(res):
|
|
|
|
return newuri
|
|
|
|
d.addCallback(_done)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _upload_localdir(self, node, localdir):
|
2007-07-07 07:16:36 +00:00
|
|
|
# build up a list of files to upload
|
|
|
|
all_files = []
|
|
|
|
all_dirs = []
|
|
|
|
for root, dirs, files in os.walk(localdir):
|
2007-07-07 17:34:05 +00:00
|
|
|
if root == localdir:
|
|
|
|
path = ()
|
|
|
|
else:
|
|
|
|
relative_root = root[len(localdir)+1:]
|
|
|
|
path = tuple(relative_root.split(os.sep))
|
2007-07-07 07:16:36 +00:00
|
|
|
for d in dirs:
|
|
|
|
all_dirs.append(path + (d,))
|
|
|
|
for f in files:
|
|
|
|
all_files.append(path + (f,))
|
|
|
|
d = defer.succeed(None)
|
|
|
|
for dir in all_dirs:
|
|
|
|
if dir:
|
|
|
|
d.addCallback(self._makedir, node, dir)
|
|
|
|
for f in all_files:
|
|
|
|
d.addCallback(self._upload_one_file, node, localdir, f)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _makedir(self, res, node, dir):
|
|
|
|
d = defer.succeed(None)
|
|
|
|
# get the parent. As long as os.walk gives us parents before
|
|
|
|
# children, this ought to work
|
|
|
|
d.addCallback(lambda res: node.get_child_at_path(dir[:-1]))
|
|
|
|
# then create the child directory
|
|
|
|
d.addCallback(lambda parent: parent.create_empty_directory(dir[-1]))
|
|
|
|
return d
|
|
|
|
|
|
|
|
def _upload_one_file(self, res, node, localdir, f):
|
|
|
|
# get the parent. We can be sure this exists because we already
|
|
|
|
# went through and created all the directories we require.
|
2007-07-07 17:34:05 +00:00
|
|
|
localfile = os.path.join(localdir, *f)
|
2007-07-07 07:16:36 +00:00
|
|
|
d = node.get_child_at_path(f[:-1])
|
|
|
|
d.addCallback(self._upload_localfile, localfile, f[-1])
|
|
|
|
return d
|
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
|
2007-06-27 02:55:21 +00:00
|
|
|
class Manifest(rend.Page):
|
|
|
|
docFactory = getxmlfile("manifest.xhtml")
|
2007-07-07 18:15:31 +00:00
|
|
|
def __init__(self, dirnode, dirpath):
|
2007-06-27 02:55:21 +00:00
|
|
|
self._dirnode = dirnode
|
2007-07-07 18:15:31 +00:00
|
|
|
self._dirpath = dirpath
|
|
|
|
|
|
|
|
def dirpath_as_string(self):
|
|
|
|
return "/" + "/".join(self._dirpath)
|
2007-06-27 02:55:21 +00:00
|
|
|
|
|
|
|
def render_title(self, ctx):
|
2007-07-07 18:15:31 +00:00
|
|
|
return T.title["Manifest of %s" % self.dirpath_as_string()]
|
2007-06-27 02:55:21 +00:00
|
|
|
|
|
|
|
def render_header(self, ctx):
|
2007-07-07 18:15:31 +00:00
|
|
|
return T.p["Manifest of %s" % self.dirpath_as_string()]
|
2007-06-27 02:55:21 +00:00
|
|
|
|
|
|
|
def data_items(self, ctx, data):
|
|
|
|
return self._dirnode.build_manifest()
|
|
|
|
|
|
|
|
def render_row(self, ctx, refresh_cap):
|
|
|
|
ctx.fillSlots("refresh_capability", refresh_cap)
|
|
|
|
return ctx.tag
|
2006-12-04 12:15:36 +00:00
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
class VDrive(rend.Page):
|
|
|
|
|
|
|
|
def __init__(self, node, name):
|
|
|
|
self.node = node
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
def get_child_at_path(self, path):
|
|
|
|
if path:
|
|
|
|
return self.node.get_child_at_path(path)
|
|
|
|
return defer.succeed(self.node)
|
|
|
|
|
|
|
|
def locateChild(self, ctx, segments):
|
|
|
|
req = inevow.IRequest(ctx)
|
|
|
|
method = req.method
|
|
|
|
path = segments
|
|
|
|
|
|
|
|
# when we're pointing at a directory (like /vdrive/public/my_pix),
|
|
|
|
# Directory.addSlash causes a redirect to /vdrive/public/my_pix/,
|
|
|
|
# which appears here as ['my_pix', '']. This is supposed to hit the
|
|
|
|
# same Directory as ['my_pix'].
|
|
|
|
if path and path[-1] == '':
|
|
|
|
path = path[:-1]
|
|
|
|
|
|
|
|
t = ""
|
|
|
|
if "t" in req.args:
|
|
|
|
t = req.args["t"][0]
|
|
|
|
|
|
|
|
localfile = None
|
|
|
|
if "localfile" in req.args:
|
|
|
|
localfile = req.args["localfile"][0]
|
2007-07-07 07:16:36 +00:00
|
|
|
if localfile != os.path.abspath(localfile):
|
|
|
|
return NeedAbsolutePathError(), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
localdir = None
|
|
|
|
if "localdir" in req.args:
|
|
|
|
localdir = req.args["localdir"][0]
|
2007-07-07 07:16:36 +00:00
|
|
|
if localdir != os.path.abspath(localdir):
|
|
|
|
return NeedAbsolutePathError(), ()
|
|
|
|
if localfile or localdir:
|
2007-08-11 01:21:22 +00:00
|
|
|
if not ILocalAccess(ctx).allow_local_access():
|
|
|
|
return LocalAccessDisabledError(), ()
|
2007-07-07 07:16:36 +00:00
|
|
|
if req.getHost().host != LOCALHOST:
|
|
|
|
return NeedLocalhostError(), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
# TODO: think about clobbering/revealing config files and node secrets
|
|
|
|
|
|
|
|
if method == "GET":
|
|
|
|
# the node must exist, and our operation will be performed on the
|
|
|
|
# node itself.
|
|
|
|
d = self.get_child_at_path(path)
|
|
|
|
def file_or_dir(node):
|
|
|
|
if IFileNode.providedBy(node):
|
2007-07-08 03:06:58 +00:00
|
|
|
filename = "unknown"
|
|
|
|
if path:
|
|
|
|
filename = path[-1]
|
|
|
|
if "filename" in req.args:
|
|
|
|
filename = req.args["filename"][0]
|
2007-07-10 20:24:10 +00:00
|
|
|
if t == "download":
|
|
|
|
if localfile:
|
|
|
|
# write contents to a local file
|
|
|
|
return LocalFileDownloader(node, localfile), ()
|
|
|
|
# send contents as the result
|
|
|
|
return FileDownloader(node, filename), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
elif t == "":
|
|
|
|
# send contents as the result
|
2007-07-08 03:06:58 +00:00
|
|
|
return FileDownloader(node, filename), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
elif t == "json":
|
|
|
|
return FileJSONMetadata(node), ()
|
|
|
|
elif t == "uri":
|
|
|
|
return FileURI(node), ()
|
2007-07-07 18:31:07 +00:00
|
|
|
elif t == "readonly-uri":
|
|
|
|
return FileURI(node), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
else:
|
|
|
|
raise RuntimeError("bad t=%s" % t)
|
|
|
|
elif IDirectoryNode.providedBy(node):
|
2007-07-10 20:24:10 +00:00
|
|
|
if t == "download":
|
|
|
|
if localdir:
|
|
|
|
# recursive download to a local directory
|
|
|
|
return LocalDirectoryDownloader(node, localdir), ()
|
|
|
|
raise RuntimeError("t=download requires localdir=")
|
2007-07-07 02:43:55 +00:00
|
|
|
elif t == "":
|
|
|
|
# send an HTML representation of the directory
|
2007-07-08 04:31:02 +00:00
|
|
|
return Directory(self.name, node, path), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
elif t == "json":
|
|
|
|
return DirectoryJSONMetadata(node), ()
|
|
|
|
elif t == "uri":
|
|
|
|
return DirectoryURI(node), ()
|
|
|
|
elif t == "readonly-uri":
|
|
|
|
return DirectoryReadonlyURI(node), ()
|
2007-07-07 18:15:31 +00:00
|
|
|
elif t == "manifest":
|
|
|
|
return Manifest(node, path), ()
|
2007-07-12 23:53:54 +00:00
|
|
|
elif t == 'rename-form':
|
|
|
|
return RenameForm(self.name, node, path), ()
|
2007-07-07 02:43:55 +00:00
|
|
|
else:
|
|
|
|
raise RuntimeError("bad t=%s" % t)
|
|
|
|
else:
|
|
|
|
raise RuntimeError("unknown node type")
|
|
|
|
d.addCallback(file_or_dir)
|
|
|
|
elif method == "POST":
|
|
|
|
# the node must exist, and our operation will be performed on the
|
|
|
|
# node itself.
|
|
|
|
d = self.get_child_at_path(path)
|
2007-07-07 07:16:36 +00:00
|
|
|
def _got(node):
|
|
|
|
return POSTHandler(node), ()
|
|
|
|
d.addCallback(_got)
|
2007-07-07 02:43:55 +00:00
|
|
|
elif method == "DELETE":
|
|
|
|
# the node must exist, and our operation will be performed on its
|
|
|
|
# parent node.
|
|
|
|
assert path # you can't delete the root
|
|
|
|
d = self.get_child_at_path(path[:-1])
|
2007-07-07 07:16:36 +00:00
|
|
|
def _got(node):
|
|
|
|
return DELETEHandler(node, path[-1]), ()
|
|
|
|
d.addCallback(_got)
|
2007-07-07 02:43:55 +00:00
|
|
|
elif method in ("PUT",):
|
|
|
|
# the node may or may not exist, and our operation may involve
|
|
|
|
# all the ancestors of the node.
|
|
|
|
return PUTHandler(self.node, path, t, localfile, localdir), ()
|
|
|
|
else:
|
|
|
|
return rend.NotFound
|
|
|
|
def _trap_KeyError(f):
|
|
|
|
f.trap(KeyError)
|
|
|
|
return rend.FourOhFour(), ()
|
|
|
|
d.addErrback(_trap_KeyError)
|
|
|
|
return d
|
|
|
|
|
2006-12-07 21:48:37 +00:00
|
|
|
|
|
|
|
class Root(rend.Page):
|
2007-07-07 02:43:55 +00:00
|
|
|
|
|
|
|
addSlash = True
|
|
|
|
docFactory = getxmlfile("welcome.xhtml")
|
|
|
|
|
2006-12-07 21:48:37 +00:00
|
|
|
def locateChild(self, ctx, segments):
|
2007-07-07 02:43:55 +00:00
|
|
|
client = IClient(ctx)
|
2007-07-08 03:06:58 +00:00
|
|
|
req = inevow.IRequest(ctx)
|
2007-07-07 02:43:55 +00:00
|
|
|
vdrive = client.getServiceNamed("vdrive")
|
|
|
|
|
|
|
|
if segments[0] == "vdrive":
|
|
|
|
if len(segments) < 2:
|
|
|
|
return rend.NotFound
|
|
|
|
if segments[1] == "global":
|
|
|
|
d = vdrive.get_public_root()
|
|
|
|
name = "public vdrive"
|
|
|
|
elif segments[1] == "private":
|
|
|
|
d = vdrive.get_private_root()
|
|
|
|
name = "private vdrive"
|
|
|
|
else:
|
|
|
|
return rend.NotFound
|
|
|
|
d.addCallback(lambda dirnode: VDrive(dirnode, name))
|
|
|
|
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
|
|
|
|
return d
|
|
|
|
elif segments[0] == "uri":
|
2007-07-08 05:06:52 +00:00
|
|
|
if len(segments) == 1 or segments[1] == '':
|
2007-07-08 03:06:58 +00:00
|
|
|
if "uri" in req.args:
|
2007-07-08 05:06:52 +00:00
|
|
|
uri = req.args["uri"][0].replace("/", "!")
|
2007-07-08 03:06:58 +00:00
|
|
|
there = url.URL.fromContext(ctx)
|
|
|
|
there = there.clear("uri")
|
|
|
|
there = there.child("uri").child(uri)
|
|
|
|
return there, ()
|
2007-07-07 02:43:55 +00:00
|
|
|
if len(segments) < 2:
|
|
|
|
return rend.NotFound
|
2007-07-08 05:06:52 +00:00
|
|
|
uri = segments[1].replace("!", "/")
|
2007-07-07 02:43:55 +00:00
|
|
|
d = vdrive.get_node(uri)
|
2007-07-08 05:06:52 +00:00
|
|
|
d.addCallback(lambda node: VDrive(node, "from-uri"))
|
2007-07-07 02:43:55 +00:00
|
|
|
d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:]))
|
2007-07-08 05:06:52 +00:00
|
|
|
def _trap_KeyError(f):
|
|
|
|
f.trap(KeyError)
|
|
|
|
return rend.FourOhFour(), ()
|
|
|
|
d.addErrback(_trap_KeyError)
|
2007-07-07 02:43:55 +00:00
|
|
|
return d
|
|
|
|
elif segments[0] == "xmlrpc":
|
|
|
|
pass # TODO
|
2006-12-07 21:48:37 +00:00
|
|
|
return rend.Page.locateChild(self, ctx, segments)
|
|
|
|
|
|
|
|
child_webform_css = webform.defaultCSS
|
2007-06-15 08:32:20 +00:00
|
|
|
child_tahoe_css = nevow_File(util.sibpath(__file__, "web/tahoe.css"))
|
2006-12-07 21:48:37 +00:00
|
|
|
|
2007-07-07 02:43:55 +00:00
|
|
|
def data_version(self, ctx, data):
|
|
|
|
v = IClient(ctx).get_versions()
|
|
|
|
return "tahoe: %s, zfec: %s, foolscap: %s, twisted: %s" % \
|
|
|
|
(v['allmydata'], v['zfec'], v['foolscap'], v['twisted'])
|
|
|
|
|
|
|
|
def data_my_nodeid(self, ctx, data):
|
2007-08-12 17:29:38 +00:00
|
|
|
return b32encode(IClient(ctx).nodeid).lower()
|
2007-07-07 02:43:55 +00:00
|
|
|
def data_introducer_furl(self, ctx, data):
|
|
|
|
return IClient(ctx).introducer_furl
|
|
|
|
def data_connected_to_introducer(self, ctx, data):
|
|
|
|
if IClient(ctx).connected_to_introducer():
|
|
|
|
return "yes"
|
|
|
|
return "no"
|
|
|
|
def data_connected_to_vdrive(self, ctx, data):
|
|
|
|
if IClient(ctx).getServiceNamed("vdrive").have_public_root():
|
|
|
|
return "yes"
|
|
|
|
return "no"
|
|
|
|
def data_num_peers(self, ctx, data):
|
|
|
|
#client = inevow.ISite(ctx)._client
|
|
|
|
client = IClient(ctx)
|
|
|
|
return len(list(client.get_all_peerids()))
|
|
|
|
|
|
|
|
def data_peers(self, ctx, data):
|
|
|
|
d = []
|
|
|
|
client = IClient(ctx)
|
|
|
|
for nodeid in sorted(client.get_all_peerids()):
|
2007-08-12 17:29:38 +00:00
|
|
|
row = (b32encode(nodeid).lower(),)
|
2007-07-07 02:43:55 +00:00
|
|
|
d.append(row)
|
|
|
|
return d
|
|
|
|
|
|
|
|
def render_row(self, ctx, data):
|
|
|
|
(nodeid_a,) = data
|
|
|
|
ctx.fillSlots("peerid", nodeid_a)
|
|
|
|
return ctx.tag
|
|
|
|
|
|
|
|
def render_global_vdrive(self, ctx, data):
|
|
|
|
if IClient(ctx).getServiceNamed("vdrive").have_public_root():
|
|
|
|
return T.p["To view the global shared filestore, ",
|
2007-07-07 18:15:31 +00:00
|
|
|
T.a(href="vdrive/global")["Click Here!"],
|
2007-07-07 02:43:55 +00:00
|
|
|
]
|
|
|
|
return T.p["vdrive.furl not specified (or vdrive server not "
|
|
|
|
"responding), no vdrive available."]
|
|
|
|
|
|
|
|
def render_private_vdrive(self, ctx, data):
|
|
|
|
if IClient(ctx).getServiceNamed("vdrive").have_private_root():
|
|
|
|
return T.p["To view your personal private non-shared filestore, ",
|
2007-07-07 18:15:31 +00:00
|
|
|
T.a(href="vdrive/private")["Click Here!"],
|
2007-07-07 02:43:55 +00:00
|
|
|
]
|
|
|
|
return T.p["personal vdrive not available."]
|
|
|
|
|
|
|
|
# this is a form where users can download files by URI
|
|
|
|
|
2007-07-08 05:06:22 +00:00
|
|
|
def render_download_form(self, ctx, data):
|
|
|
|
form = T.form(action="uri", method="get",
|
|
|
|
enctype="multipart/form-data")[
|
|
|
|
T.fieldset[
|
|
|
|
T.legend(class_="freeform-form-label")["Download a file"],
|
|
|
|
"URI of file to download: ",
|
|
|
|
T.input(type="text", name="uri"), " ",
|
|
|
|
"Filename to download as: ",
|
|
|
|
T.input(type="text", name="filename"), " ",
|
|
|
|
T.input(type="submit", value="Download"),
|
|
|
|
]]
|
|
|
|
return T.div[form]
|
2007-07-07 02:43:55 +00:00
|
|
|
|
2006-12-07 21:48:37 +00:00
|
|
|
|
2007-08-11 01:21:22 +00:00
|
|
|
class LocalAccess:
|
|
|
|
implements(ILocalAccess)
|
|
|
|
def __init__(self):
|
|
|
|
self.local_access = False
|
|
|
|
def allow_local_access(self):
|
|
|
|
return self.local_access
|
|
|
|
|
2006-12-07 21:48:37 +00:00
|
|
|
class WebishServer(service.MultiService):
|
|
|
|
name = "webish"
|
|
|
|
|
2007-08-11 01:21:22 +00:00
|
|
|
def __init__(self, webport, local_access=False):
|
2006-12-07 21:48:37 +00:00
|
|
|
service.MultiService.__init__(self)
|
|
|
|
self.root = Root()
|
|
|
|
self.site = site = appserver.NevowSite(self.root)
|
2007-08-11 00:25:33 +00:00
|
|
|
self.site.requestFactory = MyRequest
|
2007-08-11 01:21:22 +00:00
|
|
|
self.allow_local = LocalAccess()
|
|
|
|
self.site.remember(self.allow_local, ILocalAccess)
|
2006-12-07 21:48:37 +00:00
|
|
|
s = strports.service(webport, site)
|
|
|
|
s.setServiceParent(self)
|
|
|
|
self.listener = s # stash it so the tests can query for the portnum
|
|
|
|
|
2007-08-11 01:21:22 +00:00
|
|
|
def allow_local_access(self, enable=True):
|
|
|
|
self.allow_local.local_access = enable
|
|
|
|
|
2006-12-07 21:48:37 +00:00
|
|
|
def startService(self):
|
|
|
|
service.MultiService.startService(self)
|
|
|
|
# to make various services available to render_* methods, we stash a
|
|
|
|
# reference to the client on the NevowSite. This will be available by
|
|
|
|
# adapting the 'context' argument to a special marker interface named
|
|
|
|
# IClient.
|
|
|
|
self.site.remember(self.parent, IClient)
|
|
|
|
# I thought you could do the same with an existing interface, but
|
|
|
|
# apparently 'ISite' does not exist
|
|
|
|
#self.site._client = self.parent
|