From 9abccee4a98508ca4519941df6ecef24892d6ef2 Mon Sep 17 00:00:00 2001 From: Vladimir Stankovic Date: Mon, 6 Jul 2020 12:21:03 +0200 Subject: [PATCH 1/4] Chrome 83.0.4103.61, Windows 10.0.18362 Chrome's FileSystem API has a bug that files from dropped folders or files from input dialog's selected folder, with read errors (has absolute paths which exceed 260 chars) will have zero file size. When dropped folder has any file with absolute paths which exceeds 260 chars, upload should report read errors. When input dialog is used to select folder, files with absolute paths which exceeds 260 chars, will be uploaded with zero length. These files should not be uploaded, it should report read errors. --- README.md | 1 + src/flow.js | 200 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 149 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index f7eab970..c39c804f 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ added. * `.fileRetry(file, chunk)` Something went wrong during upload of a specific file, uploading is being retried. * `.fileError(file, message, chunk)` An error occurred during upload of a specific file. +* `.readErrors(files, folders, event)` This event fires before fileAdded or filesAdded events only if errors occur while reading files or folders. First argument `files` is collection of files read errors, second argument `folders` is collection of folder read errors. * `.uploadStart()` Upload has been started on the Flow object. * `.complete()` Uploading completed. * `.progress()` Uploading progress. diff --git a/src/flow.js b/src/flow.js index 90073301..6200dfb3 100644 --- a/src/flow.js +++ b/src/flow.js @@ -230,55 +230,109 @@ */ webkitReadDataTransfer: function (event) { var $ = this; - var queue = event.dataTransfer.items.length; - var files = []; - each(event.dataTransfer.items, function (item) { - var entry = item.webkitGetAsEntry(); - if (!entry) { - decrement(); - return ; + getEntries(event.dataTransfer.items).then(function (result) { + getFiles(result.files).then(function (entries) { + var files = []; + var errors = []; + each(entries, function (entry) { + if (entry.error) { + errors.push(entry); + } else { + files.push(entry); + } + }); + if (result.errors.length || errors.length) { + $.fire('readErrors', errors, result.errors, event); + } + if (files.length) { + $.addFiles(files, event); + } + }); + }); + function getEntries(items) { + var files = []; + var errors = []; + var promises = []; + + function readEntry(entry, promises) { + if (entry.isFile) { + files.push(entry); + } else if (entry.isDirectory) { + promises.push(readDirectory(entry)); + } } - if (entry.isFile) { - // due to a bug in Chrome's File System API impl - #149735 - fileReadSuccess(item.getAsFile(), entry.fullPath); - } else { - readDirectory(entry.createReader()); + + function readDirectory(entry) { + var reader = entry.createReader(); + return new Promise(function (resolve, reject) { + var promises = []; + readEntries(entry, reader, promises, resolve); + }); } - }); - function readDirectory(reader) { - reader.readEntries(function (entries) { - if (entries.length) { - queue += entries.length; - each(entries, function(entry) { - if (entry.isFile) { - var fullPath = entry.fullPath; - entry.file(function (file) { - fileReadSuccess(file, fullPath); - }, readError); - } else if (entry.isDirectory) { - readDirectory(entry.createReader()); - } + + function readEntries(entry, reader, promises, resolve) { + reader.readEntries(function (entries) { + if (entries.length) { + var promises2 = []; + each(entries, function (entry2) { + readEntry(entry2, promises2); + }); + promises.push(Promise.all(promises2)); + readEntries(entry, reader, promises, resolve); + return; + } + resolve(Promise.all(promises)); + }, function (error) { + errors.push({ + path: entry.fullPath, + error: error }); - readDirectory(reader); - } else { - decrement(); + resolve(promises); + }); + } + + each(items, function (item) { + var entry = item.webkitGetAsEntry(); + if (!entry) { + return; + } + if (entry.isFile) { + // due to a bug in Chrome's File System API impl - #149735 + files.push(getFile(item.getAsFile(), entry.fullPath)); + return; } - }, readError); + readEntry(entry, promises); + }); + + return new Promise(function (resolve, reject) { + return Promise.all(promises).then(function () { + resolve({ files: files, errors: errors }); + }); + }); + } + function getFiles(entries) { + return Promise.all(entries.map(function (entry) { + return new Promise(function (resolve, reject) { + if (entry.file) { + var fullPath = entry.fullPath; + entry.file(function (file) { + resolve(getFile(file, fullPath)); + }, function (file) { + resolve({ + path: entry.fullPath, + error: file + }); + }); + } else { + resolve(entry); + } + }); + })); } - function fileReadSuccess(file, fullPath) { + function getFile(file, fullPath) { // relative path should not start with "/" file.relativePath = fullPath.substring(1); - files.push(file); - decrement(); - } - function readError(fileError) { - decrement(); - throw fileError; - } - function decrement() { - if (--queue == 0) { - $.addFiles(files, event); - } + return file; } }, @@ -588,8 +642,12 @@ * @param {Event} [event] event is optional */ addFiles: function (fileList, event) { + var $ = this; var files = []; - each(fileList, function (file) { + var errors = []; + var promises = []; + + function addFile(file) { // https://github.com/flowjs/flow.js/issues/55 if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) { var uniqueIdentifier = this.generateUniqueIdentifier(file); @@ -600,16 +658,54 @@ } } } - }, this); - if (this.fire('filesAdded', files, event)) { - each(files, function (file) { - if (this.opts.singleFile && this.files.length > 0) { - this.removeFile(this.files[0]); - } - this.files.push(file); - }, this); - this.fire('filesSubmitted', files, event); } + + /** + * Chrome's FileSystem API has a bug that files from dropped folders or files from input dialog's selected folder, + * with read errors (has absolute paths which exceed 260 chars) will have zero file size. + */ + function validateFile(file) { + // files with size greater than zero can upload + if (file.size > 0) { + addFile.bind($)(file); + return; + } + + // try to read from from zero size file, + // if error occurs than file cannot be uploaded + promises.push(new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onloadend = function () { + if (reader.error) { + errors.push({ + path: file.webkitRelativePath || file.name, + error: reader.error + }); + } else { + addFile.bind($)(file); + } + resolve(); + }.bind($); + reader.readAsArrayBuffer(file); + })); + } + + each(fileList, validateFile); + + Promise.all(promises).then(function () { + if (errors.length) { + this.fire('readErrors', errors, [], event); + } + if (this.fire('filesAdded', files, event)) { + each(files, function (file) { + if (this.opts.singleFile && this.files.length > 0) { + this.removeFile(this.files[0]); + } + this.files.push(file); + }, this); + this.fire('filesSubmitted', files, event); + } + }.bind(this)); }, From 989c2ab0dd44605616f5e54fc8a9f235c1041c8f Mon Sep 17 00:00:00 2001 From: Vladimir Stankovic Date: Wed, 26 May 2021 15:52:25 +0200 Subject: [PATCH 2/4] Change server request parameters names --- README.md | 14 +++++++------- dist/flow.js | 16 ++++++++-------- dist/flow.min.js | 2 +- samples/Backend on Go.md | 6 +++--- samples/Backend on Haskell.md | 14 +++++++------- samples/Backend on PHP.md | 16 ++++++++-------- samples/Node.js/flow-node.js | 20 ++++++++++---------- samples/Ruby backend in Sinatra.md | 8 ++++---- src/flow.js | 16 ++++++++-------- 9 files changed, 56 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index c39c804f..45e2da29 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,13 @@ Most of the magic for Flow.js happens in the user's browser, but files still nee To handle the state of upload chunks, a number of extra parameters are sent along with all requests: -* `flowChunkNumber`: The index of the chunk in the current upload. First chunk is `1` (no base-0 counting here). -* `flowTotalChunks`: The total number of chunks. -* `flowChunkSize`: The general chunk size. Using this value and `flowTotalSize` you can calculate the total number of chunks. Please note that the size of the data received in the HTTP might be lower than `flowChunkSize` of this for the last chunk for a file. -* `flowTotalSize`: The total file size. -* `flowIdentifier`: A unique identifier for the file contained in the request. -* `flowFilename`: The original file name (since a bug in Firefox results in the file name not being transmitted in chunk multipart posts). -* `flowRelativePath`: The file's relative path when selecting a directory (defaults to file name in all browsers except Chrome). +* `chunkNumber`: The index of the chunk in the current upload. First chunk is `1` (no base-0 counting here). +* `totalChunks`: The total number of chunks. +* `chunkSize`: The general chunk size. Using this value and `totalSize` you can calculate the total number of chunks. Please note that the size of the data received in the HTTP might be lower than `chunkSize` of this for the last chunk for a file. +* `totalSize`: The total file size. +* `requestId`: A unique identifier for the file contained in the request. +* `filename`: The original file name (since a bug in Firefox results in the file name not being transmitted in chunk multipart posts). +* `relativePath`: The file's relative path when selecting a directory (defaults to file name in all browsers except Chrome). You should allow for the same chunk to be uploaded more than once; this isn't standard behaviour, but on an unstable network environment it could happen, and this case is exactly what Flow.js is designed for. diff --git a/dist/flow.js b/dist/flow.js index e61f0a75..4fd17ff6 100644 --- a/dist/flow.js +++ b/dist/flow.js @@ -1280,14 +1280,14 @@ */ getParams: function () { return { - flowChunkNumber: this.offset + 1, - flowChunkSize: this.chunkSize, - flowCurrentChunkSize: this.endByte - this.startByte, - flowTotalSize: this.fileObj.size, - flowIdentifier: this.fileObj.uniqueIdentifier, - flowFilename: this.fileObj.name, - flowRelativePath: this.fileObj.relativePath, - flowTotalChunks: this.fileObj.chunks.length + chunkNumber: this.offset + 1, + chunkSize: this.chunkSize, + currentChunkSize: this.endByte - this.startByte, + totalSize: this.fileObj.size, + requestId: this.fileObj.uniqueIdentifier, + filename: this.fileObj.name, + relativePath: this.fileObj.relativePath, + totalChunks: this.fileObj.chunks.length, }; }, diff --git a/dist/flow.min.js b/dist/flow.min.js index ef301778..b61ab2a1 100644 --- a/dist/flow.min.js +++ b/dist/flow.min.js @@ -1,2 +1,2 @@ /*! @flowjs/flow.js 2.14.1 */ -!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/Chrome/.test(a.navigator.userAgent)||/Firefox/.test(a.navigator.userAgent)||/Edge/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,changeRawDataBeforeSend:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,allowDuplicateUploads:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,413,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1,initFileFn:null,readFileFn:f},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b,d){this.flowObj=a,this.bytes=null,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=d===c?a.generateUniqueIdentifier(b):d,this.chunkSize=0,this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c,d,e){var f="slice";a.file.slice?f="slice":a.file.mozSlice?f="mozSlice":a.file.webkitSlice&&(f="webkitSlice"),e.readFinished(a.file[f](b,c,d))}function g(a,b,c){this.flowObj=a,this.fileObj=b,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.readState=0,this.loaded=0,this.total=0,this.chunkSize=this.fileObj.chunkSize,this.startByte=this.offset*this.chunkSize,this.filename=null,this.computeEndByte=function(){var a=Math.min(this.fileObj.size,(this.offset+1)*this.chunkSize);return this.fileObj.size-a-1&&a.splice(c,1)}function i(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function j(a,b){setTimeout(a.bind(b),0)}function k(a,b){return l(arguments,function(b){b!==a&&l(b,function(b,c){a[c]=b})}),a}function l(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(l(this.files,function(a){if(a.paused||l(a.chunks,function(a){if("pending"===a.status())return a.send(),b=!0,!1}),b)return!1}),b)return!0;var c=!1;return l(this.files,function(a){if(!a.isComplete())return c=!0,!1}),c||a||j(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){a instanceof Element&&(a=[a]),l(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),k(f.style,{visibility:"hidden",position:"absolute",width:"1px",height:"1px"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),l(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){a.target.value&&(g.addFiles(a.target.files,a),a.target.value="")},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return l(this.files,function(b){if(b.isUploading())return a=!0,!1}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return l(this.files,function(d){l(d.chunks,function(d){if("uploading"===d.status()&&(a++,a>=c))return b=!1,!1})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||j(function(){this.fire("complete")},this)}},resume:function(){l(this.files,function(a){a.isComplete()||a.resume()})},pause:function(){l(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return l(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];l(a,function(a){if((!m||m&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)){var d=this.generateUniqueIdentifier(a);if(this.opts.allowDuplicateUploads||!this.getFromUniqueIdentifier(d)){var f=new e(this,a,d);this.fire("fileAdded",f,b)&&c.push(f)}}},this),this.fire("filesAdded",c,b)&&(l(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b))},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort(),this.fire("fileRemoved",a))},getFromUniqueIdentifier:function(a){var b=!1;return l(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return l(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return l(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return l(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallback.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return l(this.chunks,function(b){if("uploading"===b.status())return a=!0,!1}),a},isComplete:function(){var a=!1;return l(this.chunks,function(b){var c=b.status();if("pending"===c||"uploading"===c||"reading"===c||1===b.preprocessState||1===b.readState)return a=!0,!1}),!a},sizeUploaded:function(){var a=0;return l(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},g.prototype={getParams:function(){return{flowChunkNumber:this.offset+1,flowChunkSize:this.chunkSize,flowCurrentChunkSize:this.endByte-this.startByte,flowTotalSize:this.fileObj.size,flowIdentifier:this.fileObj.uniqueIdentifier,flowFilename:this.fileObj.name,flowRelativePath:this.fileObj.relativePath,flowTotalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return 0==b.length?a:(a+=a.indexOf("?")<0?"?":"&",a+b.join("&"))},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=i(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.endByte=this.computeEndByte(),this.preprocessState=2,this.send()},readFinished:function(a){this.readState=2,this.bytes=a,this.send()},send:function(){var a=this.flowObj.opts.preprocess,b=this.flowObj.opts.readFileFn;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}switch(this.readState){case 0:return this.readState=1,void b(this.fileObj,this.startByte,this.endByte,this.fileObj.file.type,this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1,this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var c=i(this.flowObj.opts.uploadMethod,this.fileObj,this),d=this.prepareXhrRequest(c,!1,this.flowObj.opts.method,this.bytes),e=this.flowObj.opts.changeRawDataBeforeSend;"function"==typeof e&&(d=e(this,d)),this.xhr.send(d)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return 1===this.readState?"reading":this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=i(this.flowObj.opts.query,this.fileObj,this,b);e=k(e||{},this.getParams());var f=i(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var h=[];l(e,function(a,b){h.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,h),g=d||null}else g=new FormData,l(e,function(a,b){g.append(b,a)}),"undefined"!=typeof d&&g.append(this.flowObj.opts.fileParameterName,d,this.filename||this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,l(i(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=i,d.extend=k,d.each=l,d.FlowFile=e,d.FlowChunk=g,d.version="2.14.1","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}("undefined"!=typeof window&&window,"undefined"!=typeof document&&document); \ No newline at end of file +!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/Chrome/.test(a.navigator.userAgent)||/Firefox/.test(a.navigator.userAgent)||/Edge/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,changeRawDataBeforeSend:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,allowDuplicateUploads:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,413,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1,initFileFn:null,readFileFn:f},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b,d){this.flowObj=a,this.bytes=null,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=d===c?a.generateUniqueIdentifier(b):d,this.chunkSize=0,this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c,d,e){var f="slice";a.file.slice?f="slice":a.file.mozSlice?f="mozSlice":a.file.webkitSlice&&(f="webkitSlice"),e.readFinished(a.file[f](b,c,d))}function g(a,b,c){this.flowObj=a,this.fileObj=b,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.readState=0,this.loaded=0,this.total=0,this.chunkSize=this.fileObj.chunkSize,this.startByte=this.offset*this.chunkSize,this.filename=null,this.computeEndByte=function(){var a=Math.min(this.fileObj.size,(this.offset+1)*this.chunkSize);return this.fileObj.size-a-1&&a.splice(c,1)}function i(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function j(a,b){setTimeout(a.bind(b),0)}function k(a,b){return l(arguments,function(b){b!==a&&l(b,function(b,c){a[c]=b})}),a}function l(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(l(this.files,function(a){if(a.paused||l(a.chunks,function(a){if("pending"===a.status())return a.send(),b=!0,!1}),b)return!1}),b)return!0;var c=!1;return l(this.files,function(a){if(!a.isComplete())return c=!0,!1}),c||a||j(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){a instanceof Element&&(a=[a]),l(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),k(f.style,{visibility:"hidden",position:"absolute",width:"1px",height:"1px"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),l(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){a.target.value&&(g.addFiles(a.target.files,a),a.target.value="")},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return l(this.files,function(b){if(b.isUploading())return a=!0,!1}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return l(this.files,function(d){l(d.chunks,function(d){if("uploading"===d.status()&&(a++,a>=c))return b=!1,!1})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||j(function(){this.fire("complete")},this)}},resume:function(){l(this.files,function(a){a.isComplete()||a.resume()})},pause:function(){l(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return l(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];l(a,function(a){if((!m||m&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)){var d=this.generateUniqueIdentifier(a);if(this.opts.allowDuplicateUploads||!this.getFromUniqueIdentifier(d)){var f=new e(this,a,d);this.fire("fileAdded",f,b)&&c.push(f)}}},this),this.fire("filesAdded",c,b)&&(l(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b))},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort(),this.fire("fileRemoved",a))},getFromUniqueIdentifier:function(a){var b=!1;return l(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return l(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return l(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return l(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallback.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return l(this.chunks,function(b){if("uploading"===b.status())return a=!0,!1}),a},isComplete:function(){var a=!1;return l(this.chunks,function(b){var c=b.status();if("pending"===c||"uploading"===c||"reading"===c||1===b.preprocessState||1===b.readState)return a=!0,!1}),!a},sizeUploaded:function(){var a=0;return l(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},g.prototype={getParams:function(){return{chunkNumber:this.offset+1,chunkSize:this.chunkSize,currentChunkSize:this.endByte-this.startByte,totalSize:this.fileObj.size,requestId:this.fileObj.uniqueIdentifier,filename:this.fileObj.name,relativePath:this.fileObj.relativePath,totalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return 0==b.length?a:(a+=a.indexOf("?")<0?"?":"&",a+b.join("&"))},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=i(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.endByte=this.computeEndByte(),this.preprocessState=2,this.send()},readFinished:function(a){this.readState=2,this.bytes=a,this.send()},send:function(){var a=this.flowObj.opts.preprocess,b=this.flowObj.opts.readFileFn;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}switch(this.readState){case 0:return this.readState=1,void b(this.fileObj,this.startByte,this.endByte,this.fileObj.file.type,this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1,this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var c=i(this.flowObj.opts.uploadMethod,this.fileObj,this),d=this.prepareXhrRequest(c,!1,this.flowObj.opts.method,this.bytes),e=this.flowObj.opts.changeRawDataBeforeSend;"function"==typeof e&&(d=e(this,d)),this.xhr.send(d)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return 1===this.readState?"reading":this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=i(this.flowObj.opts.query,this.fileObj,this,b);e=k(e||{},this.getParams());var f=i(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var h=[];l(e,function(a,b){h.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,h),g=d||null}else g=new FormData,l(e,function(a,b){g.append(b,a)}),"undefined"!=typeof d&&g.append(this.flowObj.opts.fileParameterName,d,this.filename||this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,l(i(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=i,d.extend=k,d.each=l,d.FlowFile=e,d.FlowChunk=g,d.version="2.14.1","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}("undefined"!=typeof window&&window,"undefined"!=typeof document&&document); \ No newline at end of file diff --git a/samples/Backend on Go.md b/samples/Backend on Go.md index 2e135286..9dc6e0ad 100644 --- a/samples/Backend on Go.md +++ b/samples/Backend on Go.md @@ -74,7 +74,7 @@ func (fn streamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func continueUpload(w http.ResponseWriter, r *http.Request) { - chunkDirPath := "./incomplete/" + r.FormValue("flowFilename") + "/" + r.FormValue("flowChunkNumber") + chunkDirPath := "./incomplete/" + r.FormValue("filename") + "/" + r.FormValue("chunkNumber") if _, err := os.Stat(chunkDirPath); err != nil { w.WriteHeader(204) return @@ -84,7 +84,7 @@ func continueUpload(w http.ResponseWriter, r *http.Request) { func chunkedReader(w http.ResponseWriter, r *http.Request) error { r.ParseMultipartForm(25) - chunkDirPath := "./incomplete/" + r.FormValue("flowFilename") + chunkDirPath := "./incomplete/" + r.FormValue("filename") err := os.MkdirAll(chunkDirPath, 02750) if err != nil { return err @@ -97,7 +97,7 @@ func chunkedReader(w http.ResponseWriter, r *http.Request) error { } defer src.Close() - dst, err := os.Create(chunkDirPath + "/" + r.FormValue("flowChunkNumber")) + dst, err := os.Create(chunkDirPath + "/" + r.FormValue("chunkNumber")) if err != nil { return err } diff --git a/samples/Backend on Haskell.md b/samples/Backend on Haskell.md index 062cf132..4fdd035e 100644 --- a/samples/Backend on Haskell.md +++ b/samples/Backend on Haskell.md @@ -76,15 +76,15 @@ uploadStart = action POST (pathJSON >/> pathId withAuth $ chunkForm :: DeformActionM f (Upload, Int64, Word64) chunkForm = do csrfForm - up <- "flowIdentifier" .:> (lift . (maybeAction <=< lookupUpload) =<< deform) + up <- "requestId" .:> (lift . (maybeAction <=< lookupUpload) =<< deform) let z = uploadSize up - "flowFilename" .:> (deformGuard "Filename mismatch." . (uploadFilename up ==) =<< deform) - "flowTotalSize" .:> (deformGuard "File size mismatch." . (z ==) =<< fileSizeForm) - c <- "flowChunkSize" .:> (deformCheck "Chunk size too small." (256 <=) =<< deform) - n <- "flowTotalChunks" .:> (deformCheck "Chunk count mismatch." ((1 >=) . abs . (pred z `div` c -)) =<< deform) - i <- "flowChunkNumber" .:> (deformCheck "Chunk number out of range." (\i -> 0 <= i && i < n) =<< pred <$> deform) + "filename" .:> (deformGuard "Filename mismatch." . (uploadFilename up ==) =<< deform) + "totalSize" .:> (deformGuard "File size mismatch." . (z ==) =<< fileSizeForm) + c <- "chunkSize" .:> (deformCheck "Chunk size too small." (256 <=) =<< deform) + n <- "totalChunks" .:> (deformCheck "Chunk count mismatch." ((1 >=) . abs . (pred z `div` c -)) =<< deform) + i <- "chunkNumber" .:> (deformCheck "Chunk number out of range." (\i -> 0 <= i && i < n) =<< pred <$> deform) let o = c * i - l <- "flowCurrentChunkSize" .:> (deformCheck "Current chunk size out of range." (\l -> (c == l || i == pred n) && o + l <= z) =<< deform) + l <- "currentChunkSize" .:> (deformCheck "Current chunk size out of range." (\l -> (c == l || i == pred n) && o + l <= z) =<< deform) return (up, o, fromIntegral l) uploadChunk :: ActionRoute () diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md index 4e9e6be8..9fb1be3b 100644 --- a/samples/Backend on PHP.md +++ b/samples/Backend on PHP.md @@ -127,8 +127,8 @@ function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { //check if request is GET and the requested chunk exists or not. this makes testChunks work if ($_SERVER['REQUEST_METHOD'] === 'GET') { - $temp_dir = 'temp/'.$_GET['flowIdentifier']; - $chunk_file = $temp_dir.'/'.$_GET['flowFilename'].'.part'.$_GET['flowChunkNumber']; + $temp_dir = 'temp/'.$_GET['requestId']; + $chunk_file = $temp_dir.'/'.$_GET['filename'].'.part'.$_GET['chunkNumber']; if (file_exists($chunk_file)) { header("HTTP/1.0 200 Ok"); } else @@ -144,14 +144,14 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { // check the error status if ($file['error'] != 0) { - _log('error '.$file['error'].' in file '.$_POST['flowFilename']); + _log('error '.$file['error'].' in file '.$_POST['filename']); continue; } // init the destination file (format .part<#chunk> // the file is stored in a temporary directory - $temp_dir = 'temp/'.$_POST['flowIdentifier']; - $dest_file = $temp_dir.'/'.$_POST['flowFilename'].'.part'.$_POST['flowChunkNumber']; + $temp_dir = 'temp/'.$_POST['requestId']; + $dest_file = $temp_dir.'/'.$_POST['filename'].'.part'.$_POST['chunkNumber']; // create the temporary directory if (!is_dir($temp_dir)) { @@ -160,12 +160,12 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { // move the temporary file if (!move_uploaded_file($file['tmp_name'], $dest_file)) { - _log('Error saving (move_uploaded_file) chunk '.$_POST['flowChunkNumber'].' for file '.$_POST['flowFilename']); + _log('Error saving (move_uploaded_file) chunk '.$_POST['chunkNumber'].' for file '.$_POST['filename']); } else { // check if all the parts present, and create the final destination file - createFileFromChunks($temp_dir, $_POST['flowFilename'], - $_POST['flowChunkSize'], $_POST['flowTotalSize']); + createFileFromChunks($temp_dir, $_POST['filename'], + $_POST['chunkSize'], $_POST['totalSize']); } } ``` diff --git a/samples/Node.js/flow-node.js b/samples/Node.js/flow-node.js index fa8a03b1..001482e0 100644 --- a/samples/Node.js/flow-node.js +++ b/samples/Node.js/flow-node.js @@ -63,11 +63,11 @@ module.exports = flow = function(temporaryFolder) { //'found', filename, original_filename, identifier //'not_found', null, null, null $.get = function(req, callback) { - var chunkNumber = req.param('flowChunkNumber', 0); - var chunkSize = req.param('flowChunkSize', 0); - var totalSize = req.param('flowTotalSize', 0); - var identifier = req.param('flowIdentifier', ""); - var filename = req.param('flowFilename', ""); + var chunkNumber = req.param('chunkNumber', 0); + var chunkSize = req.param('chunkSize', 0); + var totalSize = req.param('totalSize', 0); + var identifier = req.param('requestId', ""); + var filename = req.param('filename', ""); if (validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename) == 'valid') { var chunkFilename = getChunkFilename(chunkNumber, identifier); @@ -92,11 +92,11 @@ module.exports = flow = function(temporaryFolder) { var fields = req.body; var files = req.files; - var chunkNumber = fields['flowChunkNumber']; - var chunkSize = fields['flowChunkSize']; - var totalSize = fields['flowTotalSize']; - var identifier = cleanIdentifier(fields['flowIdentifier']); - var filename = fields['flowFilename']; + var chunkNumber = fields['chunkNumber']; + var chunkSize = fields['chunkSize']; + var totalSize = fields['totalSize']; + var identifier = cleanIdentifier(fields['requestId']); + var filename = fields['filename']; if (!files[$.fileParameterName] || !files[$.fileParameterName].size) { callback('invalid_flow_request', null, null, null); diff --git a/samples/Ruby backend in Sinatra.md b/samples/Ruby backend in Sinatra.md index 9b102a5a..be033344 100644 --- a/samples/Ruby backend in Sinatra.md +++ b/samples/Ruby backend in Sinatra.md @@ -89,19 +89,19 @@ private ## # Determine if this is the last chunk based on the chunk number. def last_chunk? - params[:flowChunkNumber].to_i == params[:flowTotalChunks].to_i + params[:chunkNumber].to_i == params[:totalChunks].to_i end ## # ./tmp/flow/abc-123/upload.txt.part1 def chunk_file_path - File.join(chunk_file_directory, "#{params[:flowFilename]}.part#{params[:flowChunkNumber]}") + File.join(chunk_file_directory, "#{params[:filename]}.part#{params[:chunkNumber]}") end ## # ./tmp/flow/abc-123 def chunk_file_directory - File.join "tmp", "flow", params[:flowIdentifier] + File.join "tmp", "flow", params[:requestId] end ## @@ -123,7 +123,7 @@ private ## # /final/resting/place/upload.txt def final_file_path - File.join final_file_directory, params[:flowFilename] + File.join final_file_directory, params[:filename] end ## diff --git a/src/flow.js b/src/flow.js index 6200dfb3..5b6d3525 100644 --- a/src/flow.js +++ b/src/flow.js @@ -1376,14 +1376,14 @@ */ getParams: function () { return { - flowChunkNumber: this.offset + 1, - flowChunkSize: this.chunkSize, - flowCurrentChunkSize: this.endByte - this.startByte, - flowTotalSize: this.fileObj.size, - flowIdentifier: this.fileObj.uniqueIdentifier, - flowFilename: this.fileObj.name, - flowRelativePath: this.fileObj.relativePath, - flowTotalChunks: this.fileObj.chunks.length + chunkNumber: this.offset + 1, + chunkSize: this.chunkSize, + currentChunkSize: this.endByte - this.startByte, + totalSize: this.fileObj.size, + requestId: this.fileObj.uniqueIdentifier, + filename: this.fileObj.name, + relativePath: this.fileObj.relativePath, + totalChunks: this.fileObj.chunks.length, }; }, From 91342c5ff505c5bc276b8c6444880bba17779e56 Mon Sep 17 00:00:00 2001 From: Vladimir Stankovic Date: Wed, 26 May 2021 16:08:23 +0200 Subject: [PATCH 3/4] Remove unnecessary commas --- dist/flow.js | 2 +- src/flow.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/flow.js b/dist/flow.js index 4fd17ff6..6c1267bd 100644 --- a/dist/flow.js +++ b/dist/flow.js @@ -1287,7 +1287,7 @@ requestId: this.fileObj.uniqueIdentifier, filename: this.fileObj.name, relativePath: this.fileObj.relativePath, - totalChunks: this.fileObj.chunks.length, + totalChunks: this.fileObj.chunks.length }; }, diff --git a/src/flow.js b/src/flow.js index 2fe2c876..2d533cd5 100644 --- a/src/flow.js +++ b/src/flow.js @@ -1389,7 +1389,7 @@ requestId: this.fileObj.uniqueIdentifier, filename: this.fileObj.name, relativePath: this.fileObj.relativePath, - totalChunks: this.fileObj.chunks.length, + totalChunks: this.fileObj.chunks.length }; }, From 6c7e9391021c0e043596d6a0fd045a8ae90c880f Mon Sep 17 00:00:00 2001 From: Vladimir Stankovic Date: Wed, 26 May 2021 16:42:40 +0200 Subject: [PATCH 4/4] Dist changes --- dist/flow.js | 220 ++++++++++++++++++++++++++++++++++------------- dist/flow.min.js | 2 +- 2 files changed, 162 insertions(+), 60 deletions(-) diff --git a/dist/flow.js b/dist/flow.js index 6c1267bd..b4ace2aa 100644 --- a/dist/flow.js +++ b/dist/flow.js @@ -230,55 +230,109 @@ */ webkitReadDataTransfer: function (event) { var $ = this; - var queue = event.dataTransfer.items.length; - var files = []; - each(event.dataTransfer.items, function (item) { - var entry = item.webkitGetAsEntry(); - if (!entry) { - decrement(); - return ; + getEntries(event.dataTransfer.items).then(function (result) { + getFiles(result.files).then(function (entries) { + var files = []; + var errors = []; + each(entries, function (entry) { + if (entry.error) { + errors.push(entry); + } else { + files.push(entry); + } + }); + if (result.errors.length || errors.length) { + $.fire('readErrors', errors, result.errors, event); + } + if (files.length) { + $.addFiles(files, event); + } + }); + }); + function getEntries(items) { + var files = []; + var errors = []; + var promises = []; + + function readEntry(entry, promises) { + if (entry.isFile) { + files.push(entry); + } else if (entry.isDirectory) { + promises.push(readDirectory(entry)); + } } - if (entry.isFile) { - // due to a bug in Chrome's File System API impl - #149735 - fileReadSuccess(item.getAsFile(), entry.fullPath); - } else { - readDirectory(entry.createReader()); + + function readDirectory(entry) { + var reader = entry.createReader(); + return new Promise(function (resolve, reject) { + var promises = []; + readEntries(entry, reader, promises, resolve); + }); } - }); - function readDirectory(reader) { - reader.readEntries(function (entries) { - if (entries.length) { - queue += entries.length; - each(entries, function(entry) { - if (entry.isFile) { - var fullPath = entry.fullPath; - entry.file(function (file) { - fileReadSuccess(file, fullPath); - }, readError); - } else if (entry.isDirectory) { - readDirectory(entry.createReader()); - } + + function readEntries(entry, reader, promises, resolve) { + reader.readEntries(function (entries) { + if (entries.length) { + var promises2 = []; + each(entries, function (entry2) { + readEntry(entry2, promises2); + }); + promises.push(Promise.all(promises2)); + readEntries(entry, reader, promises, resolve); + return; + } + resolve(Promise.all(promises)); + }, function (error) { + errors.push({ + path: entry.fullPath, + error: error }); - readDirectory(reader); - } else { - decrement(); + resolve(promises); + }); + } + + each(items, function (item) { + var entry = item.webkitGetAsEntry(); + if (!entry) { + return; } - }, readError); + if (entry.isFile) { + // due to a bug in Chrome's File System API impl - #149735 + files.push(getFile(item.getAsFile(), entry.fullPath)); + return; + } + readEntry(entry, promises); + }); + + return new Promise(function (resolve, reject) { + return Promise.all(promises).then(function () { + resolve({ files: files, errors: errors }); + }); + }); } - function fileReadSuccess(file, fullPath) { + function getFiles(entries) { + return Promise.all(entries.map(function (entry) { + return new Promise(function (resolve, reject) { + if (entry.file) { + var fullPath = entry.fullPath; + entry.file(function (file) { + resolve(getFile(file, fullPath)); + }, function (file) { + resolve({ + path: entry.fullPath, + error: file + }); + }); + } else { + resolve(entry); + } + }); + })); + } + function getFile(file, fullPath) { // relative path should not start with "/" file.relativePath = fullPath.substring(1); - files.push(file); - decrement(); - } - function readError(fileError) { - decrement(); - throw fileError; - } - function decrement() { - if (--queue == 0) { - $.addFiles(files, event); - } + return file; } }, @@ -588,8 +642,12 @@ * @param {Event} [event] event is optional */ addFiles: function (fileList, event) { + var $ = this; var files = []; - each(fileList, function (file) { + var errors = []; + var promises = []; + + function addFile(file) { // https://github.com/flowjs/flow.js/issues/55 if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) { var uniqueIdentifier = this.generateUniqueIdentifier(file); @@ -600,16 +658,54 @@ } } } - }, this); - if (this.fire('filesAdded', files, event)) { - each(files, function (file) { - if (this.opts.singleFile && this.files.length > 0) { - this.removeFile(this.files[0]); - } - this.files.push(file); - }, this); - this.fire('filesSubmitted', files, event); } + + /** + * Chrome's FileSystem API has a bug that files from dropped folders or files from input dialog's selected folder, + * with read errors (has absolute paths which exceed 260 chars) will have zero file size. + */ + function validateFile(file) { + // files with size greater than zero can upload + if (file.size > 0) { + addFile.bind($)(file); + return; + } + + // try to read from from zero size file, + // if error occurs than file cannot be uploaded + promises.push(new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.onloadend = function () { + if (reader.error) { + errors.push({ + path: file.webkitRelativePath || file.name, + error: reader.error + }); + } else { + addFile.bind($)(file); + } + resolve(); + }.bind($); + reader.readAsArrayBuffer(file); + })); + } + + each(fileList, validateFile); + + Promise.all(promises).then(function () { + if (errors.length) { + this.fire('readErrors', errors, [], event); + } + if (this.fire('filesAdded', files, event)) { + each(files, function (file) { + if (this.opts.singleFile && this.files.length > 0) { + this.removeFile(this.files[0]); + } + this.files.push(file); + }, this); + this.fire('filesSubmitted', files, event); + } + }.bind(this)); }, @@ -716,12 +812,6 @@ */ this.flowObj = flowObj; - /** - * Used to store the bytes read - * @type {Blob|string} - */ - this.bytes = null; - /** * Reference to file * @type {File} @@ -937,9 +1027,16 @@ */ bootstrap: function () { if (typeof this.flowObj.opts.initFileFn === "function") { - this.flowObj.opts.initFileFn(this); + var ret = this.flowObj.opts.initFileFn(this); + if (ret && 'then' in ret) { + ret.then(this._bootstrap.bind(this)); + return; + } } + this._bootstrap(); + }, + _bootstrap: function () { this.abort(true); this.error = false; // Rebuild stack of chunks from file @@ -1144,6 +1241,11 @@ */ this.readState = 0; + /** + * Used to store the bytes read + * @type {Blob|string} + */ + this.bytes = undefined; /** * Bytes transferred from total request size diff --git a/dist/flow.min.js b/dist/flow.min.js index b61ab2a1..1db27a62 100644 --- a/dist/flow.min.js +++ b/dist/flow.min.js @@ -1,2 +1,2 @@ /*! @flowjs/flow.js 2.14.1 */ -!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/Chrome/.test(a.navigator.userAgent)||/Firefox/.test(a.navigator.userAgent)||/Edge/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,changeRawDataBeforeSend:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,allowDuplicateUploads:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,413,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1,initFileFn:null,readFileFn:f},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b,d){this.flowObj=a,this.bytes=null,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=d===c?a.generateUniqueIdentifier(b):d,this.chunkSize=0,this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c,d,e){var f="slice";a.file.slice?f="slice":a.file.mozSlice?f="mozSlice":a.file.webkitSlice&&(f="webkitSlice"),e.readFinished(a.file[f](b,c,d))}function g(a,b,c){this.flowObj=a,this.fileObj=b,this.offset=c,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.readState=0,this.loaded=0,this.total=0,this.chunkSize=this.fileObj.chunkSize,this.startByte=this.offset*this.chunkSize,this.filename=null,this.computeEndByte=function(){var a=Math.min(this.fileObj.size,(this.offset+1)*this.chunkSize);return this.fileObj.size-a-1&&a.splice(c,1)}function i(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function j(a,b){setTimeout(a.bind(b),0)}function k(a,b){return l(arguments,function(b){b!==a&&l(b,function(b,c){a[c]=b})}),a}function l(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(l(this.files,function(a){if(a.paused||l(a.chunks,function(a){if("pending"===a.status())return a.send(),b=!0,!1}),b)return!1}),b)return!0;var c=!1;return l(this.files,function(a){if(!a.isComplete())return c=!0,!1}),c||a||j(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){a instanceof Element&&(a=[a]),l(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),k(f.style,{visibility:"hidden",position:"absolute",width:"1px",height:"1px"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),l(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){a.target.value&&(g.addFiles(a.target.files,a),a.target.value="")},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return l(this.files,function(b){if(b.isUploading())return a=!0,!1}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return l(this.files,function(d){l(d.chunks,function(d){if("uploading"===d.status()&&(a++,a>=c))return b=!1,!1})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||j(function(){this.fire("complete")},this)}},resume:function(){l(this.files,function(a){a.isComplete()||a.resume()})},pause:function(){l(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return l(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){var c=[];l(a,function(a){if((!m||m&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)){var d=this.generateUniqueIdentifier(a);if(this.opts.allowDuplicateUploads||!this.getFromUniqueIdentifier(d)){var f=new e(this,a,d);this.fire("fileAdded",f,b)&&c.push(f)}}},this),this.fire("filesAdded",c,b)&&(l(c,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",c,b))},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort(),this.fire("fileRemoved",a))},getFromUniqueIdentifier:function(a){var b=!1;return l(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return l(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return l(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return l(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallback.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return l(this.chunks,function(b){if("uploading"===b.status())return a=!0,!1}),a},isComplete:function(){var a=!1;return l(this.chunks,function(b){var c=b.status();if("pending"===c||"uploading"===c||"reading"===c||1===b.preprocessState||1===b.readState)return a=!0,!1}),!a},sizeUploaded:function(){var a=0;return l(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},g.prototype={getParams:function(){return{chunkNumber:this.offset+1,chunkSize:this.chunkSize,currentChunkSize:this.endByte-this.startByte,totalSize:this.fileObj.size,requestId:this.fileObj.uniqueIdentifier,filename:this.fileObj.name,relativePath:this.fileObj.relativePath,totalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return 0==b.length?a:(a+=a.indexOf("?")<0?"?":"&",a+b.join("&"))},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=i(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.endByte=this.computeEndByte(),this.preprocessState=2,this.send()},readFinished:function(a){this.readState=2,this.bytes=a,this.send()},send:function(){var a=this.flowObj.opts.preprocess,b=this.flowObj.opts.readFileFn;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}switch(this.readState){case 0:return this.readState=1,void b(this.fileObj,this.startByte,this.endByte,this.fileObj.file.type,this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1,this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var c=i(this.flowObj.opts.uploadMethod,this.fileObj,this),d=this.prepareXhrRequest(c,!1,this.flowObj.opts.method,this.bytes),e=this.flowObj.opts.changeRawDataBeforeSend;"function"==typeof e&&(d=e(this,d)),this.xhr.send(d)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return 1===this.readState?"reading":this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=i(this.flowObj.opts.query,this.fileObj,this,b);e=k(e||{},this.getParams());var f=i(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var h=[];l(e,function(a,b){h.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,h),g=d||null}else g=new FormData,l(e,function(a,b){g.append(b,a)}),"undefined"!=typeof d&&g.append(this.flowObj.opts.fileParameterName,d,this.filename||this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,l(i(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=i,d.extend=k,d.each=l,d.FlowFile=e,d.FlowChunk=g,d.version="2.14.1","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}("undefined"!=typeof window&&window,"undefined"!=typeof document&&document); \ No newline at end of file +!function(a,b,c){"use strict";function d(b){if(this.support=!("undefined"==typeof File||"undefined"==typeof Blob||"undefined"==typeof FileList||!Blob.prototype.slice&&!Blob.prototype.webkitSlice&&!Blob.prototype.mozSlice),this.support){this.supportDirectory=/Chrome/.test(a.navigator.userAgent)||/Firefox/.test(a.navigator.userAgent)||/Edge/.test(a.navigator.userAgent),this.files=[],this.defaults={chunkSize:1048576,forceChunkSize:!1,simultaneousUploads:3,singleFile:!1,fileParameterName:"file",progressCallbacksInterval:500,speedSmoothingFactor:.1,query:{},headers:{},withCredentials:!1,preprocess:null,changeRawDataBeforeSend:null,method:"multipart",testMethod:"GET",uploadMethod:"POST",prioritizeFirstAndLastChunk:!1,allowDuplicateUploads:!1,target:"/",testChunks:!0,generateUniqueIdentifier:null,maxChunkRetries:0,chunkRetryInterval:null,permanentErrors:[404,413,415,500,501],successStatuses:[200,201,202],onDropStopPropagation:!1,initFileFn:null,readFileFn:f},this.opts={},this.events={};var c=this;this.onDrop=function(a){c.opts.onDropStopPropagation&&a.stopPropagation(),a.preventDefault();var b=a.dataTransfer;b.items&&b.items[0]&&b.items[0].webkitGetAsEntry?c.webkitReadDataTransfer(a):c.addFiles(b.files,a)},this.preventEvent=function(a){a.preventDefault()},this.opts=d.extend({},this.defaults,b||{})}}function e(a,b,d){this.flowObj=a,this.file=b,this.name=b.fileName||b.name,this.size=b.size,this.relativePath=b.relativePath||b.webkitRelativePath||this.name,this.uniqueIdentifier=d===c?a.generateUniqueIdentifier(b):d,this.chunkSize=0,this.chunks=[],this.paused=!1,this.error=!1,this.averageSpeed=0,this.currentSpeed=0,this._lastProgressCallback=Date.now(),this._prevUploadedSize=0,this._prevProgress=0,this.bootstrap()}function f(a,b,c,d,e){var f="slice";a.file.slice?f="slice":a.file.mozSlice?f="mozSlice":a.file.webkitSlice&&(f="webkitSlice"),e.readFinished(a.file[f](b,c,d))}function g(a,b,d){this.flowObj=a,this.fileObj=b,this.offset=d,this.tested=!1,this.retries=0,this.pendingRetry=!1,this.preprocessState=0,this.readState=0,this.bytes=c,this.loaded=0,this.total=0,this.chunkSize=this.fileObj.chunkSize,this.startByte=this.offset*this.chunkSize,this.filename=null,this.computeEndByte=function(){var a=Math.min(this.fileObj.size,(this.offset+1)*this.chunkSize);return this.fileObj.size-a-1&&a.splice(c,1)}function i(a,b){return"function"==typeof a&&(b=Array.prototype.slice.call(arguments),a=a.apply(null,b.slice(1))),a}function j(a,b){setTimeout(a.bind(b),0)}function k(a,b){return l(arguments,function(b){b!==a&&l(b,function(b,c){a[c]=b})}),a}function l(a,b,c){if(a){var d;if("undefined"!=typeof a.length){for(d=0;d1&&"pending"===a.chunks[a.chunks.length-1].status()?(a.chunks[a.chunks.length-1].send(),b=!0,!1):void 0}),b))return b;if(l(this.files,function(a){if(a.paused||l(a.chunks,function(a){if("pending"===a.status())return a.send(),b=!0,!1}),b)return!1}),b)return!0;var c=!1;return l(this.files,function(a){if(!a.isComplete())return c=!0,!1}),c||a||j(function(){this.fire("complete")},this),!1},assignBrowse:function(a,c,d,e){a instanceof Element&&(a=[a]),l(a,function(a){var f;"INPUT"===a.tagName&&"file"===a.type?f=a:(f=b.createElement("input"),f.setAttribute("type","file"),k(f.style,{visibility:"hidden",position:"absolute",width:"1px",height:"1px"}),a.appendChild(f),a.addEventListener("click",function(){f.click()},!1)),this.opts.singleFile||d||f.setAttribute("multiple","multiple"),c&&f.setAttribute("webkitdirectory","webkitdirectory"),l(e,function(a,b){f.setAttribute(b,a)});var g=this;f.addEventListener("change",function(a){a.target.value&&(g.addFiles(a.target.files,a),a.target.value="")},!1)},this)},assignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.addEventListener("dragover",this.preventEvent,!1),a.addEventListener("dragenter",this.preventEvent,!1),a.addEventListener("drop",this.onDrop,!1)},this)},unAssignDrop:function(a){"undefined"==typeof a.length&&(a=[a]),l(a,function(a){a.removeEventListener("dragover",this.preventEvent),a.removeEventListener("dragenter",this.preventEvent),a.removeEventListener("drop",this.onDrop)},this)},isUploading:function(){var a=!1;return l(this.files,function(b){if(b.isUploading())return a=!0,!1}),a},_shouldUploadNext:function(){var a=0,b=!0,c=this.opts.simultaneousUploads;return l(this.files,function(d){l(d.chunks,function(d){if("uploading"===d.status()&&(a++,a>=c))return b=!1,!1})}),b&&a},upload:function(){var a=this._shouldUploadNext();if(a!==!1){this.fire("uploadStart");for(var b=!1,c=1;c<=this.opts.simultaneousUploads-a;c++)b=this.uploadNextChunk(!0)||b;b||j(function(){this.fire("complete")},this)}},resume:function(){l(this.files,function(a){a.isComplete()||a.resume()})},pause:function(){l(this.files,function(a){a.pause()})},cancel:function(){for(var a=this.files.length-1;a>=0;a--)this.files[a].cancel()},progress:function(){var a=0,b=0;return l(this.files,function(c){a+=c.progress()*c.size,b+=c.size}),b>0?a/b:0},addFile:function(a,b){this.addFiles([a],b)},addFiles:function(a,b){function c(a){if((!m||m&&a.size>0)&&(a.size%4096!==0||"."!==a.name&&"."!==a.fileName)){var c=this.generateUniqueIdentifier(a);if(this.opts.allowDuplicateUploads||!this.getFromUniqueIdentifier(c)){var d=new e(this,a,c);this.fire("fileAdded",d,b)&&g.push(d)}}}function d(a){return a.size>0?void c.bind(f)(a):void i.push(new Promise(function(b,d){var e=new FileReader;e.onloadend=function(){e.error?h.push({path:a.webkitRelativePath||a.name,error:e.error}):c.bind(f)(a),b()}.bind(f),e.readAsArrayBuffer(a)}))}var f=this,g=[],h=[],i=[];l(a,d),Promise.all(i).then(function(){h.length&&this.fire("readErrors",h,[],b),this.fire("filesAdded",g,b)&&(l(g,function(a){this.opts.singleFile&&this.files.length>0&&this.removeFile(this.files[0]),this.files.push(a)},this),this.fire("filesSubmitted",g,b))}.bind(this))},removeFile:function(a){for(var b=this.files.length-1;b>=0;b--)this.files[b]===a&&(this.files.splice(b,1),a.abort(),this.fire("fileRemoved",a))},getFromUniqueIdentifier:function(a){var b=!1;return l(this.files,function(c){c.uniqueIdentifier===a&&(b=c)}),b},getSize:function(){var a=0;return l(this.files,function(b){a+=b.size}),a},sizeUploaded:function(){var a=0;return l(this.files,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){var a=0,b=0;return l(this.files,function(c){c.paused||c.error||(a+=c.size-c.sizeUploaded(),b+=c.averageSpeed)}),a&&!b?Number.POSITIVE_INFINITY:a||b?Math.floor(a/b):0}},e.prototype={measureSpeed:function(){var a=Date.now()-this._lastProgressCallback;if(a){var b=this.flowObj.opts.speedSmoothingFactor,c=this.sizeUploaded();this.currentSpeed=Math.max((c-this._prevUploadedSize)/a*1e3,0),this.averageSpeed=b*this.currentSpeed+(1-b)*this.averageSpeed,this._prevUploadedSize=c}},chunkEvent:function(a,b,c){switch(b){case"progress":if(Date.now()-this._lastProgressCallback.9999?1:b),this._prevProgress},isUploading:function(){var a=!1;return l(this.chunks,function(b){if("uploading"===b.status())return a=!0,!1}),a},isComplete:function(){var a=!1;return l(this.chunks,function(b){var c=b.status();if("pending"===c||"uploading"===c||"reading"===c||1===b.preprocessState||1===b.readState)return a=!0,!1}),!a},sizeUploaded:function(){var a=0;return l(this.chunks,function(b){a+=b.sizeUploaded()}),a},timeRemaining:function(){if(this.paused||this.error)return 0;var a=this.size-this.sizeUploaded();return a&&!this.averageSpeed?Number.POSITIVE_INFINITY:a||this.averageSpeed?Math.floor(a/this.averageSpeed):0},getType:function(){return this.file.type&&this.file.type.split("/")[1]},getExtension:function(){return this.name.substr((~-this.name.lastIndexOf(".")>>>0)+2).toLowerCase()}},g.prototype={getParams:function(){return{chunkNumber:this.offset+1,chunkSize:this.chunkSize,currentChunkSize:this.endByte-this.startByte,totalSize:this.fileObj.size,requestId:this.fileObj.uniqueIdentifier,filename:this.fileObj.name,relativePath:this.fileObj.relativePath,totalChunks:this.fileObj.chunks.length}},getTarget:function(a,b){return 0==b.length?a:(a+=a.indexOf("?")<0?"?":"&",a+b.join("&"))},test:function(){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.testHandler,!1),this.xhr.addEventListener("error",this.testHandler,!1);var a=i(this.flowObj.opts.testMethod,this.fileObj,this),b=this.prepareXhrRequest(a,!0);this.xhr.send(b)},preprocessFinished:function(){this.endByte=this.computeEndByte(),this.preprocessState=2,this.send()},readFinished:function(a){this.readState=2,this.bytes=a,this.send()},send:function(){var a=this.flowObj.opts.preprocess,b=this.flowObj.opts.readFileFn;if("function"==typeof a)switch(this.preprocessState){case 0:return this.preprocessState=1,void a(this);case 1:return}switch(this.readState){case 0:return this.readState=1,void b(this.fileObj,this.startByte,this.endByte,this.fileObj.file.type,this);case 1:return}if(this.flowObj.opts.testChunks&&!this.tested)return void this.test();this.loaded=0,this.total=0,this.pendingRetry=!1,this.xhr=new XMLHttpRequest,this.xhr.upload.addEventListener("progress",this.progressHandler,!1),this.xhr.addEventListener("load",this.doneHandler,!1),this.xhr.addEventListener("error",this.doneHandler,!1);var c=i(this.flowObj.opts.uploadMethod,this.fileObj,this),d=this.prepareXhrRequest(c,!1,this.flowObj.opts.method,this.bytes),e=this.flowObj.opts.changeRawDataBeforeSend;"function"==typeof e&&(d=e(this,d)),this.xhr.send(d)},abort:function(){var a=this.xhr;this.xhr=null,a&&a.abort()},status:function(a){return 1===this.readState?"reading":this.pendingRetry||1===this.preprocessState?"uploading":this.xhr?this.xhr.readyState<4?"uploading":this.flowObj.opts.successStatuses.indexOf(this.xhr.status)>-1?"success":this.flowObj.opts.permanentErrors.indexOf(this.xhr.status)>-1||!a&&this.retries>=this.flowObj.opts.maxChunkRetries?"error":(this.abort(),"pending"):"pending"},message:function(){return this.xhr?this.xhr.responseText:""},progress:function(){if(this.pendingRetry)return 0;var a=this.status();return"success"===a||"error"===a?1:"pending"===a?0:this.total>0?this.loaded/this.total:0},sizeUploaded:function(){var a=this.endByte-this.startByte;return"success"!==this.status()&&(a=this.progress()*a),a},prepareXhrRequest:function(a,b,c,d){var e=i(this.flowObj.opts.query,this.fileObj,this,b);e=k(e||{},this.getParams());var f=i(this.flowObj.opts.target,this.fileObj,this,b),g=null;if("GET"===a||"octet"===c){var h=[];l(e,function(a,b){h.push([encodeURIComponent(b),encodeURIComponent(a)].join("="))}),f=this.getTarget(f,h),g=d||null}else g=new FormData,l(e,function(a,b){g.append(b,a)}),"undefined"!=typeof d&&g.append(this.flowObj.opts.fileParameterName,d,this.filename||this.fileObj.file.name);return this.xhr.open(a,f,!0),this.xhr.withCredentials=this.flowObj.opts.withCredentials,l(i(this.flowObj.opts.headers,this.fileObj,this,b),function(a,b){this.xhr.setRequestHeader(b,a)},this),g}},d.evalOpts=i,d.extend=k,d.each=l,d.FlowFile=e,d.FlowChunk=g,d.version="2.14.1","object"==typeof module&&module&&"object"==typeof module.exports?module.exports=d:(a.Flow=d,"function"==typeof define&&define.amd&&define("flow",[],function(){return d}))}("undefined"!=typeof window&&window,"undefined"!=typeof document&&document); \ No newline at end of file