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, \ 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

View File

@ -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,6 +250,14 @@ 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 = {}
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 = 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,6 +274,14 @@ 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)
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 = 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:

View File

@ -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()
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 assert self.parentnode and self.name
return self.replace_me_with_a_child(req, self.client, replace) 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):

View File

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

View File

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

View File

@ -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,16 +17,24 @@ 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.
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 = 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
@ -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,6 +112,14 @@ 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)
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 = 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):