From: Brian Warner Date: Sat, 7 Jul 2007 02:43:55 +0000 (-0700) Subject: checkpointing new webapi: not all tests pass yet X-Git-Url: https://git.rkrishnan.org/components/com_hotproperty/%22doc.html/%3C?a=commitdiff_plain;h=6570253d6b646e171605e3ba3a05b15604857e1a;p=tahoe-lafs%2Ftahoe-lafs.git checkpointing new webapi: not all tests pass yet --- diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py new file mode 100644 index 00000000..41f56984 --- /dev/null +++ b/src/allmydata/test/test_web.py @@ -0,0 +1,525 @@ + +import re, os.path +from zope.interface import implements +from twisted.application import service +from twisted.trial import unittest +from twisted.internet import defer +from twisted.web import client, error +from twisted.python import failure +from allmydata import webish, interfaces, dirnode, uri +from allmydata.encode import NotEnoughPeersError +import itertools + +# create a fake uploader/downloader, and a couple of fake dirnodes, then +# create a webserver that works against them + +class MyClient(service.MultiService): + nodeid = "fake_nodeid" + def get_versions(self): + return {'allmydata': "fake", + 'foolscap': "fake", + 'twisted': "fake", + 'zfec': "fake", + } + introducer_furl = "None" + def connected_to_introducer(self): + return False + def get_all_peerids(self): + return [] + +class MyDownloader(service.Service): + implements(interfaces.IDownloader) + name = "downloader" + def __init__(self, files): + self.files = files + + def download(self, uri, target): + print "DOWNLOADING", uri + if uri not in self.files: + e = NotEnoughPeersError() + f = failure.Failure(e) + target.fail(f) + return defer.fail(f) + data = self.files[uri] + target.open(len(data)) + target.write(data) + target.close() + return defer.maybeDeferred(target.finish) + +uri_counter = itertools.count() + +class MyUploader(service.Service): + implements(interfaces.IUploader) + name = "uploader" + def __init__(self, files): + self.files = files + + def upload(self, uploadable): + f = uploadable.get_filehandle() + data = f.read() + uri = str(uri_counter.next()) + self.files[uri] = data + uploadable.close_filehandle(f) + return defer.succeed(uri) + +class MyDirectoryNode(dirnode.MutableDirectoryNode): + + def __init__(self, nodes, uri=None): + self._nodes = nodes + if uri is None: + uri = str(uri_counter.next()) + self._uri = str(uri) + self._nodes[self._uri] = self + self.children = {} + self._mutable = True + + def get_immutable_uri(self): + return self.get_uri() + "RO" + + def get(self, name): + def _try(): + uri = self.children[name] + if uri not in self._nodes: + raise IndexError("this isn't supposed to happen") + return self._nodes[uri] + return defer.maybeDeferred(_try) + + def set_uri(self, name, child_uri): + self.children[name] = child_uri + return defer.succeed(None) + + def create_empty_directory(self, name): + node = MyDirectoryNode(self._nodes) + self.children[name] = node.get_uri() + return defer.succeed(node) + + def list(self): + kids = dict([(name, self._nodes[uri]) + for name,uri in self.children.iteritems()]) + return defer.succeed(kids) + +class MyFileNode(dirnode.FileNode): + pass + + +class MyVirtualDrive(service.Service): + name = "vdrive" + public_root = None + private_root = None + def have_public_root(self): + return bool(self.public_root) + def have_private_root(self): + return bool(self.private_root) + def get_public_root(self): + return defer.succeed(self.public_root) + def get_private_root(self): + return defer.succeed(self.private_root) + +class Web(unittest.TestCase): + def setUp(self): + self.s = MyClient() + self.s.startService() + s = webish.WebishServer("0") + s.setServiceParent(self.s) + port = s.listener._port.getHost().port + self.webish_url = "http://localhost:%d" % port + + v = MyVirtualDrive() + v.setServiceParent(self.s) + + self.nodes = {} # maps URI to node + self.files = {} # maps file URI to contents + dl = MyDownloader(self.files) + dl.setServiceParent(self.s) + ul = MyUploader(self.files) + ul.setServiceParent(self.s) + + v.public_root = MyDirectoryNode(self.nodes) + v.private_root = MyDirectoryNode(self.nodes) + foo = MyDirectoryNode(self.nodes) + self._foo_node = foo + self._foo_uri = foo.get_uri() + self._foo_readonly_uri = foo.get_immutable_uri() + v.public_root.children["foo"] = foo.get_uri() + + self.BAR_CONTENTS = "bar.txt contents" + + bar_uri = uri.pack_uri("SI"+"0"*30, + "K"+"0"*15, + "EH"+"0"*30, + 25, 100, 123) + bar_txt = MyFileNode(bar_uri, self.s) + self._bar_txt_uri = bar_txt.get_uri() + self.nodes[bar_uri] = bar_txt + self.files[bar_txt.get_uri()] = self.BAR_CONTENTS + foo.children["bar.txt"] = bar_txt.get_uri() + + foo.children["sub"] = MyDirectoryNode(self.nodes).get_uri() + + blocking_uri = uri.pack_uri("SI"+"1"*30, + "K"+"1"*15, + "EH"+"1"*30, + 25, 100, 124) + blocking_file = MyFileNode(blocking_uri, self.s) + self.nodes[blocking_uri] = blocking_file + self.files[blocking_uri] = "blocking contents" + foo.children["blockingfile"] = blocking_file.get_uri() + + # public/ + # public/foo/ + # public/foo/bar.txt + # public/foo/sub/ + # public/foo/blockingfile + self.NEWFILE_CONTENTS = "newfile contents\n" + + def tearDown(self): + return self.s.stopService() + + def failUnlessIsBarDotTxt(self, res): + self.failUnlessEqual(res, self.BAR_CONTENTS) + + def GET(self, urlpath): + url = self.webish_url + urlpath + return client.getPage(url, method="GET") + + def PUT(self, urlpath, data): + url = self.webish_url + urlpath + return client.getPage(url, method="PUT", postdata=data) + + def DELETE(self, urlpath): + url = self.webish_url + urlpath + return client.getPage(url, method="DELETE") + + def POST(self, urlpath, data): + url = self.webish_url + urlpath + return client.getPage(url, method="POST", postdata=data) + + def shouldFail(self, res, expected_failure, which, substring=None): + print "SHOULDFAIL", res + if isinstance(res, failure.Failure): + res.trap(expected_failure) + if substring: + self.failUnless(substring in str(res), + "substring '%s' not in '%s'" + % (substring, str(res))) + else: + self.fail("%s was supposed to raise %s, not get '%s'" % + (which, expected_failure, res)) + + def should404(self, res, which): + if isinstance(res, failure.Failure): + res.trap(error.Error) + self.failUnlessEqual(res.value.status, "404") + else: + self.fail("%s was supposed to raise %s, not get '%s'" % + (which, expected_failure, res)) + + def test_create(self): # YES + pass + + def test_welcome(self): # YES + d = self.GET("") + return d + + def test_GET_FILEURL(self): # YES + d = self.GET("/vdrive/global/foo/bar.txt") + d.addCallback(self.failUnlessIsBarDotTxt) + return d + + def test_GET_FILEURL_missing(self): # YES + d = self.GET("/vdrive/global/foo/missing") + def _oops(f): + print f + print dir(f) + print f.value + print dir(f.value) + print f.value.args + print f.value.response + print f.value.status + return f + #d.addBoth(_oops) + d.addBoth(self.should404, "test_GET_FILEURL_missing") + return d + + def test_PUT_NEWFILEURL(self): # YES + d = self.PUT("/vdrive/global/foo/new.txt", self.NEWFILE_CONTENTS) + def _check(res): + self.failUnless("new.txt" in self._foo_node.children) + new_uri = self._foo_node.children["new.txt"] + new_contents = self.files[new_uri] + self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS) + self.failUnlessEqual(res.strip(), new_uri) + d.addCallback(_check) + return d + + def test_PUT_NEWFILEURL_mkdirs(self): # YES + d = self.PUT("/vdrive/global/foo/newdir/new.txt", self.NEWFILE_CONTENTS) + def _check(res): + self.failIf("new.txt" in self._foo_node.children) + self.failUnless("newdir" in self._foo_node.children) + newdir_uri = self._foo_node.children["newdir"] + newdir_node = self.nodes[newdir_uri] + self.failUnless("new.txt" in newdir_node.children) + new_uri = newdir_node.children["new.txt"] + new_contents = self.files[new_uri] + self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS) + self.failUnlessEqual(res.strip(), new_uri) + d.addCallback(_check) + return d + + def test_PUT_NEWFILEURL_blocked(self): # YES + d = self.PUT("/vdrive/global/foo/blockingfile/new.txt", + self.NEWFILE_CONTENTS) + d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked", + "403 Forbidden") + return d + + def test_DELETE_FILEURL(self): + d = self.DELETE("/vdrive/global/foo/bar.txt") + return d + + def test_DELETE_FILEURL_missing(self): + d = self.DELETE("/vdrive/global/foo/missing") + return d + + def test_GET_FILEURL_json(self): # YES + # twisted.web.http.parse_qs ignores any query args without an '=', so + # I can't do "GET /path?json", I have to do "GET /path/t=json" + # instead. This may make it tricky to emulate the S3 interface + # completely. + d = self.GET("/vdrive/global/foo/bar.txt?t=json") + def _got(json): + # TODO + self.failUnless("JSON" in json, json) + d.addCallback(_got) + return d + + def test_GET_FILEURL_json_missing(self): # YES + d = self.GET("/vdrive/global/foo/missing?json") + d.addBoth(self.should404, "test_GET_FILEURL_json_missing") + return d + + def test_GET_FILEURL_localfile(self): # YES + localfile = os.path.abspath("web/GET_FILEURL_localfile") + os.makedirs("web") + d = self.GET("/vdrive/global/foo/bar.txt?localfile=%s" % localfile) + def _done(res): + self.failUnless(os.path.exists(localfile)) + data = open(localfile, "rb").read() + self.failUnlessEqual(data, self.BAR_CONTENTS) + d.addCallback(_done) + return d + + def test_GET_FILEURL_localfile_nonlocal(self): # YES + # TODO: somehow pretend that we aren't local, and verify that the + # server refuses to write to local files, probably by changing the + # server's idea of what counts as "local". + old_LOCALHOST = webish.LOCALHOST + webish.LOCALHOST = "127.0.0.2" + localfile = os.path.abspath("web/GET_FILEURL_localfile_nonlocal") + os.makedirs("web") + d = self.GET("/vdrive/global/foo/bar.txt?localfile=%s" % localfile) + d.addBoth(self.shouldFail, error.Error, "localfile non-local", + "403 Forbidden") + def _check(res): + self.failIf(os.path.exists(localfile)) + d.addCallback(_check) + def _reset(res): + print "RESETTING", res + webish.LOCALHOST = old_LOCALHOST + return res + d.addBoth(_reset) + return d + + def test_PUT_NEWFILEURL_localfile(self): # YES + localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile") + os.makedirs("web") + f = open(localfile, "wb") + f.write(self.NEWFILE_CONTENTS) + f.close() + d = self.PUT("/vdrive/global/foo/new.txt?localfile=%s" % localfile, "") + def _check(res): + self.failUnless("new.txt" in self._foo_node.children) + new_uri = self._foo_node.children["new.txt"] + new_contents = self.files[new_uri] + self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS) + self.failUnlessEqual(res.strip(), new_uri) + d.addCallback(_check) + return d + + def test_PUT_NEWFILEURL_localfile_mkdirs(self): # YES + localfile = os.path.abspath("web/PUT_NEWFILEURL_localfile_mkdirs") + os.makedirs("web") + f = open(localfile, "wb") + f.write(self.NEWFILE_CONTENTS) + f.close() + d = self.PUT("/vdrive/global/foo/newdir/new.txt?localfile=%s" % + localfile, "") + def _check(res): + self.failIf("new.txt" in self._foo_node.children) + self.failUnless("newdir" in self._foo_node.children) + newdir_uri = self._foo_node.children["newdir"] + newdir_node = self.nodes[newdir_uri] + self.failUnless("new.txt" in newdir_node.children) + new_uri = newdir_node.children["new.txt"] + new_contents = self.files[new_uri] + self.failUnlessEqual(new_contents, self.NEWFILE_CONTENTS) + self.failUnlessEqual(res.strip(), new_uri) + d.addCallback(_check) + return d + + def test_GET_FILEURL_uri(self): # YES + d = self.GET("/vdrive/global/foo/bar.txt?t=uri") + def _check(res): + self.failUnlessEqual(res, self._bar_txt_uri) + d.addCallback(_check) + return d + + def test_GET_FILEURL_uri_missing(self): # YES + d = self.GET("/vdrive/global/foo/missing?t=uri") + d.addBoth(self.should404, "test_GET_FILEURL_uri_missing") + return d + + def test_GET_DIRURL(self): # YES + d = self.GET("/vdrive/global/foo") + def _check(res): + self.failUnless(re.search(r'bar.txt' + '\s+FILE' + '\s+123' + , res)) + self.failUnless(re.search(r'sub' + '\s+DIR', res)) + d.addCallback(_check) + return d + + def test_GET_DIRURL_json(self): # YES + d = self.GET("/vdrive/global/foo?t=json") + def _got(json): + # TODO + self.failUnless("JSON" in json, json) + d.addCallback(_got) + return d + + def test_GET_DIRURL_uri(self): # YES + d = self.GET("/vdrive/global/foo?t=uri") + def _check(res): + self.failUnlessEqual(res, self._foo_uri) + d.addCallback(_check) + return d + + def test_GET_DIRURL_readonly_uri(self): # YES + d = self.GET("/vdrive/global/foo?t=readonly-uri") + def _check(res): + self.failUnlessEqual(res, self._foo_readonly_uri) + d.addCallback(_check) + return d + + def test_PUT_NEWDIRURL(self): # YES + d = self.PUT("/vdrive/global/foo/newdir?t=mkdir", "") + def _check(res): + self.failUnless("newdir" in self._foo_node.children) + newdir_uri = self._foo_node.children["newdir"] + newdir_node = self.nodes[newdir_uri] + self.failIf(newdir_node.children) + d.addCallback(_check) + return d + + def test_PUT_NEWDIRURL_mkdirs(self): # YES + d = self.PUT("/vdrive/global/foo/subdir/newdir?t=mkdir", "") + def _check(res): + self.failIf("newdir" in self._foo_node.children) + self.failUnless("subdir" in self._foo_node.children) + subdir_node = self.nodes[self._foo_node.children["subdir"]] + self.failUnless("newdir" in subdir_node.children) + newdir_node = self.nodes[subdir_node.children["newdir"]] + self.failIf(newdir_node.children) + d.addCallback(_check) + return d + + def test_DELETE_DIRURL(self): + d = self.DELETE("/vdrive/global/foo") + return d + + def test_DELETE_DIRURL_missing(self): + d = self.DELETE("/vdrive/global/missing") + return d + + def test_GET_DIRURL_localdir(self): + localdir = os.path.abspath("web/GET_DIRURL_localdir") + os.makedirs("web") + d = self.GET("/vdrive/global/foo?localdir=%s" % localdir) + return d + + def test_PUT_NEWDIRURL_localdir(self): + localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir") + os.makedirs("web") + # create some files there + d = self.GET("/vdrive/global/foo/newdir?localdir=%s" % localdir) + return d + + def test_PUT_NEWDIRURL_localdir_mkdirs(self): + localdir = os.path.abspath("web/PUT_NEWDIRURL_localdir_mkdirs") + os.makedirs("web") + # create some files there + d = self.GET("/vdrive/global/foo/subdir/newdir?localdir=%s" % localdir) + return d + + def test_POST_upload(self): + form = "TODO" + d = self.POST("/vdrive/global/foo", form) + return d + + def test_POST_mkdir(self): + form = "TODO" + d = self.POST("/vdrive/global/foo", form) + return d + + def test_POST_put_uri(self): + form = "TODO" + d = self.POST("/vdrive/global/foo", form) + return d + + def test_POST_delete(self): + form = "TODO, bar.txt" + d = self.POST("/vdrive/global/foo", form) + return d + + def test_URI_GET(self): + d = self.GET("/uri/%s/bar.txt" % foo_uri) + return d + + def test_PUT_NEWFILEURL_uri(self): + d = self.PUT("/vdrive/global/foo/new.txt?uri", new_uri) + return d + + def test_XMLRPC(self): + pass + + + +""" + # GET / (welcome) + # GET FILEURL +# PUT NEWFILEURL +# DELETE FILEURL + # GET FILEURL?t=json +# GET FILEURL?localfile=$FILENAME +# PUT NEWFILEURL?localfile=$FILENAME +# GET FILEURL?t=uri +# GET DIRURL +# GET DIRURL?t=json +# GET DIRURL?t=uri +# GET DIRURL?t=readonly-uri +# PUT NEWDIRURL?t=mkdir +# DELETE DIRURL +# GET DIRURL?localdir=$DIRNAME +# PUT NEWDIRURL?localdir=$DIRNAME +# POST DIRURL?t=upload-form +# POST DIRURL?t=mkdir-form +# POST DIRURL?t=put-uri-form +# POST DIRURL?t=delete-form +# GET .../url/$URI +# and a few others +# PUT NEWFILEURL?t=uri +# /xmlrpc +""" diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index aaaaf3aa..d95d3feb 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -2,13 +2,14 @@ from twisted.application import service, strports from twisted.web import static, resource, server, html, http from twisted.python import util, log +from twisted.internet import defer from nevow import inevow, rend, loaders, appserver, url, tags as T from nevow.static import File as nevow_File # TODO: merge with static.File? from allmydata.util import idlib from allmydata.uri import unpack_uri from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode from allmydata.dirnode import FileNode -from allmydata import upload +from allmydata import upload, download from zope.interface import implements, Interface import urllib from formless import annotate, webform @@ -24,84 +25,6 @@ def get_downloader_service(ctx): def get_uploader_service(ctx): return IClient(ctx).getServiceNamed("uploader") -class Welcome(rend.Page): - addSlash = True - docFactory = getxmlfile("welcome.xhtml") - - 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): - return idlib.b2a(IClient(ctx).nodeid) - 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()): - row = (idlib.b2a(nodeid),) - 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, ", - T.a(href="../global_vdrive")["Click Here!"], - ] - 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, ", - T.a(href="../private_vdrive")["Click Here!"], - ] - return T.p["personal vdrive not available."] - - # this is a form where users can download files by URI - - def bind_download(self, ctx): - uriarg = annotate.Argument("uri", - annotate.String("URI of file to download: ")) - namearg = annotate.Argument("filename", - annotate.String("Filename to download as: ")) - ctxarg = annotate.Argument("ctx", annotate.Context()) - meth = annotate.Method(arguments=[uriarg, namearg, ctxarg], - label="Download File by URI") - # buttons always use value=data.label - # MethodBindingRenderer uses value=(data.action or data.label) - return annotate.MethodBinding("download", meth, action="Download") - - def download(self, uri, filename, ctx): - log.msg("webish downloading URI") - target = url.here.sibling("download_uri").add("uri", uri) - if filename: - target = target.add("filename", filename) - return target - - def render_forms(self, ctx, data): - return webform.renderForms() - class Directory(rend.Page): addSlash = True @@ -112,27 +35,15 @@ class Directory(rend.Page): self._dirname = dirname def childFactory(self, ctx, name): + print "Directory.childFactory", name if name.startswith("freeform"): # ick return None if name == "@manifest": # ick, this time it's my fault return Manifest(self._dirnode, self._dirname) - if self._dirname == "/": - dirname = "/" + name - else: - dirname = self._dirname + "/" + name - d = self._dirnode.get(name) - def _got_child(res): - if IFileNode.providedBy(res): - dl = get_downloader_service(ctx) - return Downloader(dl, name, res) - elif IDirectoryNode.providedBy(res): - return Directory(res, dirname) - else: - raise RuntimeError("what is this %s" % res) - d.addCallback(_got_child) - return d + return rend.NotFound def render_title(self, ctx, data): + print "DIRECTORY.render_title" return ctx.tag["Directory '%s':" % self._dirname] def render_header(self, ctx, data): @@ -380,15 +291,13 @@ class TypedFile(static.File): self.contentEncodings, self.defaultType) -class Downloader(resource.Resource): - def __init__(self, downloader, name, filenode): - self._downloader = downloader +class FileDownloader(resource.Resource): + def __init__(self, name, filenode): self._name = name IFileNode(filenode) self._filenode = filenode - def render(self, ctx): - req = inevow.IRequest(ctx) + def render(self, req): gte = static.getTypeAndEncoding type, encoding = gte(self._name, static.File.contentTypes, @@ -400,6 +309,242 @@ class Downloader(resource.Resource): d.addErrback(lambda why: None) return server.NOT_DONE_YET +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 locateChild(self, ctx, segments): + return rend.NotFound + + 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" + + + +class LocalFileDownloader(resource.Resource): + def __init__(self, filenode, local_filename): + self._local_filename = local_filename + IFileNode(filenode) + self._filenode = filenode + + def render(self, req): + print "LOCALFILEDOWNLOADER", self._local_filename + 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 + +class FileJSONMetadata(rend.Page): + def __init__(self, filenode): + self._filenode = filenode + + def renderHTTP(self, ctx): + file_uri = self._filenode.get_uri() + pieces = unpack_uri(file_uri) + data = "filenode\n" + data += "JSONny stuff here\n" + data += "uri=%s, size=%s" % (file_uri, pieces['size']) + return data + +class FileXMLMetadata(FileJSONMetadata): + def renderHTTP(self, ctx): + file_uri = self._filenode.get_uri() + pieces = unpack_uri(file_uri) + data = "\n" + data += "filenode\n" + data += "stuff here\n" + data += "uri=%s, size=%s" % (file_uri, pieces['size']) + return data + +class FileURI(FileJSONMetadata): + def renderHTTP(self, ctx): + file_uri = self._filenode.get_uri() + return file_uri + +class LocalDirectoryDownloader(resource.Resource): + def __init__(self, dirnode): + self._dirnode = dirnode + + def renderHTTP(self, ctx): + dl = get_downloader_service(ctx) + pass # TODO + +class DirectoryJSONMetadata(rend.Page): + def __init__(self, dirnode): + self._dirnode = dirnode + + def renderHTTP(self, ctx): + file_uri = self._dirnode.get_uri() + data = "dirnode\n" + data += "JSONny stuff here\n" + d = self._dirnode.list() + def _got(children, data): + for name, childnode in children.iteritems(): + data += "name=%s, child_uri=%s" % (name, childnode.get_uri()) + return data + d.addCallback(_got, data) + def _done(data): + data += "done\n" + return data + d.addCallback(_done) + return d + +class DirectoryXMLMetadata(DirectoryJSONMetadata): + def renderHTTP(self, ctx): + file_uri = self._dirnode.get_uri() + pieces = unpack_uri(file_uri) + data = "\n" + data += "dirnode\n" + data += "stuff here\n" + d = self._dirnode.list() + def _got(children, data): + for name, childnode in children: + data += "name=%s, child_uri=%s" % (name, childnode.get_uri()) + return data + d.addCallback(_got) + def _done(data): + data += "\n" + return data + d.addCallback(_done) + return d + +class DirectoryURI(DirectoryJSONMetadata): + def renderHTTP(self, ctx): + dir_uri = self._dirnode.get_uri() + return dir_uri + +class DirectoryReadonlyURI(DirectoryJSONMetadata): + def renderHTTP(self, ctx): + dir_uri = self._dirnode.get_immutable_uri() + return dir_uri + +class POSTHandler(rend.Page): + def __init__(self, node): + self._node = node + + # TODO: handler methods + +class DELETEHandler(rend.Page): + def __init__(self, node, name): + self._node = node + self._name = name + + def renderHTTP(self, ctx): + d = self._node.delete(self._name) + def _done(res): + # what should this return?? + return "%s deleted" % self._name + d.addCallback(_done) + 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 + self._uploader = get_uploader_service(ctx) + + # 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] + if localfile: + d.addCallback(self._upload_localfile, localfile, name) + elif localdir: + d.addCallback(self._upload_localdir, localdir) + 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) + req.setResponseCode(http.FORBIDDEN) + req.setHeader("content-type", "text/plain") + return str(f) + d.addErrback(_check_blocking) + return d + + def _get_or_create_directories(self, node, path): + if not IDirectoryNode.providedBy(node): + raise BlockingFileError + if not path: + return node + d = node.get(path[0]) + def _maybe_create(f): + f.trap(KeyError) + print "CREATING", path[0] + 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) + d = self._uploader.upload(uploadable) + def _uploaded(uri): + d1 = node.set_uri(name, uri) + d1.addCallback(lambda res: uri) + return d1 + d.addCallback(_uploaded) + def _done(uri): + log.msg("webish upload complete") + return uri + d.addCallback(_done) + return d + + def _upload_localfile(self, node, localfile, name): + uploadable = upload.FileName(localfile) + d = self._uploader.upload(uploadable) + def _uploaded(uri): + print "SETTING URI", name, uri + d1 = node.set_uri(name, uri) + d1.addCallback(lambda res: uri) + return d1 + d.addCallback(_uploaded) + def _done(uri): + log.msg("webish upload complete") + return uri + d.addCallback(_done) + 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): + pass # TODO + class Manifest(rend.Page): docFactory = getxmlfile("manifest.xhtml") def __init__(self, dirnode, dirname): @@ -419,10 +564,148 @@ class Manifest(rend.Page): ctx.fillSlots("refresh_capability", refresh_cap) return ctx.tag +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] + + print "VDrive.locateChild", method, segments, req.args + t = "" + if "t" in req.args: + t = req.args["t"][0] + + localfile = None + if "localfile" in req.args: + localfile = req.args["localfile"][0] + localdir = None + if "localdir" in req.args: + localdir = req.args["localdir"][0] + if (localfile or localdir) and req.getHost().host != LOCALHOST: + return NeedLocalhostError(), () + # 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. + name = path[-1] + d = self.get_child_at_path(path) + def file_or_dir(node): + if IFileNode.providedBy(node): + if localfile: + # write contents to a local file + return LocalFileDownloader(node, localfile), () + elif t == "": + # send contents as the result + print "FileDownloader" + return FileDownloader(name, node), () + elif t == "json": + print "Localfilejsonmetadata" + return FileJSONMetadata(node), () + elif t == "xml": + return FileXMLMetadata(node), () + elif t == "uri": + return FileURI(node), () + else: + raise RuntimeError("bad t=%s" % t) + elif IDirectoryNode.providedBy(node): + print "GOT DIR" + if localdir: + # recursive download to a local directory + return LocalDirectoryDownloader(node, localdir), () + elif t == "": + # send an HTML representation of the directory + print "GOT HTML DIR" + return Directory(node, name), () + elif t == "json": + return DirectoryJSONMetadata(node), () + elif t == "xml": + return DirectoryXMLMetadata(node), () + elif t == "uri": + return DirectoryURI(node), () + elif t == "readonly-uri": + return DirectoryReadonlyURI(node), () + 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) + d.addCallback(lambda node: POSTHandler(node), ()) + 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]) + d.addCallback(lambda node: DELETEHandler(node, path[-1]), ) + 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 + class Root(rend.Page): + + addSlash = True + docFactory = getxmlfile("welcome.xhtml") + def locateChild(self, ctx, segments): - if segments[0] == "download_uri": + client = IClient(ctx) + vdrive = client.getServiceNamed("vdrive") + print "Root.locateChild", segments + + 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": + if len(segments) < 2: + return rend.NotFound + uri = segments[1] + d = vdrive.get_node(uri) + d.addCallback(lambda node: VDrive(node), uri) + d.addCallback(lambda vd: vd.locateChild(ctx, segments[2:])) + return d + elif segments[0] == "xmlrpc": + pass # TODO + elif segments[0] == "download_uri": req = inevow.IRequest(ctx) dl = get_downloader_service(ctx) filename = "unknown_filename" @@ -436,14 +719,14 @@ class Root(rend.Page): uri = req.args["uri"][0] else: return rend.NotFound - child = Downloader(dl, filename, FileNode(uri, IClient(ctx))) + child = FileDownloader(filename, FileNode(uri, IClient(ctx))) return child, () return rend.Page.locateChild(self, ctx, segments) child_webform_css = webform.defaultCSS child_tahoe_css = nevow_File(util.sibpath(__file__, "web/tahoe.css")) - child_welcome = Welcome() + #child_welcome = Welcome() def child_global_vdrive(self, ctx): client = IClient(ctx) @@ -465,6 +748,80 @@ class Root(rend.Page): else: return static.Data("sorry, still initializing", "text/plain") + 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): + return idlib.b2a(IClient(ctx).nodeid) + 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()): + row = (idlib.b2a(nodeid),) + 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, ", + T.a(href="../global_vdrive")["Click Here!"], + ] + 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, ", + T.a(href="../private_vdrive")["Click Here!"], + ] + return T.p["personal vdrive not available."] + + # this is a form where users can download files by URI + + def bind_download(self, ctx): + uriarg = annotate.Argument("uri", + annotate.String("URI of file to download: ")) + namearg = annotate.Argument("filename", + annotate.String("Filename to download as: ")) + ctxarg = annotate.Argument("ctx", annotate.Context()) + meth = annotate.Method(arguments=[uriarg, namearg, ctxarg], + label="Download File by URI") + # buttons always use value=data.label + # MethodBindingRenderer uses value=(data.action or data.label) + return annotate.MethodBinding("download", meth, action="Download") + + def download(self, uri, filename, ctx): + log.msg("webish downloading URI") + target = url.here.sibling("download_uri").add("uri", uri) + if filename: + target = target.add("filename", filename) + return target + + def render_forms(self, ctx, data): + return webform.renderForms() + class WebishServer(service.MultiService): name = "webish" @@ -472,7 +829,7 @@ class WebishServer(service.MultiService): def __init__(self, webport): service.MultiService.__init__(self) self.root = Root() - self.root.putChild("", url.here.child("welcome"))#Welcome()) + #self.root.putChild("", url.here.child("welcome"))#Welcome()) self.site = site = appserver.NevowSite(self.root) s = strports.service(webport, site)