webish: add preliminary mutable file support: upload, download, listings, JSON, URI...
authorBrian Warner <warner@lothar.com>
Fri, 9 Nov 2007 10:54:27 +0000 (03:54 -0700)
committerBrian Warner <warner@lothar.com>
Fri, 9 Nov 2007 10:54:27 +0000 (03:54 -0700)
src/allmydata/dirnode.py
src/allmydata/mutable.py
src/allmydata/test/test_web.py
src/allmydata/vdrive.py
src/allmydata/webish.py

index 5b199b044bfbd7eb0e30ab8fd4ea6ecbda3b2ce1..d910ca0b982a739a55d0ba10ce60cf9bdf1688aa 100644 (file)
@@ -433,6 +433,9 @@ class FileNode:
     def get_uri(self):
         return self.uri
 
+    def is_readonly(self):
+        return True
+
     def get_size(self):
         return IFileURI(self.uri).get_size()
 
index d73e6c1297c63bead56e9b00b13a210ec0ea91ef..fe866d37b3c4aa1e2c8ea3085d658f41171a9d12 100644 (file)
@@ -1148,7 +1148,8 @@ class MutableFileNode:
         # wants to get our contents, we'll pull from shares and fill those
         # in.
         self._uri = IMutableFileURI(myuri)
-        self._writekey = self._uri.writekey
+        if not self._uri.is_readonly():
+            self._writekey = self._uri.writekey
         self._readkey = self._uri.readkey
         self._storage_index = self._uri.storage_index
         self._fingerprint = self._uri.fingerprint
@@ -1269,6 +1270,14 @@ class MutableFileNode:
 
     def get_uri(self):
         return self._uri.to_string()
+    def get_size(self):
+        return "?" # TODO: this is likely to cause problems, not being an int
+    def get_readonly(self):
+        if self.is_readonly():
+            return self
+        ro = MutableFileNode(self._client)
+        ro.init_from_uri(self._uri.get_readonly())
+        return ro
 
     def is_mutable(self):
         return self._uri.is_mutable()
@@ -1293,9 +1302,15 @@ class MutableFileNode:
         return self._client.getServiceNamed("checker").check(verifier)
 
     def download(self, target):
-        #downloader = self._client.getServiceNamed("downloader")
-        #return downloader.download(self.uri, target)
-        raise NotImplementedError
+        # fake it. TODO: make this cleaner.
+        d = self.download_to_data()
+        def _done(data):
+            target.open(len(data))
+            target.write(data)
+            target.close()
+            return target.finish()
+        d.addCallback(_done)
+        return d
 
     def download_to_data(self):
         r = Retrieve(self)
index 8ca91460ee2ae42daab9270992e2e7ff697382a4..25ca0fd89ac0063554ee1d7203780a4c1ab69cf8 100644 (file)
@@ -29,6 +29,11 @@ class MyClient(service.MultiService):
     def get_all_peerids(self):
         return []
 
+    def upload(self, uploadable):
+        uploader = self.getServiceNamed("uploader")
+        return uploader.upload(uploadable)
+
+
 class MyDownloader(service.Service):
     implements(interfaces.IDownloader)
     name = "downloader"
index bd8e2ad40ad54daf8218a49c637ae12d42da1a47..d7089e6dfd2abee1d2885c28b1f6e9a8621e6187 100644 (file)
@@ -7,6 +7,7 @@ from twisted.internet import defer
 from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI
 from allmydata.util import observer
 from allmydata import dirnode
+from allmydata.dirnode2 import INewDirectoryURI
 
 class NoGlobalVirtualDriveError(Exception):
     pass
@@ -133,10 +134,11 @@ class VirtualDrive(service.MultiService):
 
     def get_node(self, node_uri):
         node_uri = IURI(node_uri)
-        if IDirnodeURI.providedBy(node_uri):
+        if (IDirnodeURI.providedBy(node_uri)
+            and not INewDirectoryURI.providedBy(node_uri)):
             return dirnode.create_directory_node(self.parent, node_uri)
         else:
-            return defer.succeed(dirnode.FileNode(node_uri, self.parent))
+            return defer.succeed(self.parent.create_node_from_uri(node_uri))
 
 
     def get_node_at_path(self, path, root=None):
index d7e07299122201dbe6e91de758479cdb2a08a9ca..a8e888a620e17071e4dc2cdc89a66b88337fc3e3 100644 (file)
@@ -10,7 +10,8 @@ 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 fileutil
 import simplejson
-from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode
+from allmydata.interfaces import IDownloadTarget, IDirectoryNode, IFileNode, \
+     IMutableFileNode
 from allmydata import upload, download
 from allmydata import provisioning
 from zope.interface import implements, Interface
@@ -180,7 +181,9 @@ class Directory(rend.Page):
         # build the base of the uri_link link url
         uri_link = "/uri/" + urllib.quote(target.get_uri().replace("/", "!"))
 
-        assert IFileNode.providedBy(target) or IDirectoryNode.providedBy(target), target
+        assert (IFileNode.providedBy(target)
+                or IDirectoryNode.providedBy(target)
+                or IMutableFileNode.providedBy(target)), target
 
         if IFileNode.providedBy(target):
             # file
@@ -203,6 +206,27 @@ class Directory(rend.Page):
             text_plain_link = uri_link + "?filename=foo.txt"
             text_plain_tag = T.a(href=text_plain_link)["text/plain"]
 
