]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
checkpointing new webapi: not all tests pass yet
authorBrian Warner <warner@lothar.com>
Sat, 7 Jul 2007 02:43:55 +0000 (19:43 -0700)
committerBrian Warner <warner@lothar.com>
Sat, 7 Jul 2007 02:43:55 +0000 (19:43 -0700)
src/allmydata/test/test_web.py [new file with mode: 0644]
src/allmydata/webish.py

diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
new file mode 100644 (file)
index 0000000..41f5698
--- /dev/null
@@ -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'<td><a href="bar.txt">bar.txt</a></td>'
+                                      '\s+<td>FILE</td>'
+                                      '\s+<td>123</td>'
+                                      , res))
+            self.failUnless(re.search(r'<td><a href="sub">sub</a></td>'
+                                      '\s+<td>DIR</td>', 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
+"""
index aaaaf3aa2d53de06aa8b947e3cfbcdab0897081f..d95d3feb29030fefa584aa11b4a605beb457cb51 100644 (file)
@@ -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 = "<xmlish>\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 = "<xmlish>\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 += "</done>\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)