diff --git a/FileAPI.min.js b/FileAPI.min.js index 6482f2a5..2474346b 100644 --- a/FileAPI.min.js +++ b/FileAPI.min.js @@ -1,4 +1,4 @@ -/**! +/**! * FileAPI — a set of tools for working with files * * @author RubaXa @@ -61,4 +61,4 @@ c,p=function(){try{m.get(v).setImage(k)}catch(a){}},t,v=a.uid(),u=f.createElemen c&&!c.flashId?this.parent.apply(this,arguments):a.call(this,!c.info,{id:c.id,flashId:c.flashId,name:c.name,type:c.type,matrix:this.getMatrix(c.info)})}});g(a.Form.prototype,{toData:function(d){for(var c=this.items,e=c.length;e--;)if(c[e].file&&c[e].blob&&!c[e].blob.flashId)return this.parent.apply(this,arguments);a.log("flash.Form.toData");d(c)}});g(a.XHR.prototype,{_send:function(c,e){if(e.nodeName||e.append&&a.support.html5||a.isArray(e)&&"string"===typeof e[0])return this.parent.apply(this,arguments); var f={},g={},k=this,l,n;a.each(e,function(a){a.file?(g[a.name]=a={id:a.blob.id,name:a.blob.name,matrix:a.blob.matrix,flashId:a.blob.flashId},n=a.id,l=a.flashId):f[a.name]=a.blob});a.log("flash.XHR._send:",l,n,g);k.xhr={headers:{},abort:function(){m.cmd(l,"abort",n)},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var p=a.queue(function(){m.cmd(l,"upload",{url:D(c.url),data:f,files:g,headers:c.headers,callback:x(function z(e){var f=e.type, g=e.result;a.log("flash.upload."+f+":",e);if("progress"==f)e.loaded=Math.min(e.loaded,e.total),e.lengthComputable=!0,c.progress(e);else if("complete"==f)q(z),"string"==typeof g&&(k.responseText=g.replace(/%22/g,'"').replace(/%5c/g,"\\").replace(/%26/g,"&").replace(/%25/g,"%")),k.end(e.status||200);else if("abort"==f||"error"==f)k.end(0,e.message),q(z)})})});a.each(g,function(c){p.inc();a.getInfo(c,p.next)});p.check()}})}};a.Flash=m;var t=new Image;a.event.one(t,"error load",function(){a.support.dataURI= -!(1!=t.width||1!=t.height);t=null;m.init()});t.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="}})(FileAPI,window,document);"undefined"!==typeof ajs&&ajs.loaded&&ajs.loaded("{fileapi}FileAPI.min");"function"===typeof define&&define.amd&&define("FileAPI",[],function(){return window.FileAPI||{}}); \ No newline at end of file +!(1!=t.width||1!=t.height);t=null;m.init()});t.src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="}})(FileAPI,window,document);"undefined"!==typeof ajs&&ajs.loaded&&ajs.loaded("{fileapi}FileAPI.min");"function"===typeof define&&define.amd&&define("FileAPI",[],function(){return window.FileAPI||{}}); diff --git a/README.md b/README.md index 262e1afa..fda799e7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# FileAPI — a set of tools for working with files. +# FileAPI — a set of tools for working with files.

@@ -220,7 +220,7 @@ function onDrop(evt){ var el = document.getElementById('el'); FileAPI.event.dnd(el, function (over/**Boolean*/, evt/**Event*/){ - el.style.background = ever ? 'red' : ''; + el.style.background = over ? 'red' : ''; }, function (files/**Array*/, evt/**Event*/){ // ... }); diff --git a/lib/FileAPI.Flash.js b/lib/FileAPI.Flash.js index 06425416..40fe0c96 100644 --- a/lib/FileAPI.Flash.js +++ b/lib/FileAPI.Flash.js @@ -1,4 +1,4 @@ -/** +/** * FileAPI fallback to Flash * * @flash-developer "Vladimer Demidov" diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js index 7c0bcaf9..050ce3b7 100644 --- a/lib/FileAPI.Form.js +++ b/lib/FileAPI.Form.js @@ -26,7 +26,7 @@ } }, - toData: function (fn){ + toData: function (fn, options){ if( !api.support.html5 ){ api.log('FileAPI.Form.toHtmlData'); this.toHtmlData(fn); @@ -35,6 +35,10 @@ api.log('FileAPI.Form.toMultipartData'); this.toMultipartData(fn); } + else if( api.support.chunked && options.chunkSize > 0 ){ + api.log('FileAPI.Form.toMultipartData'); + this.toPlainData(fn); + } else { api.log('FileAPI.Form.toFormData'); this.toFormData(fn); @@ -74,6 +78,19 @@ }); }, + toPlainData: function (fn){ + this._to({}, fn, function (file, data, queue){ + if( file.file ){ + data.type = file.file; + } + data.name = file.blob.name; + data.file = file.blob; + data.size = file.blob.size; + data.start = 0; + data.end = 0; + data.retry = 0; + }); + }, toFormData: function (fn){ this._to(new FormData, fn, function (file, data, queue){ diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js index 18f04271..1dd17224 100644 --- a/lib/FileAPI.XHR.js +++ b/lib/FileAPI.XHR.js @@ -65,7 +65,7 @@ // Start uploading options.upload(options, _this); _this._send.call(_this, options, data); - }); + }, options); }, _send: function (options, data){ @@ -77,6 +77,7 @@ url += (~url.indexOf('?') ? '&' : '?') + api.uid(); if( data.nodeName ){ + // legacy options.upload(options, _this); xhr = document.createElement('div'); @@ -125,6 +126,7 @@ form = null; } else { + // html5 xhr = _this.xhr = api.getXHR(); xhr.open('POST', url, true); @@ -138,46 +140,114 @@ xhr.setRequestHeader(key, val); }); - if( xhr.upload ){ - // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 - xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ - options.progress(evt, _this, options); - }, 100), false); - } - - xhr.onreadystatechange = function (){ - _this.status = xhr.status; - _this.statusText = xhr.statusText; - _this.readyState = xhr.readyState; + + if (api.support.chunked && options.chunkSize > 0) { + // resumable upload + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + var e = api.extend({}, evt, { + loaded : data.start + evt.loaded, + totalSize : data.size, + total : data.size}); + options.progress(e, _this, options); + }, 100), false); + } - if( xhr.readyState == 4 ){ - for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ - _this['response'+k] = xhr['response'+k]; + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + + if (xhr.status - 201 > 0) { + // some kind of error + if (++data.retry <= options.chunkUploadRetry && (500 == xhr.status || 416 == xhr.status)) { + // let's try again the same chunk + // only applicable for recoverable error codes 500 && 416 + data.end = data.start + _this._send(options, data); + } else { + // no mo retries + _this.end(xhr.status); + } + } else { + // success + data.retry = 0; + + if (data.end == data.size - 1) { + // finished + _this.end(xhr.status); + } else { + // next chunk + _this._send(options, data); + } + } + xhr = null; } - xhr.onreadystatechange = null; - _this.end(xhr.status); - xhr = null; + }; + + data.start = data.end; + data.end = Math.min(data.end + options.chunkSize, data.size ) - 1; + + var slice; + (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file; + + xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size); + xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + data.name); + + slice = data.file[slice](data.start, data.end + 1); + + xhr.send(slice); + slice = null; + } else { + // single piece upload + if( xhr.upload ){ + // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29 + xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){ + options.progress(evt, _this, options); + }, 100), false); } - }; + + xhr.onreadystatechange = function (){ + _this.status = xhr.status; + _this.statusText = xhr.statusText; + _this.readyState = xhr.readyState; + + if( xhr.readyState == 4 ){ + for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){ + _this['response'+k] = xhr['response'+k]; + } + xhr.onreadystatechange = null; + _this.end(xhr.status); + xhr = null; + } + }; - if( api.isArray(data) ){ - // multipart - xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); - data = data.join('') +'--_'+ api.expando +'--'; + if( api.isArray(data) ){ + // multipart + xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando); + data = data.join('') +'--_'+ api.expando +'--'; - /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ - if( xhr.sendAsBinary ){ - xhr.sendAsBinary(data); - } - else { - var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; }); - xhr.send(new Uint8Array(bytes).buffer); + /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */ + if( xhr.sendAsBinary ){ + xhr.sendAsBinary(data); + } + else { + var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; }); + xhr.send(new Uint8Array(bytes).buffer); + } + } else { + // FormData + xhr.send(data); } } - else { - xhr.send(data); - } } } }; diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js index bc80beba..7278d290 100644 --- a/lib/FileAPI.core.js +++ b/lib/FileAPI.core.js @@ -20,6 +20,8 @@ && !(/safari\//.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25 cors = html5 && ('withCredentials' in (new XMLHttpRequest)), + + chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice||Blob.prototype.mozSlice||Blob.prototype.slice), document = window.document, @@ -35,8 +37,16 @@ _rinput = /input/i, _rdata = /^data:[^,]+,/, - _KB = 1024, _pow = Math.pow, + _round = Math.round, + _num = Number, + _from = function (sz) { + return _round(sz * this); + }, + _KB = new _num(1024), + _MB = new _num(_pow(_KB, 2)), + _GB = new _num(_pow(_KB, 3)), + _TB = new _num(_pow(_KB, 4)), _elEvents = {}, // element event listeners _infoReader = [], // list of file info processors @@ -58,11 +68,14 @@ flashUrl: 0, // @default: './FileAPI.flash.swf' flashImageUrl: 0, // @default: './FileAPI.flash.image.swf' + + chunkSize : 0, + chunkUploadRetry : 0, - KB: _KB, - MB: _pow(_KB, 2), - GB: _pow(_KB, 3), - TB: _pow(_KB, 4), + KB: (_KB.from = _from, _KB), + MB: (_MB.from = _from, _MB), + GB: (_GB.from = _from, _GB), + TB: (_TB.from = _from, _TB), expando: 'fileapi' + (new Date).getTime(), @@ -103,6 +116,7 @@ dnd: cors && ('ondrop' in document.createElement('div')), cors: cors, html5: html5, + chunked: chunked, dataURI: true }, @@ -712,9 +726,10 @@ , filecomplete: api.F , progress: api.F , complete: api.F + , chunkSize: api.chunkSize + , chunkUpoloadRetry: api.chunkUploadRetry }, options); - if( options.imageAutoOrientation && !options.imageTransform ){ options.imageTransform = { rotate: 'auto' }; } @@ -747,7 +762,6 @@ // emit "beforeupload" event options.beforeupload(proxyXHR, options); - // Upload by file (function _nextFile(){ var