+        elif IMutableFileNode.providedBy(target):
+            # file
+
+            # add the filename to the uri_link url
+            uri_link += '?%s' % (urllib.urlencode({'filename': name}),)
+
+            # to prevent javascript in displayed .html files from stealing a
+            # secret vdrive URI from the URL, send the browser to a URI-based
+            # page that doesn't know about the vdrive at all
+            #dlurl = urllib.quote(name)
+            dlurl = uri_link
+
+            ctx.fillSlots("filename",
+                          T.a(href=dlurl)[html.escape(name)])
+            ctx.fillSlots("type", "SSK")
+
+            ctx.fillSlots("size", "?")
+
+            text_plain_link = uri_link + "?filename=foo.txt"
+            text_plain_tag = T.a(href=text_plain_link)["text/plain"]
+
 
         elif IDirectoryNode.providedBy(target):
             # directory
@@ -274,6 +298,7 @@ class Directory(rend.Page):
             T.input(type="text", name="name"), " ",
             T.input(type="submit", value="Create"),
             ]]
+
         upload = T.form(action=".", method="post",
                         enctype="multipart/form-data")[
             T.fieldset[
@@ -284,7 +309,10 @@ class Directory(rend.Page):
             T.input(type="file", name="file", class_="freeform-input-file"),
             " ",
             T.input(type="submit", value="Upload"),
+            " Mutable?:",
+            T.input(type="checkbox", name="mutable"),
             ]]
+
         mount = T.form(action=".", method="post",
                         enctype="multipart/form-data")[
             T.fieldset[
@@ -367,7 +395,8 @@ class WebDownloadTarget:
 
 class FileDownloader(resource.Resource):
     def __init__(self, filenode, name):
-        IFileNode(filenode)
+        assert (IFileNode.providedBy(filenode)
+                or IMutableFileNode.providedBy(filenode))
         self._filenode = filenode
         self._name = name
 
@@ -457,6 +486,13 @@ class FileURI(FileJSONMetadata):
         file_uri = filenode.get_uri()
         return file_uri
 
+class FileReadOnlyURI(FileJSONMetadata):
+    def renderNode(self, filenode):
+        if filenode.is_readonly():
+            return filenode.get_uri()
+        else:
+            return filenode.get_readonly().get_uri()
+
 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
@@ -719,19 +755,41 @@ class POSTHandler(rend.Page):
             def _done(res):
                 return "thing renamed"
             d.addCallback(_done)
+
         elif t == "upload":
-            contents = req.fields["file"]
-            name = name or contents.filename
-            if name is not None:
-                name = name.strip()
-            if not name:
-                raise RuntimeError("set-uri requires a name")
-            uploadable = upload.FileHandle(contents.file)
-            d = self._check_replacement(name)
-            d.addCallback(lambda res: self._node.add_file(name, uploadable))
-            def _done(newnode):
-                return newnode.get_uri()
-            d.addCallback(_done)
+            if "mutable" in req.fields:
+                contents = req.fields["file"]
+                name = name or contents.filename
+                if name is not None:
+                    name = name.strip()
+                if not name:
+                    raise RuntimeError("upload-mutable requires a name")
+                # SDMF: files are small, and we can only upload data.
+                contents.file.seek(0)
+                data = contents.file.read()
+                uploadable = upload.FileHandle(contents.file)
+                d = self._check_replacement(name)
+                d.addCallback(lambda res:
+                              IClient(ctx).create_mutable_file(data))
+                def _uploaded(newnode):
+                    d1 = self._node.set_node(name, newnode)
+                    d1.addCallback(lambda res: newnode.get_uri())
+                    return d1
+                d.addCallback(_uploaded)
+            else:
+                contents = req.fields["file"]
+                name = name or contents.filename
+                if name is not None:
+                    name = name.strip()
+                if not name:
+                    raise RuntimeError("upload requires a name")
+                uploadable = upload.FileHandle(contents.file)
+                d = self._check_replacement(name)
+                d.addCallback(lambda res: self._node.add_file(name, uploadable))
+                def _done(newnode):
+                    return newnode.get_uri()
+                d.addCallback(_done)
+
         elif t == "check":
             d = self._node.get(name)
             def _got_child(child_node):
@@ -1006,7 +1064,8 @@ class VDrive(rend.Page):
             # node itself.
             d = self.get_child_at_path(path)
             def file_or_dir(node):
-                if IFileNode.providedBy(node):
+                if (IFileNode.providedBy(node)
+                    or IMutableFileNode.providedBy(node)):
                     filename = "unknown"
                     if path:
                         filename = path[-1]
@@ -1026,7 +1085,7 @@ class VDrive(rend.Page):
                     elif t == "uri":
                         return FileURI(node), ()
                     elif t == "readonly-uri":
-                        return FileURI(node), ()
+                        return FileReadOnlyURI(node), ()
                     else:
                         raise RuntimeError("bad t=%s" % t)
                 elif IDirectoryNode.providedBy(node):
@@ -1093,8 +1152,7 @@ class URIPUTHandler(rend.Page):
             # "PUT /uri", to create an unlinked file. This is like PUT but
             # without the associated set_uri.
             uploadable = upload.FileHandle(req.content)
-            uploader = IClient(ctx).getServiceNamed("uploader")
-            d = uploader.upload(uploadable)
+            d = IClient(ctx).upload(uploadable)
             # that fires with the URI of the new file
             return d
 
@@ -1103,6 +1161,8 @@ class URIPUTHandler(rend.Page):
             # public vdriveserver to create the dirnode.
             vdrive = IClient(ctx).getServiceNamed("vdrive")
             d = vdrive.create_directory()
+            # TODO: switch to new-style dirnodes and replace this with:
+            #d = IClient(ctx).create_empty_dirnode()
             d.addCallback(lambda dirnode: dirnode.get_uri())
             return d