webish: add preliminary mutable file support: upload, download, listings, JSON, URI, RO-URI. No replace yet.

This commit is contained in:
Brian Warner
2007-11-09 03:54:27 -07:00
parent 63233ecf37
commit 1f22768dc7
5 changed files with 110 additions and 25 deletions

View File

@ -433,6 +433,9 @@ class FileNode:
def get_uri(self): def get_uri(self):
return self.uri return self.uri
def is_readonly(self):
return True
def get_size(self): def get_size(self):
return IFileURI(self.uri).get_size() return IFileURI(self.uri).get_size()

View File

@ -1148,7 +1148,8 @@ class MutableFileNode:
# wants to get our contents, we'll pull from shares and fill those # wants to get our contents, we'll pull from shares and fill those
# in. # in.
self._uri = IMutableFileURI(myuri) 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._readkey = self._uri.readkey
self._storage_index = self._uri.storage_index self._storage_index = self._uri.storage_index
self._fingerprint = self._uri.fingerprint self._fingerprint = self._uri.fingerprint
@ -1269,6 +1270,14 @@ class MutableFileNode:
def get_uri(self): def get_uri(self):
return self._uri.to_string() 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): def is_mutable(self):
return self._uri.is_mutable() return self._uri.is_mutable()
@ -1293,9 +1302,15 @@ class MutableFileNode:
return self._client.getServiceNamed("checker").check(verifier) return self._client.getServiceNamed("checker").check(verifier)
def download(self, target): def download(self, target):
#downloader = self._client.getServiceNamed("downloader") # fake it. TODO: make this cleaner.
#return downloader.download(self.uri, target) d = self.download_to_data()
raise NotImplementedError 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): def download_to_data(self):
r = Retrieve(self) r = Retrieve(self)

View File

@ -29,6 +29,11 @@ class MyClient(service.MultiService):
def get_all_peerids(self): def get_all_peerids(self):
return [] return []
def upload(self, uploadable):
uploader = self.getServiceNamed("uploader")
return uploader.upload(uploadable)
class MyDownloader(service.Service): class MyDownloader(service.Service):
implements(interfaces.IDownloader) implements(interfaces.IDownloader)
name = "downloader" name = "downloader"

View File

@ -7,6 +7,7 @@ from twisted.internet import defer
from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI from allmydata.interfaces import IVirtualDrive, IDirnodeURI, IURI
from allmydata.util import observer from allmydata.util import observer
from allmydata import dirnode from allmydata import dirnode
from allmydata.dirnode2 import INewDirectoryURI
class NoGlobalVirtualDriveError(Exception): class NoGlobalVirtualDriveError(Exception):
pass pass
@ -133,10 +134,11 @@ class VirtualDrive(service.MultiService):
def get_node(self, node_uri): def get_node(self, node_uri):
node_uri = IURI(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) return dirnode.create_directory_node(self.parent, node_uri)
else: 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): def get_node_at_path(self, path, root=None):

