Commit 06704459 authored by Fabian-Andree Heidemann's avatar Fabian-Andree Heidemann
Browse files

[filebrowser] implemented chunked uploads for arbitrary size of file, fix #1664

[filebrowser] improved GUI for uploading: drag&drop, progress bar
[platform] added maximal request body size as vispa.args.global.max_request_size
parent e0bddfee
......@@ -251,11 +251,30 @@ class FSAjaxController(AbstractController):
if not isinstance(parts, list):
parts = [parts]
# get the byterange if chunked
chunked = False
if 'Content-Range' in cherrypy.request.headers:
chunked = True
tmp = cherrypy.request.headers['Content-Range']
tmp = tmp.split()[1].split('/')
maxbytes = int(tmp[1])
tmp = tmp[0].split('-')
firstbyte = int(tmp[0])
lastbyte = int(tmp[1])
path = kwargs['path']
for part in parts:
filename = fs.handle_file_name_collision(part.filename, path)
append = False
force = True
if chunked:
filename = '~' + filename
if chunked and (firstbyte != 0):
# check correct size of previous chunks
for file in fs.get_file_list(path, encode_json=False)['filelist']:
if file['name'] == filename and file['size'] != firstbyte:
raise AjaxException("Chunked upload failed")
append = True
else:
append = False
while True:
data = part.file.read(1024 ** 2)
......@@ -263,7 +282,7 @@ class FSAjaxController(AbstractController):
break
success, msg = fs.save_file_content(filename, data,
path=path, force=force,
path=path, force=True,
append=append)
if not success:
raise AjaxException(msg)
......@@ -271,6 +290,9 @@ class FSAjaxController(AbstractController):
if not append:
append = True
if chunked and (lastbyte + 1 == maxbytes):
fs.rename(path, filename, filename.lstrip('~'))
@cherrypy.expose
@cherrypy.tools.method(accept="GET")
def isbrowserfile(self, path):
......
.file-rightclickmenu-itemhide{display:none;visibility:hidden}
.file-rightclickmenu-itemshow{display:block;visibility:visible}
.file-content{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none;position:absolute;height:100%;bottom:0;right:0;top:0;left:0;overflow:hidden}
.file-content{-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;-ms-user-select:none;user-select:none;position:absolute;height:100%;bottom:0;right:0;top:0;left:0;overflow:hidden}
.file-content>.alert{position:absolute;top:40px;width:100%}
.upload-progress-container{width:100%;position:absolute;bottom:0;padding:6px 12px}
.upload-progress-container>.progress-row>.btn{float:right;margin-left:12px}
.upload-progress-container>.progress-row{margin-bottom:-15px}
.upload-progress-container>.progress-row>.progress-title{float:left;margin-right:12px;display:inline-block;max-width:20%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.upload-progress-container>.progress-row>.btn{float:right;margin-left:12px}
.upload-progress-container>.progress-row>.glyphicon{float:right;margin-left:12px}
.upload-progress-container>.progress-row>.glyphicon:hover{cursor:pointer;color:#000}
.fileselector{height:300px}
.fileselector>.file-content>.alert{top:55px}
.fileselector .file-path-bar{position:absolute;top:3px}
......
......@@ -44,10 +44,7 @@
bottom: 0px;
padding: @padding-base-vertical @padding-base-horizontal;
> .progress-row {
> .btn {
float: right;
margin-left: @padding-base-horizontal;
}
margin-bottom: -15px;
> .progress-title {
float: left;
margin-right: @padding-base-horizontal;
......@@ -57,6 +54,18 @@
white-space: nowrap;
text-overflow: ellipsis;
}
> .btn {
float: right;
margin-left: @padding-base-horizontal;
}
> .glyphicon {
float: right;
margin-left: @padding-base-horizontal;
&:hover {
cursor: pointer;
color: #000;
}
}
}
}
......
......@@ -416,10 +416,10 @@ define(["jquery", "jclass"], function($, JClass) {
});
},
upload: function(path) {
upload: function(path, triggered) {
var self = this;
if (path === undefined) {
if (!path) {
path = this.FileBase.instance.getState("path");
}
......@@ -428,16 +428,17 @@ define(["jquery", "jclass"], function($, JClass) {
self.$progressBarContainer = $("<div class='upload-progress-container'></div>");
self.$progressBarContainer.appendTo(self.FileBase.view.node);
}
var $progress = $("<div class='progress-row'> \
<div class='progress-title'></div> \
<div class='btn btn-xs btn-danger'><i class='glyphicon glyphicon-stop'></i>abort</div> \
<div class='progress'> \
<div class='progress-bar progress-bar-primary progress-bar-striped active' role='progressbar' \
aria-valuenow='0' aria-valuemin='0' aria-valuemax='100' style='width: 0%;'> \
0% \
</div> \
</div> \
</div>");
var progressTemplate = "<div class='progress-row'> \
<div class='progress-title'></div> \
<div class='glyphicon glyphicon-remove'></div> \
<div class='btn btn-xs btn-danger'><i class='glyphicon glyphicon-stop'></i>abort</div> \
<div class='progress'> \
<div class='progress-bar progress-bar-primary progress-bar-striped active' role='progressbar' \
aria-valuenow='0' aria-valuemin='0' aria-valuemax='100' style='width: 0%;'> \
0% \
</div> \
</div> \
</div>";
//input
var jqXHR = null;
......@@ -448,53 +449,54 @@ define(["jquery", "jclass"], function($, JClass) {
multiple: true
});
$input.fileupload({
// dropZone: null,
maxChunkSize: vispa.args.global.max_request_body_size - 500,
url: vispa.url.dynamic("ajax/fs/upload?path=" + path +
"&_workspaceId=" + self.FileBase.instance.getWorkspaceId()),
singleFileUploads: false,
add: function(event, data) {
jqXHR = data.submit();
},
submit: function(event, data) {
$progress.appendTo(self.$progressBarContainer);
if (data.files.length === 1) {
$(".progress-title", $progress).html(data.files[0].name);
} else {
$(".progress-title", $progress).html(data.files.length + " files");
}
// create progress bar and save it in data
data.$progress = $(progressTemplate);
data.$progress.appendTo(self.$progressBarContainer);
$(".progress-title", data.$progress).html(data.files[0].name);
// abort button
var clicktype = vispa.device.hasTouch ? "tab" : "click";
$(".btn-danger", data.$progress).on(clicktype, function() {
if (data.jqXHR !== null) data.jqXHR.abort();
});
// remove icon
$(".glyphicon-remove", data.$progress).on(clicktype, function() {
data.$progress.remove();
})
},
done: function() {
done: function(event, data) {
$(".progress-bar", data.$progress).toggleClass("progress-bar-primary progress-bar-success");
vispa.messenger.info("Upload succeeded", "glyphicon glyphicon-ok-sign");
},
fail: function(event, data) {
$(".progress-bar", data.$progress).toggleClass("progress-bar-primary progress-bar-danger");
if (data.errorThrown === "abort") {
vispa.messenger.info("Upload aborted", "glyphicon glyphicon-stop");
} else if (data.errorThrown === "Request Entity Too Large") {
vispa.messenger.info("Upload failed: File too large", "glyphicon glyphicon-warning-sign");
} else {
vispa.messenger.info("Upload failed", "glyphicon glyphicon-warning-sign");
}
},
always: function(event, data) {
$(".btn-danger", $progress).toggleClass("disabled", true);
$input.remove();
$(".btn-danger", data.$progress).toggleClass("disabled", true);
setTimeout(function() {
$progress.remove();
data.$progress.remove();
}, 3000);
},
progressall: function(event, data) {
progress: function(event, data) {
var percentage = parseInt(data.loaded / data.total * 100, 10);
$(".progress-bar", $progress).attr("aria-valuenow", percentage);
$(".progress-bar", $progress).css("width", percentage + "%");
$(".progress-bar", $progress).html(percentage + "%");
$(".progress-bar", data.$progress).attr("aria-valuenow", percentage);
$(".progress-bar", data.$progress).css("width", percentage + "%");
$(".progress-bar", data.$progress).html(percentage + "%");
},
stop: function() {
$input.remove();
}
});
$input.trigger("click");
// abort button
var clicktype = vispa.device.hasTouch ? "tab" : "click";
$(".btn-danger", $progress).on(clicktype, function(event) {
if (jqXHR !== null) jqXHR.abort();
});
if (!triggered) $input.trigger("click");
},
filter: function() {
......
......@@ -274,7 +274,7 @@ define(["jquery", "jclass"], function($, JClass) {
} else {
path = self.FileBase.instance.getState("path");
}
self.FileBase.actions.upload(path);
self.FileBase.actions.upload(path, true);
}
// if transfered data not empty: move file
else {
......@@ -369,7 +369,7 @@ define(["jquery", "jclass"], function($, JClass) {
$bkg.toggleClass("dragover", false);
// if files attachted, upload them
if (event.originalEvent.dataTransfer.files.length !== 0) {
self.FileBase.actions.upload();
self.FileBase.actions.upload(null, true);
}
});
}
......
......@@ -2,6 +2,7 @@
<%! import os.path%>
<%! from vispa import url %>
<%! import cherrypy%>
<%inherit file="/vispa.mako" />
<%namespace name="base" file="/base.mako" />
......@@ -26,6 +27,7 @@
logLevel : "${log_level}",
useFeedback : ${"true" if use_feedback else "false"},
workspaceAction: "${workspace_action}",
max_request_body_size: "${cherrypy.server.max_request_body_size}",
url: {
dynamicBase : "${base_dynamic}",
staticBase : "${base_static}"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment