mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-19 11:16:24 +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, \
|
||||
FileTooLargeError, NotEnoughSharesError, NoSharesError, \
|
||||
EmptyPathnameComponentError, MustBeDeepImmutableError, \
|
||||
MustBeReadonlyError, MustNotBeUnknownRWError
|
||||
MustBeReadonlyError, MustNotBeUnknownRWError, SDMF_VERSION, MDMF_VERSION
|
||||
from allmydata.mutable.common import UnrecoverableFileError
|
||||
from allmydata.util import abbreviate
|
||||
from allmydata.util.encodingutil import to_str, quote_output
|
||||
@ -32,6 +32,32 @@ def parse_replace_arg(replace):
|
||||
else:
|
||||
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):
|
||||
req = IRequest(ctx_or_req)
|
||||
# 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.interfaces import IDirectoryNode, IFileNode, IFilesystemNode, \
|
||||
IImmutableFileNode, IMutableFileNode, ExistingChildError, \
|
||||
NoSuchChildError, EmptyPathnameComponentError
|
||||
NoSuchChildError, EmptyPathnameComponentError, SDMF_VERSION, MDMF_VERSION
|
||||
from allmydata.monitor import Monitor, OperationCancelledError
|
||||
from allmydata import dirnode
|
||||
from allmydata.web.common import text_plain, WebError, \
|
||||
IOpHandleTable, NeedOperationHandleError, \
|
||||
boolean_of_arg, get_arg, get_root, parse_replace_arg, \
|
||||
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, \
|
||||
FileNodeHandler, PlaceHolderNodeHandler
|
||||
from allmydata.web.check_results import CheckResults, \
|
||||
@ -108,8 +109,17 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
mutable = True
|
||||
if t == "mkdir-immutable":
|
||||
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,
|
||||
mutable=mutable)
|
||||
mutable=mutable,
|
||||
mutable_version=mt)
|
||||
d.addCallback(make_handler_for,
|
||||
self.client, self.node, name)
|
||||
return d
|
||||
@ -150,7 +160,8 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
if not t:
|
||||
# render the directory as HTML, using the docFactory and Nevow's
|
||||
# whole templating thing.
|
||||
return DirectoryAsHTML(self.node)
|
||||
return DirectoryAsHTML(self.node,
|
||||
self.client.mutable_file_default)
|
||||
|
||||
if t == "json":
|
||||
return DirectoryJSONMetadata(ctx, self.node)
|
||||
@ -239,7 +250,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
name = name.decode("utf-8")
|
||||
replace = boolean_of_arg(get_arg(req, "replace", "true"))
|
||||
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
|
||||
return d
|
||||
|
||||
@ -255,7 +274,15 @@ class DirectoryNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
req.content.seek(0)
|
||||
kids_json = req.content.read()
|
||||
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
|
||||
return d
|
||||
|
||||
@ -552,10 +579,13 @@ class DirectoryAsHTML(rend.Page):
|
||||
docFactory = getxmlfile("directory.xhtml")
|
||||
addSlash = True
|
||||
|
||||
def __init__(self, node):
|
||||
def __init__(self, node, default_mutable_format):
|
||||
rend.Page.__init__(self)
|
||||
self.node = node
|
||||
|
||||
assert default_mutable_format in (MDMF_VERSION, SDMF_VERSION)
|
||||
self.default_mutable_format = default_mutable_format
|
||||
|
||||
def beforeRender(self, ctx):
|
||||
# attempt to get the dirnode's children, stashing them (or the
|
||||
# failure that results) for later use
|
||||
@ -753,6 +783,7 @@ class DirectoryAsHTML(rend.Page):
|
||||
|
||||
return ctx.tag
|
||||
|
||||
# XXX: Duplicated from root.py.
|
||||
def render_forms(self, ctx, data):
|
||||
forms = []
|
||||
|
||||
@ -761,6 +792,12 @@ class DirectoryAsHTML(rend.Page):
|
||||
if self.dirnode_children is None:
|
||||
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",
|
||||
enctype="multipart/form-data")[
|
||||
T.fieldset[
|
||||
@ -769,10 +806,34 @@ class DirectoryAsHTML(rend.Page):
|
||||
T.legend(class_="freeform-form-label")["Create a new directory in this directory"],
|
||||
"New directory 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"),
|
||||
]]
|
||||
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",
|
||||
enctype="multipart/form-data")[
|
||||
T.fieldset[
|
||||
@ -785,6 +846,9 @@ class DirectoryAsHTML(rend.Page):
|
||||
T.input(type="submit", value="Upload"),
|
||||
" 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])
|
||||
|
||||
@ -820,6 +884,17 @@ def DirectoryJSONMetadata(ctx, dirnode):
|
||||
kiddata = ("filenode", {'size': childnode.get_size(),
|
||||
'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):
|
||||
kiddata = ("dirnode", {'mutable': childnode.is_mutable()})
|
||||
else:
|
||||
|
@ -6,14 +6,17 @@ from twisted.internet import defer
|
||||
from nevow import url, rend
|
||||
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.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.web.common import text_plain, WebError, RenderMixin, \
|
||||
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, \
|
||||
CheckAndRepairResults, LiteralCheckResults
|
||||
from allmydata.web.info import MoreInfo
|
||||
@ -23,9 +26,13 @@ class ReplaceMeMixin:
|
||||
# a new file is being uploaded in our place.
|
||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
||||
if mutable:
|
||||
req.content.seek(0)
|
||||
data = req.content.read()
|
||||
d = client.create_mutable_file(data)
|
||||
arg = get_arg(req, "mutable-type", None)
|
||||
mutable_type = parse_mutable_type_arg(arg)
|
||||
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):
|
||||
d2 = self.parentnode.set_node(self.name, newnode,
|
||||
overwrite=replace)
|
||||
@ -58,21 +65,20 @@ class ReplaceMeMixin:
|
||||
d.addCallback(lambda res: childnode.get_uri())
|
||||
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):
|
||||
# create a new file, maybe mutable, maybe immutable
|
||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false"))
|
||||
|
||||
# create an immutable file
|
||||
contents = req.fields["file"]
|
||||
if mutable:
|
||||
data = self._read_data_from_formpost(req)
|
||||
d = client.create_mutable_file(data)
|
||||
arg = get_arg(req, "mutable-type", None)
|
||||
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):
|
||||
d2 = self.parentnode.set_node(self.name, newnode,
|
||||
overwrite=replace)
|
||||
@ -80,13 +86,13 @@ class ReplaceMeMixin:
|
||||
return d2
|
||||
d.addCallback(_uploaded)
|
||||
return d
|
||||
# create an immutable file
|
||||
contents = req.fields["file"]
|
||||
|
||||
uploadable = FileHandle(contents.file, convergence=client.convergence)
|
||||
d = self.parentnode.add_file(self.name, uploadable, overwrite=replace)
|
||||
d.addCallback(lambda newnode: newnode.get_uri())
|
||||
return d
|
||||
|
||||
|
||||
class PlaceHolderNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
def __init__(self, client, parentnode, name):
|
||||
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
|
||||
# with itself, and echo back the same bytes that we were given.
|
||||
filename = get_arg(req, "filename", self.name) or "unknown"
|
||||
if self.node.is_mutable():
|
||||
# some day: d = self.node.get_best_version()
|
||||
d = makeMutableDownloadable(self.node)
|
||||
else:
|
||||
d = defer.succeed(self.node)
|
||||
d = self.node.get_best_readable_version()
|
||||
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
||||
return d
|
||||
if t == "json":
|
||||
if self.parentnode and self.name:
|
||||
d = self.parentnode.get_metadata_for(self.name)
|
||||
# We do this to make sure that fields like size and
|
||||
# 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:
|
||||
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))
|
||||
return d
|
||||
if t == "info":
|
||||
@ -197,11 +212,7 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
if t:
|
||||
raise WebError("GET file: bad t=%s" % t)
|
||||
filename = get_arg(req, "filename", self.name) or "unknown"
|
||||
if self.node.is_mutable():
|
||||
# some day: d = self.node.get_best_version()
|
||||
d = makeMutableDownloadable(self.node)
|
||||
else:
|
||||
d = defer.succeed(self.node)
|
||||
d = self.node.get_best_readable_version()
|
||||
d.addCallback(lambda dn: FileDownloader(dn, filename))
|
||||
return d
|
||||
|
||||
@ -209,17 +220,37 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
req = IRequest(ctx)
|
||||
t = get_arg(req, "t", "").strip()
|
||||
replace = parse_replace_arg(get_arg(req, "replace", "true"))
|
||||
offset = parse_offset_arg(get_arg(req, "offset", None))
|
||||
|
||||
if not t:
|
||||
if self.node.is_mutable():
|
||||
return self.replace_my_contents(req)
|
||||
if not replace:
|
||||
# this is the early trap: if someone else modifies the
|
||||
# directory while we're uploading, the add_file(overwrite=)
|
||||
# call in replace_me_with_a_child will do the late trap.
|
||||
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 not replace:
|
||||
raise ExistingChildError()
|
||||
@ -280,46 +311,34 @@ class FileNodeHandler(RenderMixin, rend.Page, ReplaceMeMixin):
|
||||
|
||||
def replace_my_contents(self, req):
|
||||
req.content.seek(0)
|
||||
new_contents = req.content.read()
|
||||
new_contents = MutableFileHandle(req.content)
|
||||
d = self.node.overwrite(new_contents)
|
||||
d.addCallback(lambda res: self.node.get_uri())
|
||||
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):
|
||||
# we have a mutable file. Get the data from the formpost, and replace
|
||||
# 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.addCallback(lambda res: self.node.get_uri())
|
||||
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):
|
||||
def __init__(self, filenode, filename):
|
||||
@ -494,6 +513,16 @@ def FileJSONMetadata(ctx, filenode, edge_metadata):
|
||||
data[1]['mutable'] = filenode.is_mutable()
|
||||
if edge_metadata is not None:
|
||||
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)
|
||||
|
||||
def FileURI(ctx, filenode):
|
||||
|
@ -5,7 +5,7 @@ from nevow import rend, tags as T
|
||||
from nevow.inevow import IRequest
|
||||
|
||||
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.mutable.common import UnrecoverableFileError # TODO: move
|
||||
|
||||
@ -28,7 +28,12 @@ class MoreInfo(rend.Page):
|
||||
si = node.get_storage_index()
|
||||
if si:
|
||||
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 LIT file"
|
||||
return "unknown"
|
||||
|
@ -12,11 +12,11 @@ import allmydata # to display import path
|
||||
from allmydata import get_package_versions_string
|
||||
from allmydata import provisioning
|
||||
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 reliability, storage
|
||||
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):
|
||||
@ -47,7 +47,13 @@ class URIHandler(RenderMixin, rend.Page):
|
||||
if t == "":
|
||||
mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
|
||||
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:
|
||||
return unlinked.PUTUnlinkedCHK(req, self.client)
|
||||
if t == "mkdir":
|
||||
@ -65,7 +71,11 @@ class URIHandler(RenderMixin, rend.Page):
|
||||
if t in ("", "upload"):
|
||||
mutable = bool(get_arg(req, "mutable", "").strip())
|
||||
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:
|
||||
return unlinked.POSTUnlinkedCHK(req, self.client)
|
||||
if t == "mkdir":
|
||||
@ -322,6 +332,30 @@ class Root(rend.Page):
|
||||
|
||||
def render_upload_form(self, ctx, data):
|
||||
# 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",
|
||||
enctype="multipart/form-data")[
|
||||
T.fieldset[
|
||||
@ -330,16 +364,28 @@ class Root(rend.Page):
|
||||
T.input(type="file", name="file", class_="freeform-input-file")],
|
||||
T.input(type="hidden", name="t", value="upload"),
|
||||
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!")],
|
||||
]]
|
||||
return T.div[form]
|
||||
|
||||
def render_mkdir_form(self, ctx, data):
|
||||
# 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",
|
||||
enctype="multipart/form-data")[
|
||||
T.fieldset[
|
||||
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="redirect_to_result", value="true"),
|
||||
T.input(type="submit", value="Create a directory"),
|
||||
|
@ -4,8 +4,9 @@ from twisted.web import http
|
||||
from twisted.internet import defer
|
||||
from nevow import rend, url, tags as T
|
||||
from allmydata.immutable.upload import FileHandle
|
||||
from allmydata.mutable.publish import MutableFileHandle
|
||||
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
|
||||
|
||||
def PUTUnlinkedCHK(req, client):
|
||||
@ -16,17 +17,25 @@ def PUTUnlinkedCHK(req, client):
|
||||
# that fires with the URI of the new file
|
||||
return d
|
||||
|
||||
def PUTUnlinkedSSK(req, client):
|
||||
def PUTUnlinkedSSK(req, client, version):
|
||||
# SDMF: files are small, and we can only upload data
|
||||
req.content.seek(0)
|
||||
data = req.content.read()
|
||||
d = client.create_mutable_file(data)
|
||||
data = MutableFileHandle(req.content)
|
||||
d = client.create_mutable_file(data, version=version)
|
||||
d.addCallback(lambda n: n.get_uri())
|
||||
return d
|
||||
|
||||
def PUTUnlinkedCreateDirectory(req, client):
|
||||
# "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())
|
||||
# XXX add redirect_to_result
|
||||
return d
|
||||
@ -79,13 +88,12 @@ class UploadResultsPage(status.UploadResultsRendererMixin, rend.Page):
|
||||
["/uri/" + res.uri])
|
||||
return d
|
||||
|
||||
def POSTUnlinkedSSK(req, client):
|
||||
def POSTUnlinkedSSK(req, client, version):
|
||||
# "POST /uri", to create an unlinked file.
|
||||
# SDMF: files are small, and we can only upload data
|
||||
contents = req.fields["file"]
|
||||
contents.file.seek(0)
|
||||
data = contents.file.read()
|
||||
d = client.create_mutable_file(data)
|
||||
contents = req.fields["file"].file
|
||||
data = MutableFileHandle(contents)
|
||||
d = client.create_mutable_file(data, version=version)
|
||||
d.addCallback(lambda n: n.get_uri())
|
||||
return d
|
||||
|
||||
@ -104,7 +112,15 @@ def POSTUnlinkedCreateDirectory(req, client):
|
||||
raise WebError("t=mkdir does not accept children=, "
|
||||
"try t=mkdir-with-children instead",
|
||||
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")
|
||||
if boolean_of_arg(redirect):
|
||||
def _then_redir(res):
|
||||
|
Loading…
Reference in New Issue
Block a user