Commit 31328b93 authored by Jerome Mariette's avatar Jerome Mariette
Browse files

a start for the upload

parent 33a66fce
......@@ -16,6 +16,8 @@
#
import cherrypy
import cgi
import tempfile
import json
import sys
from functools import wraps
......@@ -40,6 +42,24 @@ import jflow.utils as utils
from cctools.util import time_format
class myFieldStorage(cgi.FieldStorage):
"""Our version uses a named temporary file instead of the default
non-named file; keeping it visibile (named), allows us to create a
2nd link after the upload is done, thus avoiding the overhead of
making a copy to the destination filename."""
def make_file(self, binary=None):
return tempfile.NamedTemporaryFile()
def noBodyProcess():
"""Sets cherrypy.request.process_request_body = False, giving
us direct control of the file upload destination. By default
cherrypy loads it to memory, we are directing it to disk."""
cherrypy.request.process_request_body = False
cherrypy.tools.noBodyProcess = cherrypy.Tool('before_request_body', noBodyProcess)
class JFlowServer (object):
MULTIPLE_TYPE_SPLITER = "._."
......@@ -110,6 +130,63 @@ class JFlowServer (object):
return status
@cherrypy.expose
@jsonify
def index(self):
"""Simplest possible HTML file upload form. Note that the encoding
type must be multipart/form-data."""
return """
<html>
<body>
<form action="upload" method="post" enctype="multipart/form-data">
File: <input type="file" name="theFile"/> <br/>
<input type="submit"/>
</form>
</body>
</html>
"""
@cherrypy.expose
@cherrypy.tools.noBodyProcess()
@jsonify
def upload(self, theFile):
"""upload action
We use our variation of cgi.FieldStorage to parse the MIME
encoded HTML form data containing the file."""
print theFile
# # the file transfer can take a long time; by default cherrypy
# # limits responses to 300s; we increase it to 1h
# cherrypy.response.timeout = 3600
#
# # convert the header keys to lower case
# lcHDRS = {}
# for key, val in cherrypy.request.headers.iteritems():
# lcHDRS[key.lower()] = val
#
# print lcHDRS
#
# # at this point we could limit the upload on content-length...
# # incomingBytes = int(lcHDRS['content-length'])
#
# # create our version of cgi.FieldStorage to parse the MIME encoded
# # form data where the file is contained
# formFields = myFieldStorage(fp=cherrypy.request.rfile,
# headers=lcHDRS,
# environ={'REQUEST_METHOD':'POST'},
# keep_blank_values=True)
# #print formFields
# # we now create a 2nd link to the file, using the submitted
# # filename; if we renamed, there would be a failure because
# # the NamedTemporaryFile, used by our version of cgi.FieldStorage,
# # explicitly deletes the original filename
# theFile = formFields['theFile']
# print theFile
# os.link(theFile.file.name, '/tmp/'+theFile.filename)
#
# return "ok, got it filename='%s'" % theFile.filename
@cherrypy.expose
@jsonify
def get_available_workflows(self, **kwargs):
......@@ -233,6 +310,15 @@ if __name__ == '__main__':
# define the socket host and port
jflowconf = JFlowConfigReader()
socket_opts = jflowconf.get_socket_options()
# remove any limit on the request body size; cherrypy's default is 100MB
# (maybe we should just increase it ?)
cherrypy.server.max_request_body_size = 0
# increase server socket timeout to 60s; we are more tolerant of bad
# quality client-server connections (cherrypy's defult is 10s)
cherrypy.server.socket_timeout = 60
cherrypy.config.update({'server.socket_host': socket_opts[0],
'server.socket_port': socket_opts[1]})
# start the server
......
......@@ -137,6 +137,12 @@
<script src='../src/js/jquery.tmpl.min.js' type='text/javascript'></script>
<script src='../src/js/jquery.validate.js' type='text/javascript'></script>
<script src="../src/js/jquery.ui.widget.js"></script>
<script src="../src/js/jquery.iframe-transport.js"></script>
<script src="../src/js/jquery.fileupload.js"></script>
<script src="../src/js/jquery.fileupload-process.js"></script>
<script src="../src/js/jquery.fileupload-validate.js"></script>
<script src='../src/js/jflow-activewf.js' type='text/javascript'></script>
<script src='../src/js/jflow-availablewf.js' type='text/javascript'></script>
<script src='../src/js/jflow-wfform.js' type='text/javascript'></script>
......
......@@ -113,6 +113,11 @@
<script src='../src/js/jquery.tmpl.min.js' type='text/javascript'></script>
<script src='../src/js/jquery.validate.js' type='text/javascript'></script>
<script src="../src/js/jquery.ui.widget.js"></script>
<script src="../src/js/jquery.iframe-transport.js"></script>
<script src="../src/js/jquery.fileupload.js"></script>
<script src="../src/js/jquery.fileupload-process.js"></script>
<script src="../src/js/jquery.fileupload-validate.js"></script>
<script src='../src/js/jflow-activewf.js' type='text/javascript'></script>
<script src='../src/js/jflow-availablewf.js' type='text/javascript'></script>
<script src='../src/js/jflow-wfform.js' type='text/javascript'></script>
......
......@@ -27,6 +27,7 @@
} else {
$element = $(element);
}
$element.addClass(errorClass).removeClass(validClass);
// add the bootstrap error class
if ($element.parent().parent("blockquote").find("div").length > 0) {
......@@ -36,7 +37,7 @@
$element.parent().parent("blockquote").find("div.input-append").each(function() {
$(this).find(".btn").addClass("btn-danger");
$(this).find(".icon-calendar").addClass("icon-white");
$(this).find(".icon-upload").addClass("icon-white");
$(this).find(".icon-search").addClass("icon-white");
});
//highlight prepend button as well
$element.parent().parent("blockquote").find("div.input-prepend").each(function() {
......@@ -49,11 +50,11 @@
if ($element.parent("div.input-append").find(".btn").length > 0) {
$element.parent("div.input-append").find(".btn").addClass("btn-danger");
$element.parent("div.input-append").find(".icon-calendar").addClass("icon-white");
$element.parent("div.input-append").find(".icon-upload").addClass("icon-white");
$element.parent("div.input-append").find(".icon-search").addClass("icon-white");
}
// if it's an inputfile
if ($element.parent("div.input-prepend").find(".btn-group").length > 0) {
$element.parent("div.input-prepend").find(".btn").addClass("btn-danger");
if ($element.parent("div.input-append").parent("div.input-prepend").find(".btn-group").length > 0) {
$element.parent("div.input-append").parent("div.input-prepend").find(".btn").addClass("btn-danger");
}
},
......@@ -73,7 +74,7 @@
$element.parent().parent("blockquote").find("div.input-append").each(function() {
$(this).find(".btn").removeClass("btn-danger");
$(this).find(".icon-calendar").removeClass("icon-white");
$(this).find(".icon-upload").removeClass("icon-white");
$(this).find(".icon-search").removeClass("icon-white");
});
}
} else if ($element.parents("div.control-group").find("input." + errorClass).length == 0) {
......@@ -83,16 +84,18 @@
if ($element.parent("div.input-append").find(".btn").length > 0) {
$element.parent("div.input-append").find(".btn").removeClass("btn-danger");
$element.parent("div.input-append").find(".icon-calendar").removeClass("icon-white");
$element.parent("div.input-append").find(".icon-upload").removeClass("icon-white");
$element.parent("div.input-append").find(".icon-search").removeClass("icon-white");
}
if ($element.parent("div.input-prepend").find(".btn-group").length > 0) {
$element.parent("div.input-prepend").find(".btn").removeClass("btn-danger");
if ($element.parent("div.input-append").parent("div.input-prepend").find(".btn-group").length > 0) {
$element.parent("div.input-append").parent("div.input-prepend").find(".btn").removeClass("btn-danger");
}
}
},
errorPlacement: function(error, element) {
error.addClass("help-block");
if (element.parent("div.input-append").length > 0) {
if (element.parent("div.input-append").parent("div.input-prepend").length > 0) {
error.insertAfter(element.parent("div.input-append").parent("div.input-prepend"));
} else if (element.parent("div.input-append").length > 0) {
error.insertAfter(element.parent("div.input-append"));
} else if (element.parent("div.input-prepend").length > 0) {
error.insertAfter(element.parent("div.input-prepend"));
......@@ -225,7 +228,89 @@ jQuery.validator.addMethod("exclude_required", function(value, element, options)
// handle inputfile
$("a[class^=inputfile_]").click(function(){
$("#"+$(this).attr("class")).html($(this).html());
// if this is a local file, empty the field and add a browse behaviour
var parts = $(this).attr("class").split("_"),
tid = parts.slice(1, parts.lenght).join("_");
$("#"+tid).val("");
if ($(this).html() == "local file") {
$("#urlfile_btn_"+tid).show();
$("#"+tid).attr('disabled','disabled');
} else {
$("#urlfile_btn_"+tid).hide();
$("#"+tid).removeAttr('disabled');
}
});
$("[id^=urlfile_btn_]").click(function(){
var parts = $(this).attr("id").split("_"),
tid = parts.slice(2, parts.lenght).join("_");
$("#browse_" + tid).click();
});
$("[id^=browse_]").change( function() {
var parts = $(this).attr("id").split("_"),
tid = parts.slice(1, parts.lenght).join("_");
$("#"+tid).val( $(this).val() );
});
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
// Uncomment the following to send cross-domain cookies:
//xhrFields: {withCredentials: true},
url: 'http://localhost/upload'
});
// Enable iframe cross-domain access via redirect option:
$('#fileupload').fileupload(
'option',
'redirect',
window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
)
);
if (window.location.hostname === 'localhost') {
// Demo settings:
$('#fileupload').fileupload('option', {
url: '//jquery-file-upload.appspot.com/',
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
maxFileSize: 5000000,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
});
// Upload server status check for browsers with CORS support:
if ($.support.cors) {
$.ajax({
url: '//jquery-file-upload.appspot.com/',
type: 'HEAD'
}).fail(function () {
$('<div class="alert alert-danger"/>')
.text('Upload server currently unavailable - ' +
new Date())
.appendTo('#fileupload');
});
}
} else {
// Load existing files:
$('#fileupload').addClass('fileupload-processing');
$.ajax({
// Uncomment the following to send cross-domain cookies:
//xhrFields: {withCredentials: true},
url: $('#fileupload').fileupload('option', 'url'),
dataType: 'json',
context: $('#fileupload')[0]
}).always(function () {
$(this).removeClass('fileupload-processing');
}).done(function (result) {
$(this).fileupload('option', 'done')
.call(this, $.Event('done'), {result: result});
});
}
$('.date').datepicker().on('changeDate', function(ev){ $('.date').datepicker('hide');} );
......@@ -272,9 +357,11 @@ jQuery.validator.addMethod("exclude_required", function(value, element, options)
data: {
type: function() {
if ($("#inputfile_"+param.name).html() == "url") {
return "urlfile"
return "urlfile";
} else if ($("#inputfile_"+param.name).html() == "server file") {
return "localfile"
return "localfile";
} else if ($("#inputfile_"+param.name).html() == "local file") {
return "str";
}
}
}
......@@ -356,10 +443,15 @@ jQuery.validator.addMethod("exclude_required", function(value, element, options)
' </button>',
' <ul class="dropdown-menu">',
' <li><a class="inputfile_${param.name}" href="#">server file</a></li>',
' <li><a class="inputfile_${param.name}" href="#">local file</a></li>',
' <li><a class="inputfile_${param.name}" href="#">url</a></li>',
' </ul>',
' </div>',
' <input id="${param.name}" name="${param.name}" class="${param.group} ${input_size} span2 upload" type="text" value="${param.default}">',
' <div class="input-append">',
' <input id="${param.name}" name="${param.name}" class="${param.group} ${input_size} span2 upload" type="text" value="${param.default}">',
' <button id="urlfile_btn_${param.name}" style="display: none;" class="btn" type="button"><i class="icon-search"></i></button>',
' <input name="browse_${param.name}" id="browse_${param.name}" style="display: none;" type="file">',
' </div>',
' </div>',
// if param is a boolean
'{{else param.type == "bool"}}',
......@@ -430,6 +522,9 @@ jQuery.validator.addMethod("exclude_required", function(value, element, options)
' </div>',
'{{/if}}',
'</fieldset>',
'</form>',
'<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">',
'File: <input id="fileupload" type="file" name="theFile"/> <br/>',
'</form>'].join('\n'),
workflowClass: null,
displayRunButton: true,
......
/*
* jQuery File Upload Processing Plugin 1.3.0
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2012, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* jshint nomen:false */
/* global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'./jquery.fileupload'
], factory);
} else {
// Browser globals:
factory(
window.jQuery
);
}
}(function ($) {
'use strict';
var originalAdd = $.blueimp.fileupload.prototype.options.add;
// The File Upload Processing plugin extends the fileupload widget
// with file processing functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// The list of processing actions:
processQueue: [
/*
{
action: 'log',
type: 'debug'
}
*/
],
add: function (e, data) {
var $this = $(this);
data.process(function () {
return $this.fileupload('process', data);
});
originalAdd.call(this, e, data);
}
},
processActions: {
/*
log: function (data, options) {
console[options.type](
'Processing "' + data.files[data.index].name + '"'
);
}
*/
},
_processFile: function (data, originalData) {
var that = this,
dfd = $.Deferred().resolveWith(that, [data]),
chain = dfd.promise();
this._trigger('process', null, data);
$.each(data.processQueue, function (i, settings) {
var func = function (data) {
if (originalData.errorThrown) {
return $.Deferred()
.rejectWith(that, [originalData]).promise();
}
return that.processActions[settings.action].call(
that,
data,
settings
);
};
chain = chain.pipe(func, settings.always && func);
});
chain
.done(function () {
that._trigger('processdone', null, data);
that._trigger('processalways', null, data);
})
.fail(function () {
that._trigger('processfail', null, data);
that._trigger('processalways', null, data);
});
return chain;
},
// Replaces the settings of each processQueue item that
// are strings starting with an "@", using the remaining
// substring as key for the option map,
// e.g. "@autoUpload" is replaced with options.autoUpload:
_transformProcessQueue: function (options) {
var processQueue = [];
$.each(options.processQueue, function () {
var settings = {},
action = this.action,
prefix = this.prefix === true ? action : this.prefix;
$.each(this, function (key, value) {
if ($.type(value) === 'string' &&
value.charAt(0) === '@') {
settings[key] = options[
value.slice(1) || (prefix ? prefix +
key.charAt(0).toUpperCase() + key.slice(1) : key)
];
} else {
settings[key] = value;
}
});
processQueue.push(settings);
});
options.processQueue = processQueue;
},
// Returns the number of files currently in the processsing queue:
processing: function () {
return this._processing;
},
// Processes the files given as files property of the data parameter,
// returns a Promise object that allows to bind callbacks:
process: function (data) {
var that = this,
options = $.extend({}, this.options, data);
if (options.processQueue && options.processQueue.length) {
this._transformProcessQueue(options);
if (this._processing === 0) {
this._trigger('processstart');
}
$.each(data.files, function (index) {
var opts = index ? $.extend({}, options) : options,
func = function () {
if (data.errorThrown) {
return $.Deferred()
.rejectWith(that, [data]).promise();
}
return that._processFile(opts, data);
};
opts.index = index;
that._processing += 1;
that._processingQueue = that._processingQueue.pipe(func, func)
.always(function () {
that._processing -= 1;
if (that._processing === 0) {
that._trigger('processstop');
}
});
});
}
return this._processingQueue;
},
_create: function () {
this._super();
this._processing = 0;
this._processingQueue = $.Deferred().resolveWith(this)
.promise();
}
});
}));
/*
* jQuery File Upload Validation Plugin 1.1.2
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'./jquery.fileupload-process'
], factory);
} else {
// Browser globals:
factory(
window.jQuery
);
}
}(function ($) {
'use strict';
// Append to the default processQueue:
$.blueimp.fileupload.prototype.options.processQueue.push(
{
action: 'validate',
// Always trigger this action,
// even if the previous action was rejected:
always: true,
// Options taken from the global options map:
acceptFileTypes: '@',
maxFileSize: '@',
minFileSize: '@',
maxNumberOfFiles: '@',
disabled: '@disableValidation'
}
);
// The File Upload Validation plugin extends the fileupload widget
// with file validation functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
/*
// The regular expression for allowed file types, matches
// against either file type or file name:
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
// The maximum allowed file size in bytes:
maxFileSize: 10000000, // 10 MB
// The minimum allowed file size in bytes:
minFileSize: undefined, // No minimal file size
// The limit of files to be uploaded:
maxNumberOfFiles: 10,
*/
// Function returning the current number of files,
// has to be overriden for maxNumberOfFiles validation:
getNumberOfFiles: $.noop,
// Error and info messages:
messages: {
maxNumberOfFiles: 'Maximum number of files exceeded',
acceptFileTypes: 'File type not allowed',
maxFileSize: 'File is too large',
minFileSize: 'File is too small'
}
},
processActions: {
validate: function (data, options) {
if (options.disabled) {
return data;
}
var dfd = $.Deferred(),
settings = this.options,
file = data.files[data.index],
fileSize;
if (options.minFileSize || options.maxFileSize) {
fileSize = file.size;
}
if ($.type(options.maxNumberOfFiles) === 'number' &&
(settings.getNumberOfFiles() || 0) + data.files.length >
options.maxNumberOfFiles) {
file.error = settings.i18n('maxNumberOfFiles');
} else if (options.acceptFileTypes &&
!(options.acceptFileTypes.test(file.type) ||
options.