From: Brian Warner Date: Fri, 9 Nov 2007 10:54:27 +0000 (-0700) Subject: webish: add preliminary mutable file support: upload, download, listings, JSON, URI... X-Git-Tag: allmydata-tahoe-0.7.0~235 X-Git-Url: https://git.rkrishnan.org/?a=commitdiff_plain;h=1f22768dc78803d8f87732a9cef80f47f483bc10;p=tahoe-lafs%2Ftahoe-lafs.git webish: add preliminary mutable file support: upload, download, listings, JSON, URI, RO-URI. No replace yet. --- diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index 5b199b04..d910ca0b 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -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() diff --git a/src/allmydata/mutable.py b/src/allmydata/mutable.py index d73e6c12..fe866d37 100644 --- a/src/allmydata/mutable.py +++ b/src/allmydata/mutable.py @@ -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) diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py index 8ca91460..25ca0fd8 100644 --- a/src/allmydata/test/test_web.py +++ b/src/allmydata/test/test_web.py @@ -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" diff --git a/src/allmydata/vdrive.py b/src/allmydata/vdrive.py index bd8e2ad4..d7089e6d 100644 --- a/src/allmydata/vdrive.py +++ b/src/allmydata/vdrive.py @@ -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): diff --git a/src/allmydata/webish.py b/src/allmydata/webish.py index d7e07299..a8e888a6 100644 --- a/src/allmydata/webish.py +++ b/src/allmydata/webish.py @@ -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