mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-06-23 09:15:32 +00:00
webish: add preliminary mutable file support: upload, download, listings, JSON, URI, RO-URI. No replace yet.
This commit is contained in:
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user