mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-04-27 22:39:41 +00:00
webapi changes for MDMF
- Learn how to create MDMF files and directories through the mutable-type argument. - Operate with the interface changes associated with MDMF and #993. - Learn how to do partial updates of mutable files.
This commit is contained in:
parent
bb10d685ed
commit
a9cada2e03
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ from nevow.util import resource_filename
|
|||||||
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
|
from allmydata.interfaces import ExistingChildError, NoSuchChildError, \
|
||||||
FileTooLargeError, NotEnoughSharesError, NoSharesError, \
|
FileTooLargeError, NotEnoughSharesError, NoSharesError, \
|
||||||
EmptyPathnameComponentError, MustBeDeepImmutableError, \
|
EmptyPathnameComponentError, MustBeDeepImmutableError, \
|
||||||
MustBeReadonlyError, MustNotBeUnknownRWError
|
MustBeReadonlyError, MustNotBeUnknownRWError, SDMF_VERSION, MDMF_VERSION
|
||||||
from allmydata.mutable.common import UnrecoverableFileError
|
from allmydata.mutable.common import UnrecoverableFileError
|
||||||
from allmydata.util import abbreviate
|
from allmydata.util import abbreviate
|
||||||
from allmydata.util.encodingutil import to_str, quote_output
|
from allmydata.util.encodingutil import to_str, quote_output
|
||||||
@ -32,6 +32,32 @@ def parse_replace_arg(replace):
|
|||||||
else:
|
else:
|
||||||
return boolean_of_arg(replace)
|
return boolean_of_arg(replace)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_mutable_type_arg(arg):
|
||||||
|
if not arg:
|
||||||
|
return None # interpreted by the caller as "let the nodemaker decide"
|
||||||
|
|
||||||
|
arg = arg.lower()
|
||||||
|
if arg == "mdmf":
|
||||||
|
return MDMF_VERSION
|
||||||
|
elif arg == "sdmf":
|
||||||
|
return SDMF_VERSION
|
||||||
|
|
||||||
|
return "invalid"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_offset_arg(offset):
|
||||||
|
# XXX: This will raise a ValueError when invoked on something that
|
||||||
|
# is not an integer. Is that okay? Or do we want a better error
|
||||||
|
# message? Since this call is going to be used by programmers and
|
||||||
|
# their tools rather than users (through the wui), it is not
|
||||||
|
# inconsistent to return that, I guess.
|
||||||
|
if offset is not None:
|
||||||
|
offset = int(offset)
|
||||||
|
|
||||||
|
return offset
|
||||||
|
|
||||||
|
|
||||||
def get_root(ctx_or_req):
|
def get_root(ctx_or_req):
|
||||||
req = IRequest(ctx_or_req)
|
req = IRequest(ctx_or_req)
|
||||||
# the addSlash=True gives us one extra (empty) segment
|
# the addSlash=True gives us one extra (empty) segment
|
||||||
|
@ -16,14 +16,15 @@ from allmydata.util import base32, time_format
|
|||||||
from allmydata.uri import from_string_dirnode
|
from allmydata.uri import from_string_dirnode
|
||||||
from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
|
from allmydata.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
|
||||||
IImmutableFileNode, IMutableFileNode, ExistingChildError, \
|
IImmutableFileNode, IMutableFileNode, ExistingChildError, \
|
||||||
NoSuchChildError, EmptyPathnameComponentError
|
NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
|
||||||
from allmydata.monitor import Monitor, OperationCancelledError
|
from allmydata.monitor import Monitor, OperationCancelledError
|
||||||
from allmydata import dirnode
|
from allmydata import dirnode
|
||||||
from allmydata.web.common import text_plain, WebError, \
|
from allmydata.web.common import text_plain, WebError, \
|
||||||
IOpHandleTable, NeedOperationHandleError, \
|
IOpHandleTable, NeedOperationHandleError, \
|
||||||
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
|
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
|
||||||
should_create_intermediate_directories, \
|
should_create_intermediate_directories, \
|
||||||
getxmlfile, RenderMixin, humanize_failure, convert_children_json
|
getxmlfile, RenderMixin, humanize_failure, convert_children_json, \
|
||||||
|
parse_mutable_type_arg
|
||||||
from allmydata.web.filenode import ReplaceMeMixin, \
|
from allmydata.web.filenode import ReplaceMeMixin, \
|
||||||
FileNodeHandler, PlaceHolderNodeHandler
|
FileNodeHandler, PlaceHolderNodeHandler
|
||||||
from allmydata.web.check_results import CheckResults, \
|
from allmydata.web.check_results import CheckResults, \
|
||||||
@ -108,8 +109,17 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
mutable = True
|
mutable = True
|
||||||
if t == "mkdir-immutable":
|
if t == "mkdir-immutable":
|
||||||
mutable = False
|
mutable = False
|
||||||
|
|
||||||
|
mt = None
|
||||||
|
if mutable:
|
||||||
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
mt = parse_mutable_type_arg(arg)
|
||||||
|
if mt is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg,
|
||||||
|
http.BAD_REQUEST)
|
||||||
d = self.node.create_subdirectory(name, kids,
|
d = self.node.create_subdirectory(name, kids,
|
||||||
mutable=mutable)
|
mutable=mutable,
|
||||||
|
mutable_version=mt)
|
||||||
d.addCallback(make_handler_for,
|
d.addCallback(make_handler_for,
|
||||||
self.client, self.node, name)
|
self.client, self.node, name)
|
||||||
return d
|
return d
|
||||||
@ -150,7 +160,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
if not t:
|
if not t:
|
||||||
# render the directory as HTML, using the docFactory and Nevow's
|
# render the directory as HTML, using the docFactory and Nevow's
|
||||||
# whole templating thing.
|
# whole templating thing.
|
||||||
return DirectoryAsHTML(self.node)
|
return DirectoryAsHTML(self.node,
|
||||||
|
self.client.mutable_file_default)
|
||||||
|
|
||||||
if t == "json":
|
if t == "json":
|
||||||
return DirectoryJSONMetadata(ctx, self.node)
|
return DirectoryJSONMetadata(ctx, self.node)
|
||||||
@ -239,7 +250,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
name = name.decode("utf-8")
|
name = name.decode("utf-8")
|
||||||
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
||||||
kids = {}
|
kids = {}
|
||||||
d = self.node.create_subdirectory(name, kids, overwrite=replace)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
mt = parse_mutable_type_arg(arg)
|
||||||
|
if mt is not None and mt is not "invalid":
|
||||||
|
d = self.node.create_subdirectory(name, kids, overwrite=replace,
|
||||||
|
mutable_version=mt)
|
||||||
|
elif mt is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
d = self.node.create_subdirectory(name, kids, overwrite=replace)
|
||||||
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -255,7 +274,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
req.content.seek(0)
|
req.content.seek(0)
|
||||||
kids_json = req.content.read()
|
kids_json = req.content.read()
|
||||||
kids = convert_children_json(self.client.nodemaker, kids_json)
|
kids = convert_children_json(self.client.nodemaker, kids_json)
|
||||||
d = self.node.create_subdirectory(name, kids, overwrite=False)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
mt = parse_mutable_type_arg(arg)
|
||||||
|
if mt is not None and mt is not "invalid":
|
||||||
|
d = self.node.create_subdirectory(name, kids, overwrite=False,
|
||||||
|
mutable_version=mt)
|
||||||
|
elif mt is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg)
|
||||||
|
else:
|
||||||
|
d = self.node.create_subdirectory(name, kids, overwrite=False)
|
||||||
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
d.addCallback(lambda child: child.get_uri()) # TODO: urlencode
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -552,10 +579,13 @@ class DirectoryAsHTML(rend.Page):
|
|||||||
docFactory = getxmlfile("directory.xhtml")
|
docFactory = getxmlfile("directory.xhtml")
|
||||||
addSlash = True
|
addSlash = True
|
||||||
|
|
||||||
def __init__(self, node):
|
def __init__(self, node, default_mutable_format):
|
||||||
rend.Page.__init__(self)
|
rend.Page.__init__(self)
|
||||||
self.node = node
|
self.node = node
|
||||||
|
|
||||||
|
assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
|
||||||
|
self.default_mutable_format = default_mutable_format
|
||||||
|
|
||||||
def beforeRender(self, ctx):
|
def beforeRender(self, ctx):
|
||||||
# attempt to get the dirnode's children, stashing them (or the
|
# attempt to get the dirnode's children, stashing them (or the
|
||||||
# failure that results) for later use
|
# failure that results) for later use
|
||||||
@ -753,6 +783,7 @@ class DirectoryAsHTML(rend.Page):
|
|||||||
|
|
||||||
return ctx.tag
|
return ctx.tag
|
||||||
|
|
||||||
|
# XXX: Duplicated from root.py.
|
||||||
def render_forms(self, ctx, data):
|
def render_forms(self, ctx, data):
|
||||||
forms = []
|
forms = []
|
||||||
|
|
||||||
@ -761,6 +792,12 @@ class DirectoryAsHTML(rend.Page):
|
|||||||
if self.dirnode_children is None:
|
if self.dirnode_children is None:
|
||||||
return T.div["No upload forms: directory is unreadable"]
|
return T.div["No upload forms: directory is unreadable"]
|
||||||
|
|
||||||
|
mdmf_directory_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-directory-mdmf',
|
||||||
|
value='mdmf')
|
||||||
|
sdmf_directory_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-directory-sdmf',
|
||||||
|
value='sdmf', checked='checked')
|
||||||
mkdir = T.form(action=".", method="post",
|
mkdir = T.form(action=".", method="post",
|
||||||
enctype="multipart/form-data")[
|
enctype="multipart/form-data")[
|
||||||
T.fieldset[
|
T.fieldset[
|
||||||
@ -769,10 +806,34 @@ class DirectoryAsHTML(rend.Page):
|
|||||||
T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
|
T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
|
||||||
"New directory name: ",
|
"New directory name: ",
|
||||||
T.input(type="text", name="name"), " ",
|
T.input(type="text", name="name"), " ",
|
||||||
|
T.label(for_='mutable-directory-sdmf')["SDMF"],
|
||||||
|
sdmf_directory_input,
|
||||||
|
T.label(for_='mutable-directory-mdmf')["MDMF"],
|
||||||
|
mdmf_directory_input,
|
||||||
T.input(type="submit", value="Create"),
|
T.input(type="submit", value="Create"),
|
||||||
]]
|
]]
|
||||||
forms.append(T.div(class_="freeform-form")[mkdir])
|
forms.append(T.div(class_="freeform-form")[mkdir])
|
||||||
|
|
||||||
|
# Build input elements for mutable file type. We do this outside
|
||||||
|
# of the list so we can check the appropriate format, based on
|
||||||
|
# the default configured in the client (which reflects the
|
||||||
|
# default configured in tahoe.cfg)
|
||||||
|
if self.default_mutable_format == MDMF_VERSION:
|
||||||
|
mdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-type-mdmf', value='mdmf',
|
||||||
|
checked='checked')
|
||||||
|
else:
|
||||||
|
mdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-type-mdmf', value='mdmf')
|
||||||
|
|
||||||
|
if self.default_mutable_format == SDMF_VERSION:
|
||||||
|
sdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-type-sdmf', value='sdmf',
|
||||||
|
checked="checked")
|
||||||
|
else:
|
||||||
|
sdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
id='mutable-type-sdmf', value='sdmf')
|
||||||
|
|
||||||
upload = T.form(action=".", method="post",
|
upload = T.form(action=".", method="post",
|
||||||
enctype="multipart/form-data")[
|
enctype="multipart/form-data")[
|
||||||
T.fieldset[
|
T.fieldset[
|
||||||
@ -785,6 +846,9 @@ class DirectoryAsHTML(rend.Page):
|
|||||||
T.input(type="submit", value="Upload"),
|
T.input(type="submit", value="Upload"),
|
||||||
" Mutable?:",
|
" Mutable?:",
|
||||||
T.input(type="checkbox", name="mutable"),
|
T.input(type="checkbox", name="mutable"),
|
||||||
|
sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
|
||||||
|
mdmf_input,
|
||||||
|
T.label(for_="mutable-type-mdmf")["MDMF (experimental)"],
|
||||||
]]
|
]]
|
||||||
forms.append(T.div(class_="freeform-form")[upload])
|
forms.append(T.div(class_="freeform-form")[upload])
|
||||||
|
|
||||||
@ -820,6 +884,17 @@ def DirectoryJSONMetadata(ctx, dirnode):
|
|||||||
kiddata = ("filenode", {'size': childnode.get_size(),
|
kiddata = ("filenode", {'size': childnode.get_size(),
|
||||||
'mutable': childnode.is_mutable(),
|
'mutable': childnode.is_mutable(),
|
||||||
})
|
})
|
||||||
|
if childnode.is_mutable() and \
|
||||||
|
childnode.get_version() is not None:
|
||||||
|
mutable_type = childnode.get_version()
|
||||||
|
assert mutable_type in (SDMF_VERSION, MDMF_VERSION)
|
||||||
|
|
||||||
|
if mutable_type == MDMF_VERSION:
|
||||||
|
mutable_type = "mdmf"
|
||||||
|
else:
|
||||||
|
mutable_type = "sdmf"
|
||||||
|
kiddata[1]['mutable-type'] = mutable_type
|
||||||
|
|
||||||
elif IDirectoryNode.providedBy(childnode):
|
elif IDirectoryNode.providedBy(childnode):
|
||||||
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
|
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
|
||||||
else:
|
else:
|
||||||
|
@ -6,14 +6,17 @@ from twisted.internet import defer
|
|||||||
from nevow import url, rend
|
from nevow import url, rend
|
||||||
from nevow.inevow import IRequest
|
from nevow.inevow import IRequest
|
||||||
|
|
||||||
from allmydata.interfaces import ExistingChildError
|
from allmydata.interfaces import ExistingChildError, SDMF_VERSION, MDMF_VERSION
|
||||||
from allmydata.monitor import Monitor
|
from allmydata.monitor import Monitor
|
||||||
from allmydata.immutable.upload import FileHandle
|
from allmydata.immutable.upload import FileHandle
|
||||||
|
from allmydata.mutable.publish import MutableFileHandle
|
||||||
|
from allmydata.mutable.common import MODE_READ
|
||||||
from allmydata.util import log, base32
|
from allmydata.util import log, base32
|
||||||
|
|
||||||
from allmydata.web.common import text_plain, WebError, RenderMixin, \
|
from allmydata.web.common import text_plain, WebError, RenderMixin, \
|
||||||
boolean_of_arg, get_arg, should_create_intermediate_directories, \
|
boolean_of_arg, get_arg, should_create_intermediate_directories, \
|
||||||
MyExceptionHandler, parse_replace_arg
|
MyExceptionHandler, parse_replace_arg, parse_offset_arg, \
|
||||||
|
parse_mutable_type_arg
|
||||||
from allmydata.web.check_results import CheckResults, \
|
from allmydata.web.check_results import CheckResults, \
|
||||||
CheckAndRepairResults, LiteralCheckResults
|
CheckAndRepairResults, LiteralCheckResults
|
||||||
from allmydata.web.info import MoreInfo
|
from allmydata.web.info import MoreInfo
|
||||||
@ -23,9 +26,13 @@ class ReplaceMeMixin:
|
|||||||
# a new file is being uploaded in our place.
|
# a new file is being uploaded in our place.
|
||||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
||||||
if mutable:
|
if mutable:
|
||||||
req.content.seek(0)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
data = req.content.read()
|
mutable_type = parse_mutable_type_arg(arg)
|
||||||
d = client.create_mutable_file(data)
|
if mutable_type is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
|
||||||
|
|
||||||
|
data = MutableFileHandle(req.content)
|
||||||
|
d = client.create_mutable_file(data, version=mutable_type)
|
||||||
def _uploaded(newnode):
|
def _uploaded(newnode):
|
||||||
d2 = self.parentnode.set_node(self.name, newnode,
|
d2 = self.parentnode.set_node(self.name, newnode,
|
||||||
overwrite=replace)
|
overwrite=replace)
|
||||||
@ -58,21 +65,20 @@ class ReplaceMeMixin:
|
|||||||
d.addCallback(lambda res: childnode.get_uri())
|
d.addCallback(lambda res: childnode.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def _read_data_from_formpost(self, req):
|
|
||||||
# SDMF: files are small, and we can only upload data, so we read
|
|
||||||
# the whole file into memory before uploading.
|
|
||||||
contents = req.fields["file"]
|
|
||||||
contents.file.seek(0)
|
|
||||||
data = contents.file.read()
|
|
||||||
return data
|
|
||||||
|
|
||||||
def replace_me_with_a_formpost(self, req, client, replace):
|
def replace_me_with_a_formpost(self, req, client, replace):
|
||||||
# create a new file, maybe mutable, maybe immutable
|
# create a new file, maybe mutable, maybe immutable
|
||||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
||||||
|
|
||||||
|
# create an immutable file
|
||||||
|
contents = req.fields["file"]
|
||||||
if mutable:
|
if mutable:
|
||||||
data = self._read_data_from_formpost(req)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
d = client.create_mutable_file(data)
|
mutable_type = parse_mutable_type_arg(arg)
|
||||||
|
if mutable_type is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
|
||||||
|
uploadable = MutableFileHandle(contents.file)
|
||||||
|
d = client.create_mutable_file(uploadable, version=mutable_type)
|
||||||
def _uploaded(newnode):
|
def _uploaded(newnode):
|
||||||
d2 = self.parentnode.set_node(self.name, newnode,
|
d2 = self.parentnode.set_node(self.name, newnode,
|
||||||
overwrite=replace)
|
overwrite=replace)
|
||||||
@ -80,13 +86,13 @@ class ReplaceMeMixin:
|
|||||||
return d2
|
return d2
|
||||||
d.addCallback(_uploaded)
|
d.addCallback(_uploaded)
|
||||||
return d
|
return d
|
||||||
# create an immutable file
|
|
||||||
contents = req.fields["file"]
|
|
||||||
uploadable = FileHandle(contents.file, convergence=client.convergence)
|
uploadable = FileHandle(contents.file, convergence=client.convergence)
|
||||||
d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
|
d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
|
||||||
d.addCallback(lambda newnode: newnode.get_uri())
|
d.addCallback(lambda newnode: newnode.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||||
def __init__(self, client, parentnode, name):
|
def __init__(self, client, parentnode, name):
|
||||||
rend.Page.__init__(self)
|
rend.Page.__init__(self)
|
||||||
@ -169,18 +175,27 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
# properly. So we assume that at least the browser will agree
|
# properly. So we assume that at least the browser will agree
|
||||||
# with itself, and echo back the same bytes that we were given.
|
# with itself, and echo back the same bytes that we were given.
|
||||||
filename = get_arg(req, "filename", self.name) or "unknown"
|
filename = get_arg(req, "filename", self.name) or "unknown"
|
||||||
if self.node.is_mutable():
|
d = self.node.get_best_readable_version()
|
||||||
# some day: d = self.node.get_best_version()
|
|
||||||
d = makeMutableDownloadable(self.node)
|
|
||||||
else:
|
|
||||||
d = defer.succeed(self.node)
|
|
||||||
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
||||||
return d
|
return d
|
||||||
if t == "json":
|
if t == "json":
|
||||||
if self.parentnode and self.name:
|
# We do this to make sure that fields like size and
|
||||||
d = self.parentnode.get_metadata_for(self.name)
|
# mutable-type (which depend on the file on the grid and not
|
||||||
|
# just on the cap) are filled in. The latter gets used in
|
||||||
|
# tests, in particular.
|
||||||
|
#
|
||||||
|
# TODO: Make it so that the servermap knows how to update in
|
||||||
|
# a mode specifically designed to fill in these fields, and
|
||||||
|
# then update it in that mode.
|
||||||
|
if self.node.is_mutable():
|
||||||
|
d = self.node.get_servermap(MODE_READ)
|
||||||
else:
|
else:
|
||||||
d = defer.succeed(None)
|
d = defer.succeed(None)
|
||||||
|
if self.parentnode and self.name:
|
||||||
|
d.addCallback(lambda ignored:
|
||||||
|
self.parentnode.get_metadata_for(self.name))
|
||||||
|
else:
|
||||||
|
d.addCallback(lambda ignored: None)
|
||||||
d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
|
d.addCallback(lambda md: FileJSONMetadata(ctx, self.node, md))
|
||||||
return d
|
return d
|
||||||
if t == "info":
|
if t == "info":
|
||||||
@ -197,11 +212,7 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
if t:
|
if t:
|
||||||
raise WebError("GET file: bad t=%s" % t)
|
raise WebError("GET file: bad t=%s" % t)
|
||||||
filename = get_arg(req, "filename", self.name) or "unknown"
|
filename = get_arg(req, "filename", self.name) or "unknown"
|
||||||
if self.node.is_mutable():
|
d = self.node.get_best_readable_version()
|
||||||
# some day: d = self.node.get_best_version()
|
|
||||||
d = makeMutableDownloadable(self.node)
|
|
||||||
else:
|
|
||||||
d = defer.succeed(self.node)
|
|
||||||
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -209,17 +220,37 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
req = IRequest(ctx)
|
req = IRequest(ctx)
|
||||||
t = get_arg(req, "t", "").strip()
|
t = get_arg(req, "t", "").strip()
|
||||||
replace = parse_replace_arg(get_arg(req, "replace", "true"))
|
replace = parse_replace_arg(get_arg(req, "replace", "true"))
|
||||||
|
offset = parse_offset_arg(get_arg(req, "offset", None))
|
||||||
|
|
||||||
if not t:
|
if not t:
|
||||||
if self.node.is_mutable():
|
|
||||||
return self.replace_my_contents(req)
|
|
||||||
if not replace:
|
if not replace:
|
||||||
# this is the early trap: if someone else modifies the
|
# this is the early trap: if someone else modifies the
|
||||||
# directory while we're uploading, the add_file(overwrite=)
|
# directory while we're uploading, the add_file(overwrite=)
|
||||||
# call in replace_me_with_a_child will do the late trap.
|
# call in replace_me_with_a_child will do the late trap.
|
||||||
raise ExistingChildError()
|
raise ExistingChildError()
|
||||||
assert self.parentnode and self.name
|
|
||||||
return self.replace_me_with_a_child(req, self.client, replace)
|
if self.node.is_mutable():
|
||||||
|
# Are we a readonly filenode? We shouldn't allow callers
|
||||||
|
# to try to replace us if we are.
|
||||||
|
if self.node.is_readonly():
|
||||||
|
raise WebError("PUT to a mutable file: replace or update"
|
||||||
|
" requested with read-only cap")
|
||||||
|
if offset is None:
|
||||||
|
return self.replace_my_contents(req)
|
||||||
|
|
||||||
|
if offset >= 0:
|
||||||
|
return self.update_my_contents(req, offset)
|
||||||
|
|
||||||
|
raise WebError("PUT to a mutable file: Invalid offset")
|
||||||
|
|
||||||
|
else:
|
||||||
|
if offset is not None:
|
||||||
|
raise WebError("PUT to a file: append operation invoked "
|
||||||
|
"on an immutable cap")
|
||||||
|
|
||||||
|
assert self.parentnode and self.name
|
||||||
|
return self.replace_me_with_a_child(req, self.client, replace)
|
||||||
|
|
||||||
if t == "uri":
|
if t == "uri":
|
||||||
if not replace:
|
if not replace:
|
||||||
raise ExistingChildError()
|
raise ExistingChildError()
|
||||||
@ -280,46 +311,34 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
|||||||
|
|
||||||
def replace_my_contents(self, req):
|
def replace_my_contents(self, req):
|
||||||
req.content.seek(0)
|
req.content.seek(0)
|
||||||
new_contents = req.content.read()
|
new_contents = MutableFileHandle(req.content)
|
||||||
d = self.node.overwrite(new_contents)
|
d = self.node.overwrite(new_contents)
|
||||||
d.addCallback(lambda res: self.node.get_uri())
|
d.addCallback(lambda res: self.node.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def update_my_contents(self, req, offset):
|
||||||
|
req.content.seek(0)
|
||||||
|
added_contents = MutableFileHandle(req.content)
|
||||||
|
|
||||||
|
d = self.node.get_best_mutable_version()
|
||||||
|
d.addCallback(lambda mv:
|
||||||
|
mv.update(added_contents, offset))
|
||||||
|
d.addCallback(lambda ignored:
|
||||||
|
self.node.get_uri())
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def replace_my_contents_with_a_formpost(self, req):
|
def replace_my_contents_with_a_formpost(self, req):
|
||||||
# we have a mutable file. Get the data from the formpost, and replace
|
# we have a mutable file. Get the data from the formpost, and replace
|
||||||
# the mutable file's contents with it.
|
# the mutable file's contents with it.
|
||||||
new_contents = self._read_data_from_formpost(req)
|
new_contents = req.fields['file']
|
||||||
|
new_contents = MutableFileHandle(new_contents.file)
|
||||||
|
|
||||||
d = self.node.overwrite(new_contents)
|
d = self.node.overwrite(new_contents)
|
||||||
d.addCallback(lambda res: self.node.get_uri())
|
d.addCallback(lambda res: self.node.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
class MutableDownloadable:
|
|
||||||
#implements(IDownloadable)
|
|
||||||
def __init__(self, size, node):
|
|
||||||
self.size = size
|
|
||||||
self.node = node
|
|
||||||
def get_size(self):
|
|
||||||
return self.size
|
|
||||||
def is_mutable(self):
|
|
||||||
return True
|
|
||||||
def read(self, consumer, offset=0, size=None):
|
|
||||||
d = self.node.download_best_version()
|
|
||||||
d.addCallback(self._got_data, consumer, offset, size)
|
|
||||||
return d
|
|
||||||
def _got_data(self, contents, consumer, offset, size):
|
|
||||||
start = offset
|
|
||||||
if size is not None:
|
|
||||||
end = offset+size
|
|
||||||
else:
|
|
||||||
end = self.size
|
|
||||||
# SDMF: we can write the whole file in one big chunk
|
|
||||||
consumer.write(contents[start:end])
|
|
||||||
return consumer
|
|
||||||
|
|
||||||
def makeMutableDownloadable(n):
|
|
||||||
d = defer.maybeDeferred(n.get_size_of_best_version)
|
|
||||||
d.addCallback(MutableDownloadable, n)
|
|
||||||
return d
|
|
||||||
|
|
||||||
class FileDownloader(rend.Page):
|
class FileDownloader(rend.Page):
|
||||||
def __init__(self, filenode, filename):
|
def __init__(self, filenode, filename):
|
||||||
@ -494,6 +513,16 @@ def FileJSONMetadata(ctx, filenode, edge_metadata):
|
|||||||
data[1]['mutable'] = filenode.is_mutable()
|
data[1]['mutable'] = filenode.is_mutable()
|
||||||
if edge_metadata is not None:
|
if edge_metadata is not None:
|
||||||
data[1]['metadata'] = edge_metadata
|
data[1]['metadata'] = edge_metadata
|
||||||
|
|
||||||
|
if filenode.is_mutable() and filenode.get_version() is not None:
|
||||||
|
mutable_type = filenode.get_version()
|
||||||
|
assert mutable_type in (MDMF_VERSION, SDMF_VERSION)
|
||||||
|
if mutable_type == MDMF_VERSION:
|
||||||
|
mutable_type = "mdmf"
|
||||||
|
else:
|
||||||
|
mutable_type = "sdmf"
|
||||||
|
data[1]['mutable-type'] = mutable_type
|
||||||
|
|
||||||
return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
|
return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx)
|
||||||
|
|
||||||
def FileURI(ctx, filenode):
|
def FileURI(ctx, filenode):
|
||||||
|
@ -5,7 +5,7 @@ from nevow import rend, tags as T
|
|||||||
from nevow.inevow import IRequest
|
from nevow.inevow import IRequest
|
||||||
|
|
||||||
from allmydata.util import base32
|
from allmydata.util import base32
|
||||||
from allmydata.interfaces import IDirectoryNode, IFileNode
|
from allmydata.interfaces import IDirectoryNode, IFileNode, MDMF_VERSION
|
||||||
from allmydata.web.common import getxmlfile
|
from allmydata.web.common import getxmlfile
|
||||||
from allmydata.mutable.common import UnrecoverableFileError # TODO: move
|
from allmydata.mutable.common import UnrecoverableFileError # TODO: move
|
||||||
|
|
||||||
@ -28,7 +28,12 @@ class MoreInfo(rend.Page):
|
|||||||
si = node.get_storage_index()
|
si = node.get_storage_index()
|
||||||
if si:
|
if si:
|
||||||
if node.is_mutable():
|
if node.is_mutable():
|
||||||
return "mutable file"
|
ret = "mutable file"
|
||||||
|
if node.get_version() == MDMF_VERSION:
|
||||||
|
ret += " (mdmf)"
|
||||||
|
else:
|
||||||
|
ret += " (sdmf)"
|
||||||
|
return ret
|
||||||
return "immutable file"
|
return "immutable file"
|
||||||
return "immutable LIT file"
|
return "immutable LIT file"
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
@ -12,11 +12,11 @@ import allmydata # to display import path
|
|||||||
from allmydata import get_package_versions_string
|
from allmydata import get_package_versions_string
|
||||||
from allmydata import provisioning
|
from allmydata import provisioning
|
||||||
from allmydata.util import idlib, log
|
from allmydata.util import idlib, log
|
||||||
from allmydata.interfaces import IFileNode
|
from allmydata.interfaces import IFileNode, MDMF_VERSION, SDMF_VERSION
|
||||||
from allmydata.web import filenode, directory, unlinked, status, operations
|
from allmydata.web import filenode, directory, unlinked, status, operations
|
||||||
from allmydata.web import reliability, storage
|
from allmydata.web import reliability, storage
|
||||||
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
|
from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
|
||||||
get_arg, RenderMixin, boolean_of_arg
|
get_arg, RenderMixin, boolean_of_arg, parse_mutable_type_arg
|
||||||
|
|
||||||
|
|
||||||
class URIHandler(RenderMixin, rend.Page):
|
class URIHandler(RenderMixin, rend.Page):
|
||||||
@ -47,7 +47,13 @@ class URIHandler(RenderMixin, rend.Page):
|
|||||||
if t == "":
|
if t == "":
|
||||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
|
mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
|
||||||
if mutable:
|
if mutable:
|
||||||
return unlinked.PUTUnlinkedSSK(req, self.client)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
version = parse_mutable_type_arg(arg)
|
||||||
|
if version == "invalid":
|
||||||
|
errmsg = "Unknown type: %s" % arg
|
||||||
|
raise WebError(errmsg, http.BAD_REQUEST)
|
||||||
|
|
||||||
|
return unlinked.PUTUnlinkedSSK(req, self.client, version)
|
||||||
else:
|
else:
|
||||||
return unlinked.PUTUnlinkedCHK(req, self.client)
|
return unlinked.PUTUnlinkedCHK(req, self.client)
|
||||||
if t == "mkdir":
|
if t == "mkdir":
|
||||||
@ -65,7 +71,11 @@ class URIHandler(RenderMixin, rend.Page):
|
|||||||
if t in ("", "upload"):
|
if t in ("", "upload"):
|
||||||
mutable = bool(get_arg(req, "mutable", "").strip())
|
mutable = bool(get_arg(req, "mutable", "").strip())
|
||||||
if mutable:
|
if mutable:
|
||||||
return unlinked.POSTUnlinkedSSK(req, self.client)
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
version = parse_mutable_type_arg(arg)
|
||||||
|
if version is "invalid":
|
||||||
|
raise WebError("Unknown type: %s" % arg, http.BAD_REQUEST)
|
||||||
|
return unlinked.POSTUnlinkedSSK(req, self.client, version)
|
||||||
else:
|
else:
|
||||||
return unlinked.POSTUnlinkedCHK(req, self.client)
|
return unlinked.POSTUnlinkedCHK(req, self.client)
|
||||||
if t == "mkdir":
|
if t == "mkdir":
|
||||||
@ -322,6 +332,30 @@ class Root(rend.Page):
|
|||||||
|
|
||||||
def render_upload_form(self, ctx, data):
|
def render_upload_form(self, ctx, data):
|
||||||
# this is a form where users can upload unlinked files
|
# this is a form where users can upload unlinked files
|
||||||
|
#
|
||||||
|
# for mutable files, users can choose the format by selecting
|
||||||
|
# MDMF or SDMF from a radio button. They can also configure a
|
||||||
|
# default format in tahoe.cfg, which they rightly expect us to
|
||||||
|
# obey. we convey to them that we are obeying their choice by
|
||||||
|
# ensuring that the one that they've chosen is selected in the
|
||||||
|
# interface.
|
||||||
|
if self.client.mutable_file_default == MDMF_VERSION:
|
||||||
|
mdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='mdmf', id='mutable-type-mdmf',
|
||||||
|
checked='checked')
|
||||||
|
else:
|
||||||
|
mdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='mdmf', id='mutable-type-mdmf')
|
||||||
|
|
||||||
|
if self.client.mutable_file_default == SDMF_VERSION:
|
||||||
|
sdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='sdmf', id='mutable-type-sdmf',
|
||||||
|
checked='checked')
|
||||||
|
else:
|
||||||
|
sdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='sdmf', id='mutable-type-sdmf')
|
||||||
|
|
||||||
|
|
||||||
form = T.form(action="uri", method="post",
|
form = T.form(action="uri", method="post",
|
||||||
enctype="multipart/form-data")[
|
enctype="multipart/form-data")[
|
||||||
T.fieldset[
|
T.fieldset[
|
||||||
@ -330,16 +364,28 @@ class Root(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="hidden", name="t", value="upload"),
|
T.input(type="hidden", name="t", value="upload"),
|
||||||
T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
|
T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
|
||||||
|
sdmf_input, T.label(for_="mutable-type-sdmf")["SDMF"],
|
||||||
|
mdmf_input,
|
||||||
|
T.label(for_='mutable-type-mdmf')['MDMF (experimental)'],
|
||||||
" ", T.input(type="submit", value="Upload!")],
|
" ", T.input(type="submit", value="Upload!")],
|
||||||
]]
|
]]
|
||||||
return T.div[form]
|
return T.div[form]
|
||||||
|
|
||||||
def render_mkdir_form(self, ctx, data):
|
def render_mkdir_form(self, ctx, data):
|
||||||
# this is a form where users can create new directories
|
# this is a form where users can create new directories
|
||||||
|
mdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='mdmf', id='mutable-directory-mdmf')
|
||||||
|
sdmf_input = T.input(type='radio', name='mutable-type',
|
||||||
|
value='sdmf', id='mutable-directory-sdmf',
|
||||||
|
checked='checked')
|
||||||
form = T.form(action="uri", method="post",
|
form = T.form(action="uri", method="post",
|
||||||
enctype="multipart/form-data")[
|
enctype="multipart/form-data")[
|
||||||
T.fieldset[
|
T.fieldset[
|
||||||
T.legend(class_="freeform-form-label")["Create a directory"],
|
T.legend(class_="freeform-form-label")["Create a directory"],
|
||||||
|
T.label(for_='mutable-directory-sdmf')["SDMF"],
|
||||||
|
sdmf_input,
|
||||||
|
T.label(for_='mutable-directory-mdmf')["MDMF"],
|
||||||
|
mdmf_input,
|
||||||
T.input(type="hidden", name="t", value="mkdir"),
|
T.input(type="hidden", name="t", value="mkdir"),
|
||||||
T.input(type="hidden", name="redirect_to_result", value="true"),
|
T.input(type="hidden", name="redirect_to_result", value="true"),
|
||||||
T.input(type="submit", value="Create a directory"),
|
T.input(type="submit", value="Create a directory"),
|
||||||
|
@ -4,8 +4,9 @@ from twisted.web import http
|
|||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from nevow import rend, url, tags as T
|
from nevow import rend, url, tags as T
|
||||||
from allmydata.immutable.upload import FileHandle
|
from allmydata.immutable.upload import FileHandle
|
||||||
|
from allmydata.mutable.publish import MutableFileHandle
|
||||||
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
|
from allmydata.web.common import getxmlfile, get_arg, boolean_of_arg, \
|
||||||
convert_children_json, WebError
|
convert_children_json, WebError, parse_mutable_type_arg
|
||||||
from allmydata.web import status
|
from allmydata.web import status
|
||||||
|
|
||||||
def PUTUnlinkedCHK(req, client):
|
def PUTUnlinkedCHK(req, client):
|
||||||
@ -16,17 +17,25 @@ def PUTUnlinkedCHK(req, client):
|
|||||||
# that fires with the URI of the new file
|
# that fires with the URI of the new file
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def PUTUnlinkedSSK(req, client):
|
def PUTUnlinkedSSK(req, client, version):
|
||||||
# SDMF: files are small, and we can only upload data
|
# SDMF: files are small, and we can only upload data
|
||||||
req.content.seek(0)
|
req.content.seek(0)
|
||||||
data = req.content.read()
|
data = MutableFileHandle(req.content)
|
||||||
d = client.create_mutable_file(data)
|
d = client.create_mutable_file(data, version=version)
|
||||||
d.addCallback(lambda n: n.get_uri())
|
d.addCallback(lambda n: n.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def PUTUnlinkedCreateDirectory(req, client):
|
def PUTUnlinkedCreateDirectory(req, client):
|
||||||
# "PUT /uri?t=mkdir", to create an unlinked directory.
|
# "PUT /uri?t=mkdir", to create an unlinked directory.
|
||||||
d = client.create_dirnode()
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
mt = parse_mutable_type_arg(arg)
|
||||||
|
if mt is not None and mt is not "invalid":
|
||||||
|
d = client.create_dirnode(version=mt)
|
||||||
|
elif mt is "invalid":
|
||||||
|
msg = "Unknown type: %s" % arg
|
||||||
|
raise WebError(msg, http.BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
d = client.create_dirnode()
|
||||||
d.addCallback(lambda dirnode: dirnode.get_uri())
|
d.addCallback(lambda dirnode: dirnode.get_uri())
|
||||||
# XXX add redirect_to_result
|
# XXX add redirect_to_result
|
||||||
return d
|
return d
|
||||||
@ -79,13 +88,12 @@ class UploadResultsPage(status.UploadResultsRendererMixin, rend.Page):
|
|||||||
["/uri/" + res.uri])
|
["/uri/" + res.uri])
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def POSTUnlinkedSSK(req, client):
|
def POSTUnlinkedSSK(req, client, version):
|
||||||
# "POST /uri", to create an unlinked file.
|
# "POST /uri", to create an unlinked file.
|
||||||
# SDMF: files are small, and we can only upload data
|
# SDMF: files are small, and we can only upload data
|
||||||
contents = req.fields["file"]
|
contents = req.fields["file"].file
|
||||||
contents.file.seek(0)
|
data = MutableFileHandle(contents)
|
||||||
data = contents.file.read()
|
d = client.create_mutable_file(data, version=version)
|
||||||
d = client.create_mutable_file(data)
|
|
||||||
d.addCallback(lambda n: n.get_uri())
|
d.addCallback(lambda n: n.get_uri())
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -104,7 +112,15 @@ def POSTUnlinkedCreateDirectory(req, client):
|
|||||||
raise WebError("t=mkdir does not accept children=, "
|
raise WebError("t=mkdir does not accept children=, "
|
||||||
"try t=mkdir-with-children instead",
|
"try t=mkdir-with-children instead",
|
||||||
http.BAD_REQUEST)
|
http.BAD_REQUEST)
|
||||||
d = client.create_dirnode()
|
arg = get_arg(req, "mutable-type", None)
|
||||||
|
mt = parse_mutable_type_arg(arg)
|
||||||
|
if mt is not None and mt is not "invalid":
|
||||||
|
d = client.create_dirnode(version=mt)
|
||||||
|
elif mt is "invalid":
|
||||||
|
msg = "Unknown type: %s" % arg
|
||||||
|
raise WebError(msg, http.BAD_REQUEST)
|
||||||
|
else:
|
||||||
|
d = client.create_dirnode()
|
||||||
redirect = get_arg(req, "redirect_to_result", "false")
|
redirect = get_arg(req, "redirect_to_result", "false")
|
||||||
if boolean_of_arg(redirect):
|
if boolean_of_arg(redirect):
|
||||||
def _then_redir(res):
|
def _then_redir(res):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user