View 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 nevow.static import File as nevow_File # TODO: merge with static.File?
from allmydata.util import fileutil from allmydata.util import fileutil
import simplejson 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 upload, download
from allmydata import provisioning from allmydata import provisioning
from zope.interface import implements, Interface from zope.interface import implements, Interface
@ -180,7 +181,9 @@ class Directory(rend.Page):
# build the base of the uri_link link url # build the base of the uri_link link url
uri_link = "/uri/" + urllib.quote(target.get_uri().replace("/", "!")) 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): if IFileNode.providedBy(target):
# file # file
@ -203,6 +206,27 @@ class Directory(rend.Page):
text_plain_link = uri_link + "?filename=foo.txt" text_plain_link = uri_link + "?filename=foo.txt"
text_plain_tag = T.a(href=text_plain_link)["text/plain"] 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): elif IDirectoryNode.providedBy(target):
# directory # directory
@ -274,6 +298,7 @@ class Directory(rend.Page):
T.input(type="text", name="name"), " ", T.input(type="text", name="name"), " ",
T.input(type="submit", value="Create"), T.input(type="submit", value="Create"),
]] ]]
upload = T.form(action=".", method="post", upload = T.form(action=".", method="post",
enctype="multipart/form-data")[ enctype="multipart/form-data")[
T.fieldset[ T.fieldset[
@ -284,7 +309,10 @@ class Directory(rend.Page):
T.input(type="file", name="file", class_="freeform-input-file"), T.input(type="file", name="file", class_="freeform-input-file"),
" ", " ",
T.input(type="submit", value="Upload"), T.input(type="submit", value="Upload"),
" Mutable?:",
T.input(type="checkbox", name="mutable"),
]] ]]
mount = T.form(action=".", method="post", mount = T.form(action=".", method="post",
enctype="multipart/form-data")[ enctype="multipart/form-data")[
T.fieldset[ T.fieldset[
@ -367,7 +395,8 @@ class WebDownloadTarget:
class FileDownloader(resource.Resource): class FileDownloader(resource.Resource):
def __init__(self, filenode, name): def __init__(self, filenode, name):
IFileNode(filenode) assert (IFileNode.providedBy(filenode)
or IMutableFileNode.providedBy(filenode))
self._filenode = filenode self._filenode = filenode
self._name = name self._name = name
@ -457,6 +486,13 @@ class FileURI(FileJSONMetadata):
file_uri = filenode.get_uri() file_uri = filenode.get_uri()
return file_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: class DirnodeWalkerMixin:
"""Visit all nodes underneath (and including) the rootnode, one at a """Visit all nodes underneath (and including) the rootnode, one at a
time. For each one, call the visitor. The visitor will see the time. For each one, call the visitor. The visitor will see the
@ -719,19 +755,41 @@ class POSTHandler(rend.Page):
def _done(res): def _done(res):
return "thing renamed" return "thing renamed"
d.addCallback(_done) d.addCallback(_done)
elif t == "upload": elif t == "upload":
contents = req.fields["file"] if "mutable" in req.fields:
name = name or contents.filename contents = req.fields["file"]
if name is not None: name = name or contents.filename
name = name.strip() if name is not None:
if not name: name = name.strip()
raise RuntimeError("set-uri requires a name") if not name:
uploadable = upload.FileHandle(contents.file) raise RuntimeError("upload-mutable requires a name")
d = self._check_replacement(name) # SDMF: files are small, and we can only upload data.
d.addCallback(lambda res: self._node.add_file(name, uploadable)) contents.file.seek(0)
def _done(newnode): data = contents.file.read()
return newnode.get_uri() uploadable = upload.FileHandle(contents.file)
d.addCallback(_done) 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": elif t == "check":
d = self._node.get(name) d = self._node.get(name)
def _got_child(child_node): def _got_child(child_node):
@ -1006,7 +1064,8 @@ class VDrive(rend.Page):
# node itself. # node itself.
d = self.get_child_at_path(path) d = self.get_child_at_path(path)
def file_or_dir(node): def file_or_dir(node):
if IFileNode.providedBy(node): if (IFileNode.providedBy(node)
or IMutableFileNode.providedBy(node)):
filename = "unknown" filename = "unknown"
if path: if path:
filename = path[-1] filename = path[-1]
@ -1026,7 +1085,7 @@ class VDrive(rend.Page):
elif t == "uri": elif t == "uri":
return FileURI(node), () return FileURI(node), ()
elif t == "readonly-uri": elif t == "readonly-uri":
return FileURI(node), () return FileReadOnlyURI(node), ()
else: else:
raise RuntimeError("bad t=%s" % t) raise RuntimeError("bad t=%s" % t)
elif IDirectoryNode.providedBy(node): elif IDirectoryNode.providedBy(node):
@ -1093,8 +1152,7 @@ class URIPUTHandler(rend.Page):
# "PUT /uri", to create an unlinked file. This is like PUT but # "PUT /uri", to create an unlinked file. This is like PUT but
# without the associated set_uri. # without the associated set_uri.
uploadable = upload.FileHandle(req.content) uploadable = upload.FileHandle(req.content)
uploader = IClient(ctx).getServiceNamed("uploader") d = IClient(ctx).upload(uploadable)
d = uploader.upload(uploadable)
# that fires with the URI of the new file # that fires with the URI of the new file
return d return d
@ -1103,6 +1161,8 @@ class URIPUTHandler(rend.Page):
# public vdriveserver to create the dirnode. # public vdriveserver to create the dirnode.
vdrive = IClient(ctx).getServiceNamed("vdrive") vdrive = IClient(ctx).getServiceNamed("vdrive")
d = vdrive.create_directory() 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()) d.addCallback(lambda dirnode: dirnode.get_uri())
return d return d