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:
Kevan Carstensen 2011-08-06 17:43:48 -07:00
parent bb10d685ed
commit a9cada2e03
7 changed files with 1165 additions and 155 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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"

View File

@ -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"),

View File

@ -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):