From f19133f6ecb42ea5ca52c03342a2274a6a9d4c27 Mon Sep 17 00:00:00 2001 From: GuDuJian-J-Zhang <458195880@qq.com> Date: Wed, 3 Apr 2019 18:14:22 +0800 Subject: [PATCH 1/3] add sample for python-flask --- samples/python-flask/app.py | 91 ++ samples/python-flask/requirements.txt | 2 + samples/python-flask/static/cancel.png | Bin 0 -> 255 bytes samples/python-flask/static/pause.png | Bin 0 -> 83 bytes samples/python-flask/static/resumable.js | 1172 +++++++++++++++++++++ samples/python-flask/static/resume.png | Bin 0 -> 126 bytes samples/python-flask/static/style.css | 51 + samples/python-flask/templates/index.html | 116 ++ 8 files changed, 1432 insertions(+) create mode 100644 samples/python-flask/app.py create mode 100644 samples/python-flask/requirements.txt create mode 100644 samples/python-flask/static/cancel.png create mode 100644 samples/python-flask/static/pause.png create mode 100644 samples/python-flask/static/resumable.js create mode 100644 samples/python-flask/static/resume.png create mode 100644 samples/python-flask/static/style.css create mode 100644 samples/python-flask/templates/index.html diff --git a/samples/python-flask/app.py b/samples/python-flask/app.py new file mode 100644 index 00000000..bb537b7e --- /dev/null +++ b/samples/python-flask/app.py @@ -0,0 +1,91 @@ +from flask import Flask, render_template, request, abort, jsonify +import os + +app = Flask(__name__, static_folder='static', static_url_path='/static') +app.debug = True + +temp_base = os.path.expanduser("/home/parallels/work/workroot/uploads") + + +# landing page +@app.route("/resumable") +def resumable_example(): + return render_template("index.html") + + +# resumable.js uses a GET request to check if it uploaded the file already. +# NOTE: your validation here needs to match whatever you do in the POST (otherwise it will NEVER find the files) +@app.route("/upload", methods=['GET']) +def resumable(): + resumableIdentfier = request.args.get('resumableIdentifier', type=str) + resumableFilename = request.args.get('resumableFilename', type=str) + resumableChunkNumber = request.args.get('resumableChunkNumber', type=int) + + if not resumableIdentfier or not resumableFilename or not resumableChunkNumber: + # Parameters are missing or invalid + abort(500, 'Parameter error') + + # chunk folder path based on the parameters + temp_dir = os.path.join(temp_base, resumableIdentfier) + + # chunk path based on the parameters + chunk_file = os.path.join(temp_dir, get_chunk_name(resumableFilename, resumableChunkNumber)) + app.logger.debug('Getting chunk: %s', chunk_file) + + if os.path.isfile(chunk_file): + # Let resumable.js know this chunk already exists + return 'OK' + else: + # Let resumable.js know this chunk does not exists and needs to be uploaded + abort(404, 'Not found') + + +# if it didn't already upload, resumable.js sends the file here +@app.route("/upload", methods=['POST']) +def resumable_post(): + resumableTotalChunks = request.form.get('resumableTotalChunks', type=int) + resumableChunkNumber = request.form.get('resumableChunkNumber', default=1, type=int) + resumableFilename = request.form.get('resumableFilename', default='error', type=str) + resumableIdentfier = request.form.get('resumableIdentifier', default='error', type=str) + + # get the chunk data + chunk_data = request.files['file'] + + # make our temp directory + temp_dir = os.path.join(temp_base, resumableIdentfier) + if not os.path.isdir(temp_dir): + os.makedirs(temp_dir, 0777) + + # save the chunk data + chunk_name = get_chunk_name(resumableFilename, resumableChunkNumber) + chunk_file = os.path.join(temp_dir, chunk_name) + chunk_data.save(chunk_file) + app.logger.debug('Saved chunk: %s', chunk_file) + + # check if the upload is complete + chunk_paths = [os.path.join(temp_dir, get_chunk_name(resumableFilename, x)) for x in range(1, resumableTotalChunks+1)] + upload_complete = all([os.path.exists(p) for p in chunk_paths]) + + # combine all the chunks to create the final file + if upload_complete: + target_file_name = os.path.join(temp_base, resumableFilename) + with open(target_file_name, "ab") as target_file: + for p in chunk_paths: + stored_chunk_file_name = p + stored_chunk_file = open(stored_chunk_file_name, 'rb') + target_file.write(stored_chunk_file.read()) + stored_chunk_file.close() + os.unlink(stored_chunk_file_name) + target_file.close() + os.rmdir(temp_dir) + app.logger.debug('File saved to: %s', target_file_name) + + return 'OK' + + +def get_chunk_name(uploaded_filename, chunk_number): + return uploaded_filename + "_part_%03d" % chunk_number + + +if __name__ == '__main__': + app.run() diff --git a/samples/python-flask/requirements.txt b/samples/python-flask/requirements.txt new file mode 100644 index 00000000..e31f8ac6 --- /dev/null +++ b/samples/python-flask/requirements.txt @@ -0,0 +1,2 @@ +Flask + diff --git a/samples/python-flask/static/cancel.png b/samples/python-flask/static/cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..f5a10aba9ed74268b351af17794b51fb0debccb0 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>VN3FMcVYMs zf(!O8p9~b?EbxddW?e1Z*^34Hz2352ER~zR(~0%g^jl{=>Kd|iKkeWA qex|Z!e;wCZmwtD)%O4e_fAFr0&dB%6Ro4XC!r^hB=eBO}%9;7YS6w;OXk;vd$@?2>|~57Eb^G literal 0 HcmV?d00001 diff --git a/samples/python-flask/static/resumable.js b/samples/python-flask/static/resumable.js new file mode 100644 index 00000000..cba0ed4a --- /dev/null +++ b/samples/python-flask/static/resumable.js @@ -0,0 +1,1172 @@ +/* +* MIT Licensed +* http://www.23developer.com/opensource +* http://github.com/23/resumable.js +* Steffen Tiedemann Christensen, steffen@23company.com +*/ + +(function(){ +"use strict"; + + var Resumable = function(opts){ + if ( !(this instanceof Resumable) ) { + return new Resumable(opts); + } + this.version = 1.0; + // SUPPORTED BY BROWSER? + // Check if these features are support by the browser: + // - File object type + // - Blob object type + // - FileList object type + // - slicing files + this.support = ( + (typeof(File)!=='undefined') + && + (typeof(Blob)!=='undefined') + && + (typeof(FileList)!=='undefined') + && + (!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||!!Blob.prototype.slice||false) + ); + if(!this.support) return(false); + + + // PROPERTIES + var $ = this; + $.files = []; + $.defaults = { + chunkSize:1*1024*1024, + forceChunkSize:false, + simultaneousUploads:3, + fileParameterName:'file', + chunkNumberParameterName: 'resumableChunkNumber', + chunkSizeParameterName: 'resumableChunkSize', + currentChunkSizeParameterName: 'resumableCurrentChunkSize', + totalSizeParameterName: 'resumableTotalSize', + typeParameterName: 'resumableType', + identifierParameterName: 'resumableIdentifier', + fileNameParameterName: 'resumableFilename', + relativePathParameterName: 'resumableRelativePath', + totalChunksParameterName: 'resumableTotalChunks', + dragOverClass: 'dragover', + throttleProgressCallbacks: 0.5, + query:{}, + headers:{}, + preprocess:null, + preprocessFile:null, + method:'multipart', + uploadMethod: 'POST', + testMethod: 'GET', + prioritizeFirstAndLastChunk:false, + target:'/', + testTarget: null, + parameterNamespace:'', + testChunks:true, + generateUniqueIdentifier:null, + getTarget:null, + maxChunkRetries:100, + chunkRetryInterval:undefined, + permanentErrors:[400, 401, 403, 404, 409, 415, 500, 501], + maxFiles:undefined, + withCredentials:false, + xhrTimeout:0, + clearInput:true, + chunkFormat:'blob', + setChunkTypeFromFile:false, + maxFilesErrorCallback:function (files, errorCount) { + var maxFiles = $.getOpt('maxFiles'); + alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); + }, + minFileSize:1, + minFileSizeErrorCallback:function(file, errorCount) { + alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); + }, + maxFileSize:undefined, + maxFileSizeErrorCallback:function(file, errorCount) { + alert(file.fileName||file.name +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); + }, + fileType: [], + fileTypeErrorCallback: function(file, errorCount) { + alert(file.fileName||file.name +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); + } + }; + $.opts = opts||{}; + $.getOpt = function(o) { + var $opt = this; + // Get multiple option if passed an array + if(o instanceof Array) { + var options = {}; + $h.each(o, function(option){ + options[option] = $opt.getOpt(option); + }); + return options; + } + // Otherwise, just return a simple option + if ($opt instanceof ResumableChunk) { + if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } + else { $opt = $opt.fileObj; } + } + if ($opt instanceof ResumableFile) { + if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } + else { $opt = $opt.resumableObj; } + } + if ($opt instanceof Resumable) { + if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } + else { return $opt.defaults[o]; } + } + }; + $.indexOf = function(array, obj) { + if (array.indexOf) { return array.indexOf(obj); } + for (var i = 0; i < array.length; i++) { + if (array[i] === obj) { return i; } + } + return -1; + } + + // EVENTS + // catchAll(event, ...) + // fileSuccess(file), fileProgress(file), fileAdded(file, event), filesAdded(files, filesSkipped), fileRetry(file), + // fileError(file, message), complete(), progress(), error(message, file), pause() + $.events = []; + $.on = function(event,callback){ + $.events.push(event.toLowerCase(), callback); + }; + $.fire = function(){ + // `arguments` is an object, not array, in FF, so: + var args = []; + for (var i=0; i= 0) { // only for file drop + e.stopPropagation(); + dt.dropEffect = "copy"; + dt.effectAllowed = "copy"; + e.currentTarget.classList.add($.getOpt('dragOverClass')); + } else { // not work on IE/Edge.... + dt.dropEffect = "none"; + dt.effectAllowed = "none"; + } + }; + + /** + * processes a single upload item (file or directory) + * @param {Object} item item to upload, may be file or directory entry + * @param {string} path current file path + * @param {File[]} items list of files to append new items to + * @param {Function} cb callback invoked when item is processed + */ + function processItem(item, path, items, cb) { + var entry; + if(item.isFile){ + // file provided + return item.file(function(file){ + file.relativePath = path + file.name; + items.push(file); + cb(); + }); + }else if(item.isDirectory){ + // item is already a directory entry, just assign + entry = item; + }else if(item instanceof File) { + items.push(item); + } + if('function' === typeof item.webkitGetAsEntry){ + // get entry from file object + entry = item.webkitGetAsEntry(); + } + if(entry && entry.isDirectory){ + // directory provided, process it + return processDirectory(entry, path + entry.name + '/', items, cb); + } + if('function' === typeof item.getAsFile){ + // item represents a File object, convert it + item = item.getAsFile(); + if(item instanceof File) { + item.relativePath = path + item.name; + items.push(item); + } + } + cb(); // indicate processing is done + } + + + /** + * cps-style list iteration. + * invokes all functions in list and waits for their callback to be + * triggered. + * @param {Function[]} items list of functions expecting callback parameter + * @param {Function} cb callback to trigger after the last callback has been invoked + */ + function processCallbacks(items, cb){ + if(!items || items.length === 0){ + // empty or no list, invoke callback + return cb(); + } + // invoke current function, pass the next part as continuation + items[0](function(){ + processCallbacks(items.slice(1), cb); + }); + } + + /** + * recursively traverse directory and collect files to upload + * @param {Object} directory directory to process + * @param {string} path current path + * @param {File[]} items target list of items + * @param {Function} cb callback invoked after traversing directory + */ + function processDirectory (directory, path, items, cb) { + var dirReader = directory.createReader(); + var allEntries = []; + + function readEntries () { + dirReader.readEntries(function(entries){ + if (entries.length) { + allEntries = allEntries.concat(entries); + return readEntries(); + } + + // process all conversion callbacks, finally invoke own one + processCallbacks( + allEntries.map(function(entry){ + // bind all properties except for callback + return processItem.bind(null, entry, path, items); + }), + cb + ); + }); + } + + readEntries(); + } + + /** + * process items to extract files to be uploaded + * @param {File[]} items items to process + * @param {Event} event event that led to upload + */ + function loadFiles(items, event) { + if(!items.length){ + return; // nothing to do + } + $.fire('beforeAdd'); + var files = []; + processCallbacks( + Array.prototype.map.call(items, function(item){ + // bind all properties except for callback + var entry = item; + if('function' === typeof item.webkitGetAsEntry){ + entry = item.webkitGetAsEntry(); + } + return processItem.bind(null, entry, "", files); + }), + function(){ + if(files.length){ + // at least one file found + appendFilesFromFileList(files, event); + } + } + ); + }; + + var appendFilesFromFileList = function(fileList, event){ + // check for uploading too many files + var errorCount = 0; + var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']); + if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) { + // if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file + if (o.maxFiles===1 && $.files.length===1 && fileList.length===1) { + $.removeFile($.files[0]); + } else { + o.maxFilesErrorCallback(fileList, errorCount++); + return false; + } + } + var files = [], filesSkipped = [], remaining = fileList.length; + var decreaseReamining = function(){ + if(!--remaining){ + // all files processed, trigger event + if(!files.length && !filesSkipped.length){ + // no succeeded files, just skip + return; + } + window.setTimeout(function(){ + $.fire('filesAdded', files, filesSkipped); + },0); + } + }; + $h.each(fileList, function(file){ + var fileName = file.name; + var fileType = file.type; // e.g video/mp4 + if(o.fileType.length > 0){ + var fileTypeFound = false; + for(var index in o.fileType){ + // For good behaviour we do some inital sanitizing. Remove spaces and lowercase all + o.fileType[index] = o.fileType[index].replace(/\s/g, '').toLowerCase(); + + // Allowing for both [extension, .extension, mime/type, mime/*] + var extension = ((o.fileType[index].match(/^[^.][^/]+$/)) ? '.' : '') + o.fileType[index]; + + if ((fileName.substr(-1 * extension.length).toLowerCase() === extension) || + //If MIME type, check for wildcard or if extension matches the files tiletype + (extension.indexOf('/') !== -1 && ( + (extension.indexOf('*') !== -1 && fileType.substr(0, extension.indexOf('*')) === extension.substr(0, extension.indexOf('*'))) || + fileType === extension + )) + ){ + fileTypeFound = true; + break; + } + } + if (!fileTypeFound) { + o.fileTypeErrorCallback(file, errorCount++); + return true; + } + } + + if (typeof(o.minFileSize)!=='undefined' && file.sizeo.maxFileSize) { + o.maxFileSizeErrorCallback(file, errorCount++); + return true; + } + + function addFile(uniqueIdentifier){ + if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ + file.uniqueIdentifier = uniqueIdentifier; + var f = new ResumableFile($, file, uniqueIdentifier); + $.files.push(f); + files.push(f); + f.container = (typeof event != 'undefined' ? event.srcElement : null); + window.setTimeout(function(){ + $.fire('fileAdded', f, event) + },0); + })()} else { + filesSkipped.push(file); + }; + decreaseReamining(); + } + // directories have size == 0 + var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); + if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ + // Promise or Promise-like object provided as unique identifier + uniqueIdentifier + .then( + function(uniqueIdentifier){ + // unique identifier generation succeeded + addFile(uniqueIdentifier); + }, + function(){ + // unique identifier generation failed + // skip further processing, only decrease file count + decreaseReamining(); + } + ); + }else{ + // non-Promise provided as unique identifier, process synchronously + addFile(uniqueIdentifier); + } + }); + }; + + // INTERNAL OBJECT TYPES + function ResumableFile(resumableObj, file, uniqueIdentifier){ + var $ = this; + $.opts = {}; + $.getOpt = resumableObj.getOpt; + $._prevProgress = 0; + $.resumableObj = resumableObj; + $.file = file; + $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox + $.size = file.size; + $.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; + $.uniqueIdentifier = uniqueIdentifier; + $._pause = false; + $.container = ''; + $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished + var _error = uniqueIdentifier !== undefined; + + // Callback when something happens within the chunk + var chunkEvent = function(event, message){ + // event can be 'progress', 'success', 'error' or 'retry' + switch(event){ + case 'progress': + $.resumableObj.fire('fileProgress', $, message); + break; + case 'error': + $.abort(); + _error = true; + $.chunks = []; + $.resumableObj.fire('fileError', $, message); + break; + case 'success': + if(_error) return; + $.resumableObj.fire('fileProgress', $, message); // it's at least progress + if($.isComplete()) { + $.resumableObj.fire('fileSuccess', $, message); + } + break; + case 'retry': + $.resumableObj.fire('fileRetry', $); + break; + } + }; + + // Main code to set up a file object with chunks, + // packaged to be able to handle retries if needed. + $.chunks = []; + $.abort = function(){ + // Stop current uploads + var abortCount = 0; + $h.each($.chunks, function(c){ + if(c.status()=='uploading') { + c.abort(); + abortCount++; + } + }); + if(abortCount>0) $.resumableObj.fire('fileProgress', $); + }; + $.cancel = function(){ + // Reset this file to be void + var _chunks = $.chunks; + $.chunks = []; + // Stop current uploads + $h.each(_chunks, function(c){ + if(c.status()=='uploading') { + c.abort(); + $.resumableObj.uploadNextChunk(); + } + }); + $.resumableObj.removeFile($); + $.resumableObj.fire('fileProgress', $); + }; + $.retry = function(){ + $.bootstrap(); + var firedRetry = false; + $.resumableObj.on('chunkingComplete', function(){ + if(!firedRetry) $.resumableObj.upload(); + firedRetry = true; + }); + }; + $.bootstrap = function(){ + $.abort(); + _error = false; + // Rebuild stack of chunks from file + $.chunks = []; + $._prevProgress = 0; + var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; + var maxOffset = Math.max(round($.file.size/$.getOpt('chunkSize')),1); + for (var offset=0; offset0.99999 ? 1 : ret)); + ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused + $._prevProgress = ret; + return(ret); + }; + $.isUploading = function(){ + var uploading = false; + $h.each($.chunks, function(chunk){ + if(chunk.status()=='uploading') { + uploading = true; + return(false); + } + }); + return(uploading); + }; + $.isComplete = function(){ + var outstanding = false; + if ($.preprocessState === 1) { + return(false); + } + $h.each($.chunks, function(chunk){ + var status = chunk.status(); + if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { + outstanding = true; + return(false); + } + }); + return(!outstanding); + }; + $.pause = function(pause){ + if(typeof(pause)==='undefined'){ + $._pause = ($._pause ? false : true); + }else{ + $._pause = pause; + } + }; + $.isPaused = function() { + return $._pause; + }; + $.preprocessFinished = function(){ + $.preprocessState = 2; + $.upload(); + }; + $.upload = function () { + var found = false; + if ($.isPaused() === false) { + var preprocess = $.getOpt('preprocessFile'); + if(typeof preprocess === 'function') { + switch($.preprocessState) { + case 0: $.preprocessState = 1; preprocess($); return(true); + case 1: return(true); + case 2: break; + } + } + $h.each($.chunks, function (chunk) { + if (chunk.status() == 'pending' && chunk.preprocessState !== 1) { + chunk.send(); + found = true; + return(false); + } + }); + } + return(found); + } + $.markChunksCompleted = function (chunkNumber) { + if (!$.chunks || $.chunks.length <= chunkNumber) { + return; + } + for (var num = 0; num < chunkNumber; num++) { + $.chunks[num].markComplete = true; + } + }; + + // Bootstrap and return + $.resumableObj.fire('chunkingStart', $); + $.bootstrap(); + return(this); + } + + + function ResumableChunk(resumableObj, fileObj, offset, callback){ + var $ = this; + $.opts = {}; + $.getOpt = resumableObj.getOpt; + $.resumableObj = resumableObj; + $.fileObj = fileObj; + $.fileObjSize = fileObj.size; + $.fileObjType = fileObj.file.type; + $.offset = offset; + $.callback = callback; + $.lastProgressCallback = (new Date); + $.tested = false; + $.retries = 0; + $.pendingRetry = false; + $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished + $.markComplete = false; + + // Computed properties + var chunkSize = $.getOpt('chunkSize'); + $.loaded = 0; + $.startByte = $.offset*chunkSize; + $.endByte = Math.min($.fileObjSize, ($.offset+1)*chunkSize); + if ($.fileObjSize-$.endByte < chunkSize && !$.getOpt('forceChunkSize')) { + // The last chunk will be bigger than the chunk size, but less than 2*chunkSize + $.endByte = $.fileObjSize; + } + $.xhr = null; + + // test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session + $.test = function(){ + // Set up request and listen for event + $.xhr = new XMLHttpRequest(); + + var testHandler = function(e){ + $.tested = true; + var status = $.status(); + if(status=='success') { + $.callback(status, $.message()); + $.resumableObj.uploadNextChunk(); + } else { + $.send(); + } + }; + $.xhr.addEventListener('load', testHandler, false); + $.xhr.addEventListener('error', testHandler, false); + $.xhr.addEventListener('timeout', testHandler, false); + + // Add data from the query options + var params = []; + var parameterNamespace = $.getOpt('parameterNamespace'); + var customQuery = $.getOpt('query'); + if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); + $h.each(customQuery, function(k,v){ + params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); + }); + // Add extra data to identify chunk + params = params.concat( + [ + // define key/value pairs for additional parameters + ['chunkNumberParameterName', $.offset + 1], + ['chunkSizeParameterName', $.getOpt('chunkSize')], + ['currentChunkSizeParameterName', $.endByte - $.startByte], + ['totalSizeParameterName', $.fileObjSize], + ['typeParameterName', $.fileObjType], + ['identifierParameterName', $.fileObj.uniqueIdentifier], + ['fileNameParameterName', $.fileObj.fileName], + ['relativePathParameterName', $.fileObj.relativePath], + ['totalChunksParameterName', $.fileObj.chunks.length] + ].filter(function(pair){ + // include items that resolve to truthy values + // i.e. exclude false, null, undefined and empty strings + return $.getOpt(pair[0]); + }) + .map(function(pair){ + // map each key/value pair to its final form + return [ + parameterNamespace + $.getOpt(pair[0]), + encodeURIComponent(pair[1]) + ].join('='); + }) + ); + // Append the relevant chunk and send it + $.xhr.open($.getOpt('testMethod'), $h.getTarget('test', params)); + $.xhr.timeout = $.getOpt('xhrTimeout'); + $.xhr.withCredentials = $.getOpt('withCredentials'); + // Add data from header options + var customHeaders = $.getOpt('headers'); + if(typeof customHeaders === 'function') { + customHeaders = customHeaders($.fileObj, $); + } + $h.each(customHeaders, function(k,v) { + $.xhr.setRequestHeader(k, v); + }); + $.xhr.send(null); + }; + + $.preprocessFinished = function(){ + $.preprocessState = 2; + $.send(); + }; + + // send() uploads the actual data in a POST call + $.send = function(){ + var preprocess = $.getOpt('preprocess'); + if(typeof preprocess === 'function') { + switch($.preprocessState) { + case 0: $.preprocessState = 1; preprocess($); return; + case 1: return; + case 2: break; + } + } + if($.getOpt('testChunks') && !$.tested) { + $.test(); + return; + } + + // Set up request and listen for event + $.xhr = new XMLHttpRequest(); + + // Progress + $.xhr.upload.addEventListener('progress', function(e){ + if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) { + $.callback('progress'); + $.lastProgressCallback = (new Date); + } + $.loaded=e.loaded||0; + }, false); + $.loaded = 0; + $.pendingRetry = false; + $.callback('progress'); + + // Done (either done, failed or retry) + var doneHandler = function(e){ + var status = $.status(); + if(status=='success'||status=='error') { + $.callback(status, $.message()); + $.resumableObj.uploadNextChunk(); + } else { + $.callback('retry', $.message()); + $.abort(); + $.retries++; + var retryInterval = $.getOpt('chunkRetryInterval'); + if(retryInterval !== undefined) { + $.pendingRetry = true; + setTimeout($.send, retryInterval); + } else { + $.send(); + } + } + }; + $.xhr.addEventListener('load', doneHandler, false); + $.xhr.addEventListener('error', doneHandler, false); + $.xhr.addEventListener('timeout', doneHandler, false); + + // Set up the basic query data from Resumable + var query = [ + ['chunkNumberParameterName', $.offset + 1], + ['chunkSizeParameterName', $.getOpt('chunkSize')], + ['currentChunkSizeParameterName', $.endByte - $.startByte], + ['totalSizeParameterName', $.fileObjSize], + ['typeParameterName', $.fileObjType], + ['identifierParameterName', $.fileObj.uniqueIdentifier], + ['fileNameParameterName', $.fileObj.fileName], + ['relativePathParameterName', $.fileObj.relativePath], + ['totalChunksParameterName', $.fileObj.chunks.length], + ].filter(function(pair){ + // include items that resolve to truthy values + // i.e. exclude false, null, undefined and empty strings + return $.getOpt(pair[0]); + }) + .reduce(function(query, pair){ + // assign query key/value + query[$.getOpt(pair[0])] = pair[1]; + return query; + }, {}); + // Mix in custom data + var customQuery = $.getOpt('query'); + if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); + $h.each(customQuery, function(k,v){ + query[k] = v; + }); + + var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))); + var bytes = $.fileObj.file[func]($.startByte, $.endByte, $.getOpt('setChunkTypeFromFile') ? $.fileObj.file.type : ""); + var data = null; + var params = []; + + var parameterNamespace = $.getOpt('parameterNamespace'); + if ($.getOpt('method') === 'octet') { + // Add data from the query options + data = bytes; + $h.each(query, function (k, v) { + params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); + }); + } else { + // Add data from the query options + data = new FormData(); + $h.each(query, function (k, v) { + data.append(parameterNamespace + k, v); + params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); + }); + if ($.getOpt('chunkFormat') == 'blob') { + data.append(parameterNamespace + $.getOpt('fileParameterName'), bytes, $.fileObj.fileName); + } + else if ($.getOpt('chunkFormat') == 'base64') { + var fr = new FileReader(); + fr.onload = function (e) { + data.append(parameterNamespace + $.getOpt('fileParameterName'), fr.result); + $.xhr.send(data); + } + fr.readAsDataURL(bytes); + } + } + + var target = $h.getTarget('upload', params); + var method = $.getOpt('uploadMethod'); + + $.xhr.open(method, target); + if ($.getOpt('method') === 'octet') { + $.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); + } + $.xhr.timeout = $.getOpt('xhrTimeout'); + $.xhr.withCredentials = $.getOpt('withCredentials'); + // Add data from header options + var customHeaders = $.getOpt('headers'); + if(typeof customHeaders === 'function') { + customHeaders = customHeaders($.fileObj, $); + } + + $h.each(customHeaders, function(k,v) { + $.xhr.setRequestHeader(k, v); + }); + + if ($.getOpt('chunkFormat') == 'blob') { + $.xhr.send(data); + } + }; + $.abort = function(){ + // Abort and reset + if($.xhr) $.xhr.abort(); + $.xhr = null; + }; + $.status = function(){ + // Returns: 'pending', 'uploading', 'success', 'error' + if($.pendingRetry) { + // if pending retry then that's effectively the same as actively uploading, + // there might just be a slight delay before the retry starts + return('uploading'); + } else if($.markComplete) { + return 'success'; + } else if(!$.xhr) { + return('pending'); + } else if($.xhr.readyState<4) { + // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening + return('uploading'); + } else { + if($.xhr.status == 200 || $.xhr.status == 201) { + // HTTP 200, 201 (created) + return('success'); + } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { + // HTTP 400, 404, 409, 415, 500, 501 (permanent error) + return('error'); + } else { + // this should never happen, but we'll reset and queue a retry + // a likely case for this would be 503 service unavailable + $.abort(); + return('pending'); + } + } + }; + $.message = function(){ + return($.xhr ? $.xhr.responseText : ''); + }; + $.progress = function(relative){ + if(typeof(relative)==='undefined') relative = false; + var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1); + if($.pendingRetry) return(0); + if((!$.xhr || !$.xhr.status) && !$.markComplete) factor*=.95; + var s = $.status(); + switch(s){ + case 'success': + case 'error': + return(1*factor); + case 'pending': + return(0*factor); + default: + return($.loaded/($.endByte-$.startByte)*factor); + } + }; + return(this); + } + + // QUEUE + $.uploadNextChunk = function(){ + var found = false; + + // In some cases (such as videos) it's really handy to upload the first + // and last chunk of a file quickly; this let's the server check the file's + // metadata and determine if there's even a point in continuing. + if ($.getOpt('prioritizeFirstAndLastChunk')) { + $h.each($.files, function(file){ + if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) { + file.chunks[0].send(); + found = true; + return(false); + } + if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[file.chunks.length-1].preprocessState === 0) { + file.chunks[file.chunks.length-1].send(); + found = true; + return(false); + } + }); + if(found) return(true); + } + + // Now, simply look for the next, best thing to upload + $h.each($.files, function(file){ + found = file.upload(); + if(found) return(false); + }); + if(found) return(true); + + // The are no more outstanding chunks to upload, check is everything is done + var outstanding = false; + $h.each($.files, function(file){ + if(!file.isComplete()) { + outstanding = true; + return(false); + } + }); + if(!outstanding) { + // All chunks have been uploaded, complete + $.fire('complete'); + } + return(false); + }; + + + // PUBLIC METHODS FOR RESUMABLE.JS + $.assignBrowse = function(domNodes, isDirectory){ + if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; + $h.each(domNodes, function(domNode) { + var input; + if(domNode.tagName==='INPUT' && domNode.type==='file'){ + input = domNode; + } else { + input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.style.display = 'none'; + domNode.addEventListener('click', function(){ + input.style.opacity = 0; + input.style.display='block'; + input.focus(); + input.click(); + input.style.display='none'; + }, false); + domNode.appendChild(input); + } + var maxFiles = $.getOpt('maxFiles'); + if (typeof(maxFiles)==='undefined'||maxFiles!=1){ + input.setAttribute('multiple', 'multiple'); + } else { + input.removeAttribute('multiple'); + } + if(isDirectory){ + input.setAttribute('webkitdirectory', 'webkitdirectory'); + } else { + input.removeAttribute('webkitdirectory'); + } + var fileTypes = $.getOpt('fileType'); + if (typeof (fileTypes) !== 'undefined' && fileTypes.length >= 1) { + input.setAttribute('accept', fileTypes.map(function (e) { + e = e.replace(/\s/g, '').toLowerCase(); + if(e.match(/^[^.][^/]+$/)){ + e = '.' + e; + } + return e; + }).join(',')); + } + else { + input.removeAttribute('accept'); + } + // When new files are added, simply append them to the overall list + input.addEventListener('change', function(e){ + appendFilesFromFileList(e.target.files,e); + var clearInput = $.getOpt('clearInput'); + if (clearInput) { + e.target.value = ''; + } + }, false); + }); + }; + $.assignDrop = function(domNodes){ + if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; + + $h.each(domNodes, function(domNode) { + domNode.addEventListener('dragover', onDragOverEnter, false); + domNode.addEventListener('dragenter', onDragOverEnter, false); + domNode.addEventListener('dragleave', onDragLeave, false); + domNode.addEventListener('drop', onDrop, false); + }); + }; + $.unAssignDrop = function(domNodes) { + if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; + + $h.each(domNodes, function(domNode) { + domNode.removeEventListener('dragover', onDragOverEnter); + domNode.removeEventListener('dragenter', onDragOverEnter); + domNode.removeEventListener('dragleave', onDragLeave); + domNode.removeEventListener('drop', onDrop); + }); + }; + $.isUploading = function(){ + var uploading = false; + $h.each($.files, function(file){ + if (file.isUploading()) { + uploading = true; + return(false); + } + }); + return(uploading); + }; + $.upload = function(){ + // Make sure we don't start too many uploads at once + if($.isUploading()) return; + // Kick off the queue + $.fire('uploadStart'); + for (var num=1; num<=$.getOpt('simultaneousUploads'); num++) { + $.uploadNextChunk(); + } + }; + $.pause = function(){ + // Resume all chunks currently being uploaded + $h.each($.files, function(file){ + file.abort(); + }); + $.fire('pause'); + }; + $.cancel = function(){ + $.fire('beforeCancel'); + for(var i = $.files.length - 1; i >= 0; i--) { + $.files[i].cancel(); + } + $.fire('cancel'); + }; + $.progress = function(){ + var totalDone = 0; + var totalSize = 0; + // Resume all chunks currently being uploaded + $h.each($.files, function(file){ + totalDone += file.progress()*file.size; + totalSize += file.size; + }); + return(totalSize>0 ? totalDone/totalSize : 0); + }; + $.addFile = function(file, event){ + appendFilesFromFileList([file], event); + }; + $.addFiles = function(files, event){ + appendFilesFromFileList(files, event); + }; + $.removeFile = function(file){ + for(var i = $.files.length - 1; i >= 0; i--) { + if($.files[i] === file) { + $.files.splice(i, 1); + } + } + }; + $.getFromUniqueIdentifier = function(uniqueIdentifier){ + var ret = false; + $h.each($.files, function(f){ + if(f.uniqueIdentifier==uniqueIdentifier) ret = f; + }); + return(ret); + }; + $.getSize = function(){ + var totalSize = 0; + $h.each($.files, function(file){ + totalSize += file.size; + }); + return(totalSize); + }; + $.handleDropEvent = function (e) { + onDrop(e); + }; + $.handleChangeEvent = function (e) { + appendFilesFromFileList(e.target.files, e); + e.target.value = ''; + }; + $.updateQuery = function(query){ + $.opts.query = query; + }; + + return(this); + }; + + + // Node.js-style export for Node and Component + if (typeof module != 'undefined') { + // left here for backwards compatibility + module.exports = Resumable; + module.exports.Resumable = Resumable; + } else if (typeof define === "function" && define.amd) { + // AMD/requirejs: Define the module + define(function(){ + return Resumable; + }); + } else { + // Browser: Expose to window + window.Resumable = Resumable; + } + +})(); diff --git a/samples/python-flask/static/resume.png b/samples/python-flask/static/resume.png new file mode 100644 index 0000000000000000000000000000000000000000..b150936d49f8c3a7bb1b4d4c66e4e473384a04ec GIT binary patch literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^96-#)!3HEdkIOdzDOXPy#}J9B$se+|S27&opV1~o&4H1mf+_A<~w22WQ%mvv4FO#r2yB&PrX literal 0 HcmV?d00001 diff --git a/samples/python-flask/static/style.css b/samples/python-flask/static/style.css new file mode 100644 index 00000000..6b447b13 --- /dev/null +++ b/samples/python-flask/static/style.css @@ -0,0 +1,51 @@ +/* Reset */ +body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;} + +/* Baseline */ +body, p, h1, h2, h3, h4, h5, h6 {font:normal 12px/1.3em Helvetica, Arial, sans-serif; color:#333; } +h1 {font-size:22px; font-weight:bold;} +h2 {font-size:19px; font-weight:bold;} +h3 {font-size:16px; font-weight:bold;} +h4 {font-size:14px; font-weight:bold;} +h5 {font-size:12px; font-weight:bold;} +p {margin:10px 0;} + + +body {text-align:center; margin:40px;} +#frame {margin:0 auto; width:800px; text-align:left;} + + + +/* Uploader: Drag & Drop */ +.resumable-error {display:none; font-size:14px; font-style:italic;} +.resumable-drop {padding:15px; font-size:13px; text-align:center; color:#666; font-weight:bold;background-color:#eee; border:2px dashed #aaa; border-radius:10px; margin-top:40px; z-index:9999; display:none;} +.dragover {padding:30px; color:#555; background-color:#ddd; border:1px solid #999;} + +/* Uploader: Progress bar */ +.resumable-progress {margin:30px 0 30px 0; width:100%; display:none;} +.progress-container {height:7px; background:#9CBD94; position:relative; } +.progress-bar {position:absolute; top:0; left:0; bottom:0; background:#45913A; width:0;} +.progress-text {font-size:11px; line-height:9px; padding-left:10px;} +.progress-pause {padding:0 0 0 7px;} +.progress-resume-link {display:none;} +.is-paused .progress-resume-link {display:inline;} +.is-paused .progress-pause-link {display:none;} +.is-complete .progress-pause {display:none;} + +/* Uploader: List of items being uploaded */ +.resumable-list {overflow:auto; margin-right:-20px; display:none;} +.uploader-item {width:148px; height:90px; background-color:#666; position:relative; border:2px solid black; float:left; margin:0 6px 6px 0;} +.uploader-item-thumbnail {width:100%; height:100%; position:absolute; top:0; left:0;} +.uploader-item img.uploader-item-thumbnail {opacity:0;} +.uploader-item-creating-thumbnail {padding:0 5px; font-size:9px; color:white;} +.uploader-item-title {position:absolute; font-size:9px; line-height:11px; padding:3px 50px 3px 5px; bottom:0; left:0; right:0; color:white; background-color:rgba(0,0,0,0.6); min-height:27px;} +.uploader-item-status {position:absolute; bottom:3px; right:3px;} + +/* Uploader: Hover & Active status */ +.uploader-item:hover, .is-active .uploader-item {border-color:#4a873c; cursor:pointer; } +.uploader-item:hover .uploader-item-title, .is-active .uploader-item .uploader-item-title {background-color:rgba(74,135,60,0.8);} + +/* Uploader: Error status */ +.is-error .uploader-item:hover, .is-active.is-error .uploader-item {border-color:#900;} +.is-error .uploader-item:hover .uploader-item-title, .is-active.is-error .uploader-item .uploader-item-title {background-color:rgba(153,0,0,0.6);} +.is-error .uploader-item-creating-thumbnail {display:none;} diff --git a/samples/python-flask/templates/index.html b/samples/python-flask/templates/index.html new file mode 100644 index 00000000..9f384ff4 --- /dev/null +++ b/samples/python-flask/templates/index.html @@ -0,0 +1,116 @@ + + + + Resumable.js - Multiple simultaneous, stable and resumable uploads via the HTML5 File API + + + + +
+ +

Resumable.js

+

It's a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.

+ +

The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause and resume uploads without loosing state.

+ +

Resumable.js relies on the HTML5 File API and the ability to chunks files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.

+ +
+ +

Demo

+ + + +
+ Your browser, unfortunately, is not supported by Resumable.js. The library requires support for the HTML5 File API along with file slicing. +
+ +
+ Drop video files here to upload or select from your computer +
+ +
+ + + + + + +
+ + + +
+
+ +
    + + + +
    + + + + + From ab2ce1882ed36df9834e449101ac20c12af3c908 Mon Sep 17 00:00:00 2001 From: GuDuJian-J-Zhang <458195880@qq.com> Date: Thu, 4 Apr 2019 10:39:57 +0800 Subject: [PATCH 2/3] code reorg --- samples/python-flask/app.py | 15 +- samples/python-flask/static/resumable.js | 1172 --------------------- samples/python-flask/templates/index.html | 2 +- 3 files changed, 13 insertions(+), 1176 deletions(-) delete mode 100644 samples/python-flask/static/resumable.js diff --git a/samples/python-flask/app.py b/samples/python-flask/app.py index bb537b7e..ec383328 100644 --- a/samples/python-flask/app.py +++ b/samples/python-flask/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, request, abort, jsonify +from flask import Flask, render_template, request, abort, make_response, jsonify import os app = Flask(__name__, static_folder='static', static_url_path='/static') @@ -8,7 +8,7 @@ # landing page -@app.route("/resumable") +@app.route("/") def resumable_example(): return render_template("index.html") @@ -16,7 +16,7 @@ def resumable_example(): # resumable.js uses a GET request to check if it uploaded the file already. # NOTE: your validation here needs to match whatever you do in the POST (otherwise it will NEVER find the files) @app.route("/upload", methods=['GET']) -def resumable(): +def resumable_get(): resumableIdentfier = request.args.get('resumableIdentifier', type=str) resumableFilename = request.args.get('resumableFilename', type=str) resumableChunkNumber = request.args.get('resumableChunkNumber', type=int) @@ -83,6 +83,15 @@ def resumable_post(): return 'OK' +@app.route("/resumable.js", methods=['GET']) +def resumable_js(): + js_file = open('../../resumable.js', 'rb') + resp = make_response() + resp.headers["content-type"] = "application/javascript" + resp.set_data(js_file.read()) + return resp + + def get_chunk_name(uploaded_filename, chunk_number): return uploaded_filename + "_part_%03d" % chunk_number diff --git a/samples/python-flask/static/resumable.js b/samples/python-flask/static/resumable.js deleted file mode 100644 index cba0ed4a..00000000 --- a/samples/python-flask/static/resumable.js +++ /dev/null @@ -1,1172 +0,0 @@ -/* -* MIT Licensed -* http://www.23developer.com/opensource -* http://github.com/23/resumable.js -* Steffen Tiedemann Christensen, steffen@23company.com -*/ - -(function(){ -"use strict"; - - var Resumable = function(opts){ - if ( !(this instanceof Resumable) ) { - return new Resumable(opts); - } - this.version = 1.0; - // SUPPORTED BY BROWSER? - // Check if these features are support by the browser: - // - File object type - // - Blob object type - // - FileList object type - // - slicing files - this.support = ( - (typeof(File)!=='undefined') - && - (typeof(Blob)!=='undefined') - && - (typeof(FileList)!=='undefined') - && - (!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||!!Blob.prototype.slice||false) - ); - if(!this.support) return(false); - - - // PROPERTIES - var $ = this; - $.files = []; - $.defaults = { - chunkSize:1*1024*1024, - forceChunkSize:false, - simultaneousUploads:3, - fileParameterName:'file', - chunkNumberParameterName: 'resumableChunkNumber', - chunkSizeParameterName: 'resumableChunkSize', - currentChunkSizeParameterName: 'resumableCurrentChunkSize', - totalSizeParameterName: 'resumableTotalSize', - typeParameterName: 'resumableType', - identifierParameterName: 'resumableIdentifier', - fileNameParameterName: 'resumableFilename', - relativePathParameterName: 'resumableRelativePath', - totalChunksParameterName: 'resumableTotalChunks', - dragOverClass: 'dragover', - throttleProgressCallbacks: 0.5, - query:{}, - headers:{}, - preprocess:null, - preprocessFile:null, - method:'multipart', - uploadMethod: 'POST', - testMethod: 'GET', - prioritizeFirstAndLastChunk:false, - target:'/', - testTarget: null, - parameterNamespace:'', - testChunks:true, - generateUniqueIdentifier:null, - getTarget:null, - maxChunkRetries:100, - chunkRetryInterval:undefined, - permanentErrors:[400, 401, 403, 404, 409, 415, 500, 501], - maxFiles:undefined, - withCredentials:false, - xhrTimeout:0, - clearInput:true, - chunkFormat:'blob', - setChunkTypeFromFile:false, - maxFilesErrorCallback:function (files, errorCount) { - var maxFiles = $.getOpt('maxFiles'); - alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); - }, - minFileSize:1, - minFileSizeErrorCallback:function(file, errorCount) { - alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); - }, - maxFileSize:undefined, - maxFileSizeErrorCallback:function(file, errorCount) { - alert(file.fileName||file.name +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); - }, - fileType: [], - fileTypeErrorCallback: function(file, errorCount) { - alert(file.fileName||file.name +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); - } - }; - $.opts = opts||{}; - $.getOpt = function(o) { - var $opt = this; - // Get multiple option if passed an array - if(o instanceof Array) { - var options = {}; - $h.each(o, function(option){ - options[option] = $opt.getOpt(option); - }); - return options; - } - // Otherwise, just return a simple option - if ($opt instanceof ResumableChunk) { - if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } - else { $opt = $opt.fileObj; } - } - if ($opt instanceof ResumableFile) { - if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } - else { $opt = $opt.resumableObj; } - } - if ($opt instanceof Resumable) { - if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } - else { return $opt.defaults[o]; } - } - }; - $.indexOf = function(array, obj) { - if (array.indexOf) { return array.indexOf(obj); } - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { return i; } - } - return -1; - } - - // EVENTS - // catchAll(event, ...) - // fileSuccess(file), fileProgress(file), fileAdded(file, event), filesAdded(files, filesSkipped), fileRetry(file), - // fileError(file, message), complete(), progress(), error(message, file), pause() - $.events = []; - $.on = function(event,callback){ - $.events.push(event.toLowerCase(), callback); - }; - $.fire = function(){ - // `arguments` is an object, not array, in FF, so: - var args = []; - for (var i=0; i= 0) { // only for file drop - e.stopPropagation(); - dt.dropEffect = "copy"; - dt.effectAllowed = "copy"; - e.currentTarget.classList.add($.getOpt('dragOverClass')); - } else { // not work on IE/Edge.... - dt.dropEffect = "none"; - dt.effectAllowed = "none"; - } - }; - - /** - * processes a single upload item (file or directory) - * @param {Object} item item to upload, may be file or directory entry - * @param {string} path current file path - * @param {File[]} items list of files to append new items to - * @param {Function} cb callback invoked when item is processed - */ - function processItem(item, path, items, cb) { - var entry; - if(item.isFile){ - // file provided - return item.file(function(file){ - file.relativePath = path + file.name; - items.push(file); - cb(); - }); - }else if(item.isDirectory){ - // item is already a directory entry, just assign - entry = item; - }else if(item instanceof File) { - items.push(item); - } - if('function' === typeof item.webkitGetAsEntry){ - // get entry from file object - entry = item.webkitGetAsEntry(); - } - if(entry && entry.isDirectory){ - // directory provided, process it - return processDirectory(entry, path + entry.name + '/', items, cb); - } - if('function' === typeof item.getAsFile){ - // item represents a File object, convert it - item = item.getAsFile(); - if(item instanceof File) { - item.relativePath = path + item.name; - items.push(item); - } - } - cb(); // indicate processing is done - } - - - /** - * cps-style list iteration. - * invokes all functions in list and waits for their callback to be - * triggered. - * @param {Function[]} items list of functions expecting callback parameter - * @param {Function} cb callback to trigger after the last callback has been invoked - */ - function processCallbacks(items, cb){ - if(!items || items.length === 0){ - // empty or no list, invoke callback - return cb(); - } - // invoke current function, pass the next part as continuation - items[0](function(){ - processCallbacks(items.slice(1), cb); - }); - } - - /** - * recursively traverse directory and collect files to upload - * @param {Object} directory directory to process - * @param {string} path current path - * @param {File[]} items target list of items - * @param {Function} cb callback invoked after traversing directory - */ - function processDirectory (directory, path, items, cb) { - var dirReader = directory.createReader(); - var allEntries = []; - - function readEntries () { - dirReader.readEntries(function(entries){ - if (entries.length) { - allEntries = allEntries.concat(entries); - return readEntries(); - } - - // process all conversion callbacks, finally invoke own one - processCallbacks( - allEntries.map(function(entry){ - // bind all properties except for callback - return processItem.bind(null, entry, path, items); - }), - cb - ); - }); - } - - readEntries(); - } - - /** - * process items to extract files to be uploaded - * @param {File[]} items items to process - * @param {Event} event event that led to upload - */ - function loadFiles(items, event) { - if(!items.length){ - return; // nothing to do - } - $.fire('beforeAdd'); - var files = []; - processCallbacks( - Array.prototype.map.call(items, function(item){ - // bind all properties except for callback - var entry = item; - if('function' === typeof item.webkitGetAsEntry){ - entry = item.webkitGetAsEntry(); - } - return processItem.bind(null, entry, "", files); - }), - function(){ - if(files.length){ - // at least one file found - appendFilesFromFileList(files, event); - } - } - ); - }; - - var appendFilesFromFileList = function(fileList, event){ - // check for uploading too many files - var errorCount = 0; - var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']); - if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) { - // if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file - if (o.maxFiles===1 && $.files.length===1 && fileList.length===1) { - $.removeFile($.files[0]); - } else { - o.maxFilesErrorCallback(fileList, errorCount++); - return false; - } - } - var files = [], filesSkipped = [], remaining = fileList.length; - var decreaseReamining = function(){ - if(!--remaining){ - // all files processed, trigger event - if(!files.length && !filesSkipped.length){ - // no succeeded files, just skip - return; - } - window.setTimeout(function(){ - $.fire('filesAdded', files, filesSkipped); - },0); - } - }; - $h.each(fileList, function(file){ - var fileName = file.name; - var fileType = file.type; // e.g video/mp4 - if(o.fileType.length > 0){ - var fileTypeFound = false; - for(var index in o.fileType){ - // For good behaviour we do some inital sanitizing. Remove spaces and lowercase all - o.fileType[index] = o.fileType[index].replace(/\s/g, '').toLowerCase(); - - // Allowing for both [extension, .extension, mime/type, mime/*] - var extension = ((o.fileType[index].match(/^[^.][^/]+$/)) ? '.' : '') + o.fileType[index]; - - if ((fileName.substr(-1 * extension.length).toLowerCase() === extension) || - //If MIME type, check for wildcard or if extension matches the files tiletype - (extension.indexOf('/') !== -1 && ( - (extension.indexOf('*') !== -1 && fileType.substr(0, extension.indexOf('*')) === extension.substr(0, extension.indexOf('*'))) || - fileType === extension - )) - ){ - fileTypeFound = true; - break; - } - } - if (!fileTypeFound) { - o.fileTypeErrorCallback(file, errorCount++); - return true; - } - } - - if (typeof(o.minFileSize)!=='undefined' && file.sizeo.maxFileSize) { - o.maxFileSizeErrorCallback(file, errorCount++); - return true; - } - - function addFile(uniqueIdentifier){ - if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ - file.uniqueIdentifier = uniqueIdentifier; - var f = new ResumableFile($, file, uniqueIdentifier); - $.files.push(f); - files.push(f); - f.container = (typeof event != 'undefined' ? event.srcElement : null); - window.setTimeout(function(){ - $.fire('fileAdded', f, event) - },0); - })()} else { - filesSkipped.push(file); - }; - decreaseReamining(); - } - // directories have size == 0 - var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); - if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ - // Promise or Promise-like object provided as unique identifier - uniqueIdentifier - .then( - function(uniqueIdentifier){ - // unique identifier generation succeeded - addFile(uniqueIdentifier); - }, - function(){ - // unique identifier generation failed - // skip further processing, only decrease file count - decreaseReamining(); - } - ); - }else{ - // non-Promise provided as unique identifier, process synchronously - addFile(uniqueIdentifier); - } - }); - }; - - // INTERNAL OBJECT TYPES - function ResumableFile(resumableObj, file, uniqueIdentifier){ - var $ = this; - $.opts = {}; - $.getOpt = resumableObj.getOpt; - $._prevProgress = 0; - $.resumableObj = resumableObj; - $.file = file; - $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox - $.size = file.size; - $.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; - $.uniqueIdentifier = uniqueIdentifier; - $._pause = false; - $.container = ''; - $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished - var _error = uniqueIdentifier !== undefined; - - // Callback when something happens within the chunk - var chunkEvent = function(event, message){ - // event can be 'progress', 'success', 'error' or 'retry' - switch(event){ - case 'progress': - $.resumableObj.fire('fileProgress', $, message); - break; - case 'error': - $.abort(); - _error = true; - $.chunks = []; - $.resumableObj.fire('fileError', $, message); - break; - case 'success': - if(_error) return; - $.resumableObj.fire('fileProgress', $, message); // it's at least progress - if($.isComplete()) { - $.resumableObj.fire('fileSuccess', $, message); - } - break; - case 'retry': - $.resumableObj.fire('fileRetry', $); - break; - } - }; - - // Main code to set up a file object with chunks, - // packaged to be able to handle retries if needed. - $.chunks = []; - $.abort = function(){ - // Stop current uploads - var abortCount = 0; - $h.each($.chunks, function(c){ - if(c.status()=='uploading') { - c.abort(); - abortCount++; - } - }); - if(abortCount>0) $.resumableObj.fire('fileProgress', $); - }; - $.cancel = function(){ - // Reset this file to be void - var _chunks = $.chunks; - $.chunks = []; - // Stop current uploads - $h.each(_chunks, function(c){ - if(c.status()=='uploading') { - c.abort(); - $.resumableObj.uploadNextChunk(); - } - }); - $.resumableObj.removeFile($); - $.resumableObj.fire('fileProgress', $); - }; - $.retry = function(){ - $.bootstrap(); - var firedRetry = false; - $.resumableObj.on('chunkingComplete', function(){ - if(!firedRetry) $.resumableObj.upload(); - firedRetry = true; - }); - }; - $.bootstrap = function(){ - $.abort(); - _error = false; - // Rebuild stack of chunks from file - $.chunks = []; - $._prevProgress = 0; - var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; - var maxOffset = Math.max(round($.file.size/$.getOpt('chunkSize')),1); - for (var offset=0; offset0.99999 ? 1 : ret)); - ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused - $._prevProgress = ret; - return(ret); - }; - $.isUploading = function(){ - var uploading = false; - $h.each($.chunks, function(chunk){ - if(chunk.status()=='uploading') { - uploading = true; - return(false); - } - }); - return(uploading); - }; - $.isComplete = function(){ - var outstanding = false; - if ($.preprocessState === 1) { - return(false); - } - $h.each($.chunks, function(chunk){ - var status = chunk.status(); - if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { - outstanding = true; - return(false); - } - }); - return(!outstanding); - }; - $.pause = function(pause){ - if(typeof(pause)==='undefined'){ - $._pause = ($._pause ? false : true); - }else{ - $._pause = pause; - } - }; - $.isPaused = function() { - return $._pause; - }; - $.preprocessFinished = function(){ - $.preprocessState = 2; - $.upload(); - }; - $.upload = function () { - var found = false; - if ($.isPaused() === false) { - var preprocess = $.getOpt('preprocessFile'); - if(typeof preprocess === 'function') { - switch($.preprocessState) { - case 0: $.preprocessState = 1; preprocess($); return(true); - case 1: return(true); - case 2: break; - } - } - $h.each($.chunks, function (chunk) { - if (chunk.status() == 'pending' && chunk.preprocessState !== 1) { - chunk.send(); - found = true; - return(false); - } - }); - } - return(found); - } - $.markChunksCompleted = function (chunkNumber) { - if (!$.chunks || $.chunks.length <= chunkNumber) { - return; - } - for (var num = 0; num < chunkNumber; num++) { - $.chunks[num].markComplete = true; - } - }; - - // Bootstrap and return - $.resumableObj.fire('chunkingStart', $); - $.bootstrap(); - return(this); - } - - - function ResumableChunk(resumableObj, fileObj, offset, callback){ - var $ = this; - $.opts = {}; - $.getOpt = resumableObj.getOpt; - $.resumableObj = resumableObj; - $.fileObj = fileObj; - $.fileObjSize = fileObj.size; - $.fileObjType = fileObj.file.type; - $.offset = offset; - $.callback = callback; - $.lastProgressCallback = (new Date); - $.tested = false; - $.retries = 0; - $.pendingRetry = false; - $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished - $.markComplete = false; - - // Computed properties - var chunkSize = $.getOpt('chunkSize'); - $.loaded = 0; - $.startByte = $.offset*chunkSize; - $.endByte = Math.min($.fileObjSize, ($.offset+1)*chunkSize); - if ($.fileObjSize-$.endByte < chunkSize && !$.getOpt('forceChunkSize')) { - // The last chunk will be bigger than the chunk size, but less than 2*chunkSize - $.endByte = $.fileObjSize; - } - $.xhr = null; - - // test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session - $.test = function(){ - // Set up request and listen for event - $.xhr = new XMLHttpRequest(); - - var testHandler = function(e){ - $.tested = true; - var status = $.status(); - if(status=='success') { - $.callback(status, $.message()); - $.resumableObj.uploadNextChunk(); - } else { - $.send(); - } - }; - $.xhr.addEventListener('load', testHandler, false); - $.xhr.addEventListener('error', testHandler, false); - $.xhr.addEventListener('timeout', testHandler, false); - - // Add data from the query options - var params = []; - var parameterNamespace = $.getOpt('parameterNamespace'); - var customQuery = $.getOpt('query'); - if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); - $h.each(customQuery, function(k,v){ - params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); - }); - // Add extra data to identify chunk - params = params.concat( - [ - // define key/value pairs for additional parameters - ['chunkNumberParameterName', $.offset + 1], - ['chunkSizeParameterName', $.getOpt('chunkSize')], - ['currentChunkSizeParameterName', $.endByte - $.startByte], - ['totalSizeParameterName', $.fileObjSize], - ['typeParameterName', $.fileObjType], - ['identifierParameterName', $.fileObj.uniqueIdentifier], - ['fileNameParameterName', $.fileObj.fileName], - ['relativePathParameterName', $.fileObj.relativePath], - ['totalChunksParameterName', $.fileObj.chunks.length] - ].filter(function(pair){ - // include items that resolve to truthy values - // i.e. exclude false, null, undefined and empty strings - return $.getOpt(pair[0]); - }) - .map(function(pair){ - // map each key/value pair to its final form - return [ - parameterNamespace + $.getOpt(pair[0]), - encodeURIComponent(pair[1]) - ].join('='); - }) - ); - // Append the relevant chunk and send it - $.xhr.open($.getOpt('testMethod'), $h.getTarget('test', params)); - $.xhr.timeout = $.getOpt('xhrTimeout'); - $.xhr.withCredentials = $.getOpt('withCredentials'); - // Add data from header options - var customHeaders = $.getOpt('headers'); - if(typeof customHeaders === 'function') { - customHeaders = customHeaders($.fileObj, $); - } - $h.each(customHeaders, function(k,v) { - $.xhr.setRequestHeader(k, v); - }); - $.xhr.send(null); - }; - - $.preprocessFinished = function(){ - $.preprocessState = 2; - $.send(); - }; - - // send() uploads the actual data in a POST call - $.send = function(){ - var preprocess = $.getOpt('preprocess'); - if(typeof preprocess === 'function') { - switch($.preprocessState) { - case 0: $.preprocessState = 1; preprocess($); return; - case 1: return; - case 2: break; - } - } - if($.getOpt('testChunks') && !$.tested) { - $.test(); - return; - } - - // Set up request and listen for event - $.xhr = new XMLHttpRequest(); - - // Progress - $.xhr.upload.addEventListener('progress', function(e){ - if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) { - $.callback('progress'); - $.lastProgressCallback = (new Date); - } - $.loaded=e.loaded||0; - }, false); - $.loaded = 0; - $.pendingRetry = false; - $.callback('progress'); - - // Done (either done, failed or retry) - var doneHandler = function(e){ - var status = $.status(); - if(status=='success'||status=='error') { - $.callback(status, $.message()); - $.resumableObj.uploadNextChunk(); - } else { - $.callback('retry', $.message()); - $.abort(); - $.retries++; - var retryInterval = $.getOpt('chunkRetryInterval'); - if(retryInterval !== undefined) { - $.pendingRetry = true; - setTimeout($.send, retryInterval); - } else { - $.send(); - } - } - }; - $.xhr.addEventListener('load', doneHandler, false); - $.xhr.addEventListener('error', doneHandler, false); - $.xhr.addEventListener('timeout', doneHandler, false); - - // Set up the basic query data from Resumable - var query = [ - ['chunkNumberParameterName', $.offset + 1], - ['chunkSizeParameterName', $.getOpt('chunkSize')], - ['currentChunkSizeParameterName', $.endByte - $.startByte], - ['totalSizeParameterName', $.fileObjSize], - ['typeParameterName', $.fileObjType], - ['identifierParameterName', $.fileObj.uniqueIdentifier], - ['fileNameParameterName', $.fileObj.fileName], - ['relativePathParameterName', $.fileObj.relativePath], - ['totalChunksParameterName', $.fileObj.chunks.length], - ].filter(function(pair){ - // include items that resolve to truthy values - // i.e. exclude false, null, undefined and empty strings - return $.getOpt(pair[0]); - }) - .reduce(function(query, pair){ - // assign query key/value - query[$.getOpt(pair[0])] = pair[1]; - return query; - }, {}); - // Mix in custom data - var customQuery = $.getOpt('query'); - if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); - $h.each(customQuery, function(k,v){ - query[k] = v; - }); - - var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))); - var bytes = $.fileObj.file[func]($.startByte, $.endByte, $.getOpt('setChunkTypeFromFile') ? $.fileObj.file.type : ""); - var data = null; - var params = []; - - var parameterNamespace = $.getOpt('parameterNamespace'); - if ($.getOpt('method') === 'octet') { - // Add data from the query options - data = bytes; - $h.each(query, function (k, v) { - params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); - }); - } else { - // Add data from the query options - data = new FormData(); - $h.each(query, function (k, v) { - data.append(parameterNamespace + k, v); - params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); - }); - if ($.getOpt('chunkFormat') == 'blob') { - data.append(parameterNamespace + $.getOpt('fileParameterName'), bytes, $.fileObj.fileName); - } - else if ($.getOpt('chunkFormat') == 'base64') { - var fr = new FileReader(); - fr.onload = function (e) { - data.append(parameterNamespace + $.getOpt('fileParameterName'), fr.result); - $.xhr.send(data); - } - fr.readAsDataURL(bytes); - } - } - - var target = $h.getTarget('upload', params); - var method = $.getOpt('uploadMethod'); - - $.xhr.open(method, target); - if ($.getOpt('method') === 'octet') { - $.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); - } - $.xhr.timeout = $.getOpt('xhrTimeout'); - $.xhr.withCredentials = $.getOpt('withCredentials'); - // Add data from header options - var customHeaders = $.getOpt('headers'); - if(typeof customHeaders === 'function') { - customHeaders = customHeaders($.fileObj, $); - } - - $h.each(customHeaders, function(k,v) { - $.xhr.setRequestHeader(k, v); - }); - - if ($.getOpt('chunkFormat') == 'blob') { - $.xhr.send(data); - } - }; - $.abort = function(){ - // Abort and reset - if($.xhr) $.xhr.abort(); - $.xhr = null; - }; - $.status = function(){ - // Returns: 'pending', 'uploading', 'success', 'error' - if($.pendingRetry) { - // if pending retry then that's effectively the same as actively uploading, - // there might just be a slight delay before the retry starts - return('uploading'); - } else if($.markComplete) { - return 'success'; - } else if(!$.xhr) { - return('pending'); - } else if($.xhr.readyState<4) { - // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening - return('uploading'); - } else { - if($.xhr.status == 200 || $.xhr.status == 201) { - // HTTP 200, 201 (created) - return('success'); - } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { - // HTTP 400, 404, 409, 415, 500, 501 (permanent error) - return('error'); - } else { - // this should never happen, but we'll reset and queue a retry - // a likely case for this would be 503 service unavailable - $.abort(); - return('pending'); - } - } - }; - $.message = function(){ - return($.xhr ? $.xhr.responseText : ''); - }; - $.progress = function(relative){ - if(typeof(relative)==='undefined') relative = false; - var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1); - if($.pendingRetry) return(0); - if((!$.xhr || !$.xhr.status) && !$.markComplete) factor*=.95; - var s = $.status(); - switch(s){ - case 'success': - case 'error': - return(1*factor); - case 'pending': - return(0*factor); - default: - return($.loaded/($.endByte-$.startByte)*factor); - } - }; - return(this); - } - - // QUEUE - $.uploadNextChunk = function(){ - var found = false; - - // In some cases (such as videos) it's really handy to upload the first - // and last chunk of a file quickly; this let's the server check the file's - // metadata and determine if there's even a point in continuing. - if ($.getOpt('prioritizeFirstAndLastChunk')) { - $h.each($.files, function(file){ - if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) { - file.chunks[0].send(); - found = true; - return(false); - } - if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[file.chunks.length-1].preprocessState === 0) { - file.chunks[file.chunks.length-1].send(); - found = true; - return(false); - } - }); - if(found) return(true); - } - - // Now, simply look for the next, best thing to upload - $h.each($.files, function(file){ - found = file.upload(); - if(found) return(false); - }); - if(found) return(true); - - // The are no more outstanding chunks to upload, check is everything is done - var outstanding = false; - $h.each($.files, function(file){ - if(!file.isComplete()) { - outstanding = true; - return(false); - } - }); - if(!outstanding) { - // All chunks have been uploaded, complete - $.fire('complete'); - } - return(false); - }; - - - // PUBLIC METHODS FOR RESUMABLE.JS - $.assignBrowse = function(domNodes, isDirectory){ - if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; - $h.each(domNodes, function(domNode) { - var input; - if(domNode.tagName==='INPUT' && domNode.type==='file'){ - input = domNode; - } else { - input = document.createElement('input'); - input.setAttribute('type', 'file'); - input.style.display = 'none'; - domNode.addEventListener('click', function(){ - input.style.opacity = 0; - input.style.display='block'; - input.focus(); - input.click(); - input.style.display='none'; - }, false); - domNode.appendChild(input); - } - var maxFiles = $.getOpt('maxFiles'); - if (typeof(maxFiles)==='undefined'||maxFiles!=1){ - input.setAttribute('multiple', 'multiple'); - } else { - input.removeAttribute('multiple'); - } - if(isDirectory){ - input.setAttribute('webkitdirectory', 'webkitdirectory'); - } else { - input.removeAttribute('webkitdirectory'); - } - var fileTypes = $.getOpt('fileType'); - if (typeof (fileTypes) !== 'undefined' && fileTypes.length >= 1) { - input.setAttribute('accept', fileTypes.map(function (e) { - e = e.replace(/\s/g, '').toLowerCase(); - if(e.match(/^[^.][^/]+$/)){ - e = '.' + e; - } - return e; - }).join(',')); - } - else { - input.removeAttribute('accept'); - } - // When new files are added, simply append them to the overall list - input.addEventListener('change', function(e){ - appendFilesFromFileList(e.target.files,e); - var clearInput = $.getOpt('clearInput'); - if (clearInput) { - e.target.value = ''; - } - }, false); - }); - }; - $.assignDrop = function(domNodes){ - if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; - - $h.each(domNodes, function(domNode) { - domNode.addEventListener('dragover', onDragOverEnter, false); - domNode.addEventListener('dragenter', onDragOverEnter, false); - domNode.addEventListener('dragleave', onDragLeave, false); - domNode.addEventListener('drop', onDrop, false); - }); - }; - $.unAssignDrop = function(domNodes) { - if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; - - $h.each(domNodes, function(domNode) { - domNode.removeEventListener('dragover', onDragOverEnter); - domNode.removeEventListener('dragenter', onDragOverEnter); - domNode.removeEventListener('dragleave', onDragLeave); - domNode.removeEventListener('drop', onDrop); - }); - }; - $.isUploading = function(){ - var uploading = false; - $h.each($.files, function(file){ - if (file.isUploading()) { - uploading = true; - return(false); - } - }); - return(uploading); - }; - $.upload = function(){ - // Make sure we don't start too many uploads at once - if($.isUploading()) return; - // Kick off the queue - $.fire('uploadStart'); - for (var num=1; num<=$.getOpt('simultaneousUploads'); num++) { - $.uploadNextChunk(); - } - }; - $.pause = function(){ - // Resume all chunks currently being uploaded - $h.each($.files, function(file){ - file.abort(); - }); - $.fire('pause'); - }; - $.cancel = function(){ - $.fire('beforeCancel'); - for(var i = $.files.length - 1; i >= 0; i--) { - $.files[i].cancel(); - } - $.fire('cancel'); - }; - $.progress = function(){ - var totalDone = 0; - var totalSize = 0; - // Resume all chunks currently being uploaded - $h.each($.files, function(file){ - totalDone += file.progress()*file.size; - totalSize += file.size; - }); - return(totalSize>0 ? totalDone/totalSize : 0); - }; - $.addFile = function(file, event){ - appendFilesFromFileList([file], event); - }; - $.addFiles = function(files, event){ - appendFilesFromFileList(files, event); - }; - $.removeFile = function(file){ - for(var i = $.files.length - 1; i >= 0; i--) { - if($.files[i] === file) { - $.files.splice(i, 1); - } - } - }; - $.getFromUniqueIdentifier = function(uniqueIdentifier){ - var ret = false; - $h.each($.files, function(f){ - if(f.uniqueIdentifier==uniqueIdentifier) ret = f; - }); - return(ret); - }; - $.getSize = function(){ - var totalSize = 0; - $h.each($.files, function(file){ - totalSize += file.size; - }); - return(totalSize); - }; - $.handleDropEvent = function (e) { - onDrop(e); - }; - $.handleChangeEvent = function (e) { - appendFilesFromFileList(e.target.files, e); - e.target.value = ''; - }; - $.updateQuery = function(query){ - $.opts.query = query; - }; - - return(this); - }; - - - // Node.js-style export for Node and Component - if (typeof module != 'undefined') { - // left here for backwards compatibility - module.exports = Resumable; - module.exports.Resumable = Resumable; - } else if (typeof define === "function" && define.amd) { - // AMD/requirejs: Define the module - define(function(){ - return Resumable; - }); - } else { - // Browser: Expose to window - window.Resumable = Resumable; - } - -})(); diff --git a/samples/python-flask/templates/index.html b/samples/python-flask/templates/index.html index 9f384ff4..4c1c3e5a 100644 --- a/samples/python-flask/templates/index.html +++ b/samples/python-flask/templates/index.html @@ -19,7 +19,7 @@

    Resumable.js

    Demo

    - +
    Your browser, unfortunately, is not supported by Resumable.js. The library requires support for the HTML5 File API along with file slicing. From dd6f2890017ca560f66d5da26a633a8d1cfa09f1 Mon Sep 17 00:00:00 2001 From: GuDuJian-J-Zhang <458195880@qq.com> Date: Thu, 4 Apr 2019 10:48:13 +0800 Subject: [PATCH 3/3] change route api --- samples/python-flask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/python-flask/app.py b/samples/python-flask/app.py index ec383328..b614bd25 100644 --- a/samples/python-flask/app.py +++ b/samples/python-flask/app.py @@ -4,11 +4,11 @@ app = Flask(__name__, static_folder='static', static_url_path='/static') app.debug = True -temp_base = os.path.expanduser("/home/parallels/work/workroot/uploads") +temp_base = os.path.expanduser("/home/tmp/uploads") # landing page -@app.route("/") +@app.route("/api/upload") def resumable_example(): return render_template("index.html") @@ -83,7 +83,7 @@ def resumable_post(): return 'OK' -@app.route("/resumable.js", methods=['GET']) +@app.route("/api/resumable.js", methods=['GET']) def resumable_js(): js_file = open('../../resumable.js', 'rb') resp = make_response()