From 8d4eb539ba0f1974e4cd53789c2c90c07130c4fb Mon Sep 17 00:00:00 2001 From: James Carlos Date: Mon, 12 Aug 2013 01:14:03 -0700 Subject: [PATCH 001/211] add ability to use a target that contains a query string --- resumable.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index b9f6119e..96509846 100644 --- a/resumable.js +++ b/resumable.js @@ -389,7 +389,13 @@ var Resumable = function(opts){ params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); // Append the relevant chunk and send it - $.xhr.open("GET", $.getOpt('target') + '?' + params.join('&')); + var targetUrl = $.getOpt('target'); + if(targetUrl.indexOf('?') < 0) { + targetUrl += '?'; + } else { + targetUrl += '&'; + } + $.xhr.open("GET", targetUrl + params.join('&')); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { $.xhr.setRequestHeader(k, v); From 09beae9676e564b7da4d7398fd5cf25038333f3b Mon Sep 17 00:00:00 2001 From: James Carlos Date: Mon, 12 Aug 2013 01:50:26 -0700 Subject: [PATCH 002/211] add getTarget method to help compile target url and query parameters --- resumable.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/resumable.js b/resumable.js index 96509846..5f37fdc6 100644 --- a/resumable.js +++ b/resumable.js @@ -168,6 +168,15 @@ var Resumable = function(opts){ } else { return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB'; } + }, + getTarget:function(params){ + var target = $.getOpt('target'); + if(target.indexOf('?') < 0) { + target += '?'; + } else { + target += '&'; + } + return target + params.join('&'); } }; @@ -389,13 +398,7 @@ var Resumable = function(opts){ params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); // Append the relevant chunk and send it - var targetUrl = $.getOpt('target'); - if(targetUrl.indexOf('?') < 0) { - targetUrl += '?'; - } else { - targetUrl += '&'; - } - $.xhr.open("GET", targetUrl + params.join('&')); + $.xhr.open("GET", $h.getTarget(params)); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { $.xhr.setRequestHeader(k, v); @@ -490,7 +493,7 @@ var Resumable = function(opts){ $h.each(query, function(k,v){ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); }); - target += '?' + params.join('&'); + target = $h.getTarget(params); } else { // Add data from the query options data = new FormData(); From d207c731e87600297827563786236a93d0060691 Mon Sep 17 00:00:00 2001 From: Tommy Odom Date: Sun, 18 Aug 2013 07:08:44 -0400 Subject: [PATCH 003/211] Modified check for fileSuccess to base it off of status instead --- resumable.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/resumable.js b/resumable.js index 5f37fdc6..4cf37553 100644 --- a/resumable.js +++ b/resumable.js @@ -259,7 +259,7 @@ var Resumable = function(opts){ case 'success': if(_error) return; $.resumableObj.fire('fileProgress', $); // it's at least progress - if($.progress()==1) { + if($.isComplete()) { $.resumableObj.fire('fileSuccess', $, message); } break; @@ -331,8 +331,20 @@ var Resumable = function(opts){ } }); return(uploading); + }; + $.isComplete = function(){ + var outstanding = false; + $h.each($.chunks, function(chunk){ + var status = chunk.status(); + if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { + outstanding = true; + return(false); + } + }); + return(!outstanding); }; + // Bootstrap and return $.bootstrap(); return(this); @@ -601,14 +613,10 @@ var Resumable = function(opts){ // The are no more outstanding chunks to upload, check is everything is done var outstanding = false; $h.each($.files, function(file){ - $h.each(file.chunks, function(chunk){ - var status = chunk.status(); - if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { - outstanding = true; - return(false); - } - }); - if(outstanding) return(false); + if(!file.isComplete()) { + outstanding = true; + return(false); + } }); if(!outstanding) { // All chunks have been uploaded, complete From 8ce91a15d625da2c506db7ef4523c171e12641d4 Mon Sep 17 00:00:00 2001 From: Tommy Odom Date: Sun, 18 Aug 2013 07:24:15 -0400 Subject: [PATCH 004/211] Documented new ResumableFile.isComplete method --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c3f107c..415ee007 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Available configuration options are: * `.retry()` Retry uploading the file. * `.bootstrap()` Rebuild the state of a `ResumableFile` object, including reassigning chunks and XMLHttpRequest instances. * `.isUploading()` Returns a boolean indicating whether file chunks is uploading. +* `.isComplete()` Returns a boolean indicating whether the file has completed uploading and received a server response. ## Alternatives From afbe92a0e5071fc709131044a8051b76b77af665 Mon Sep 17 00:00:00 2001 From: Tommy Odom Date: Sun, 18 Aug 2013 07:23:40 -0400 Subject: [PATCH 005/211] Documented retry configuration options --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7c3f107c..ecd24e35 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ Available configuration options are: * `maxFileSizeErrorCallback(file, errorCount)` A function which displays an error a selected file is larger than allowed. (Default: displays an alert for every bad file.) * `fileType` The file types allowed to upload. An empty array allow any file type. (Default: `[]`) * `fileTypeErrorCallback(file, errorCount)` A function which displays an error a selected file has type not allowed. (Default: displays an alert for every bad file.) +* `maxChunkRetries` The maximum number of retries for a chunk before the upload is failed. Valid values are any positive integer and `undefined` for no limit. (Default: `undefined`) +* `chunkRetryInterval` The number of milliseconds to wait before retrying a chunk on a non-permanent error. Valid values are any positive integer and `undefined` for immediate retry. (Default: `undefined`) #### Properties From 203218533e294777b7e3304717bcc8abb4613622 Mon Sep 17 00:00:00 2001 From: "Guilherme W. O. Pereira" Date: Tue, 20 Aug 2013 18:48:25 -0300 Subject: [PATCH 006/211] file.fileName / file.name property in validations --- resumable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index 4cf37553..ea6dd5d4 100644 --- a/resumable.js +++ b/resumable.js @@ -57,15 +57,15 @@ var Resumable = function(opts){ }, minFileSize:1, minFileSizeErrorCallback:function(file, errorCount) { - alert(file.fileName +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); + 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 +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); + 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 +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); + alert(file.fileName||file.name +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); } }; $.opts = opts||{}; From 5280f25685c633132e60eb694e6f96d9b433557f Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Fri, 23 Aug 2013 10:39:45 +0200 Subject: [PATCH 007/211] Close #102 --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index ea6dd5d4..2b2a2717 100644 --- a/resumable.js +++ b/resumable.js @@ -49,7 +49,7 @@ var Resumable = function(opts){ generateUniqueIdentifier:null, maxChunkRetries:undefined, chunkRetryInterval:undefined, - permanentErrors:[415, 500, 501], + permanentErrors:[404, 415, 500, 501], maxFiles:undefined, maxFilesErrorCallback:function (files, errorCount) { var maxFiles = $.getOpt('maxFiles'); From e95ca3d138a64cda56824123caf5cbec05b0eeae Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Fri, 23 Aug 2013 10:40:33 +0200 Subject: [PATCH 008/211] Update to documentation after solving #102 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 318a4a7c..869f74af 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ You should allow for the same chunk to be uploaded more than once; this isn't st For every request, you can confirm reception in HTTP status codes (can be change through the `permanentErrors` option): * `200`: The chunk was accepted and correct. No need to re-upload. -* `415`. `500`, `501`: The file for which the chunk was uploaded is not supported, cancel the entire upload. +* `404`, `415`. `500`, `501`: The file for which the chunk was uploaded is not supported, cancel the entire upload. * _Anything else_: Something went wrong, but try reuploading the file. ## Handling GET (or `test()` requests) From f13540ed05f357dd446c019b41e6f5ec7ba1144f Mon Sep 17 00:00:00 2001 From: "francsics.balazs" Date: Thu, 15 Aug 2013 13:50:22 +0200 Subject: [PATCH 009/211] Ability to handle CORS --- resumable.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 2b2a2717..61346b4e 100644 --- a/resumable.js +++ b/resumable.js @@ -51,6 +51,7 @@ var Resumable = function(opts){ chunkRetryInterval:undefined, permanentErrors:[404, 415, 500, 501], maxFiles:undefined, + withCredentials:false, maxFilesErrorCallback:function (files, errorCount) { var maxFiles = $.getOpt('maxFiles'); alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); @@ -411,6 +412,7 @@ var Resumable = function(opts){ params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); // Append the relevant chunk and send it $.xhr.open("GET", $h.getTarget(params)); + $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { $.xhr.setRequestHeader(k, v); @@ -515,7 +517,8 @@ var Resumable = function(opts){ data.append($.getOpt('fileParameterName'), bytes); } - $.xhr.open('POST', target); + $.xhr.open('POST', target); + $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { $.xhr.setRequestHeader(k, v); From 86267fc816ba877adfd47d0bfb37990139822830 Mon Sep 17 00:00:00 2001 From: "francsics.balazs" Date: Thu, 15 Aug 2013 15:57:28 +0200 Subject: [PATCH 010/211] Update docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 869f74af..4fd2dcc2 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Available configuration options are: * `fileTypeErrorCallback(file, errorCount)` A function which displays an error a selected file has type not allowed. (Default: displays an alert for every bad file.) * `maxChunkRetries` The maximum number of retries for a chunk before the upload is failed. Valid values are any positive integer and `undefined` for no limit. (Default: `undefined`) * `chunkRetryInterval` The number of milliseconds to wait before retrying a chunk on a non-permanent error. Valid values are any positive integer and `undefined` for immediate retry. (Default: `undefined`) +* `withCredentials` Standard CORS requests do not send or set any cookies by default. In order to include cookies as part of the request, you need to set the `withCredentials` property to true. (Default: `false`) #### Properties From 13eb929f6bdbb5549be87f42880ef76060035c6f Mon Sep 17 00:00:00 2001 From: Chris Hutchinson Date: Sat, 21 Sep 2013 03:23:40 -0400 Subject: [PATCH 011/211] Reveal mime type --- resumable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resumable.js b/resumable.js index 61346b4e..71bcfbea 100644 --- a/resumable.js +++ b/resumable.js @@ -358,6 +358,7 @@ var Resumable = function(opts){ $.resumableObj = resumableObj; $.fileObj = fileObj; $.fileObjSize = fileObj.size; + $.fileObjType = fileObj.file.type; $.offset = offset; $.callback = callback; $.lastProgressCallback = (new Date); @@ -407,6 +408,7 @@ var Resumable = function(opts){ params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); + params.push(['resumableType', encodeURIComponent($.fileObjType)].join('=')); params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); @@ -483,6 +485,7 @@ var Resumable = function(opts){ resumableChunkSize: $.getOpt('chunkSize'), resumableCurrentChunkSize: $.endByte - $.startByte, resumableTotalSize: $.fileObjSize, + resumableType: $.fileObjType, resumableIdentifier: $.fileObj.uniqueIdentifier, resumableFilename: $.fileObj.fileName, resumableRelativePath: $.fileObj.relativePath, From 3219581527dadd34c6ca53de8853c685f84a6fff Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Sep 2013 12:01:12 +0100 Subject: [PATCH 012/211] Update to fileType detection, it now works. Previous implemented file.type is empty which fails this verification. --- resumable.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 71bcfbea..02b9b4b6 100644 --- a/resumable.js +++ b/resumable.js @@ -203,9 +203,12 @@ var Resumable = function(opts){ return false; } } - var files = []; + var files = [], fileName = '', fileType = ''; $h.each(fileList, function(file){ - if (o.fileType.length > 0 && !$h.contains(o.fileType, file.type.split('/')[1])) { + fileName = file.name.split('.'); + fileType = fileName[fileName.length-1]; + + if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) { o.fileTypeErrorCallback(file, errorCount++); return false; } From bd3b606faceedab8fe8cb1ddd271b23481798790 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Mon, 30 Sep 2013 00:50:47 +0200 Subject: [PATCH 013/211] Close #120 --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 02b9b4b6..dff76338 100644 --- a/resumable.js +++ b/resumable.js @@ -206,7 +206,7 @@ var Resumable = function(opts){ var files = [], fileName = '', fileType = ''; $h.each(fileList, function(file){ fileName = file.name.split('.'); - fileType = fileName[fileName.length-1]; + fileType = fileName[fileName.length-1].toLowerCase(); if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) { o.fileTypeErrorCallback(file, errorCount++); From 72693d40943f1f14e535e23ce6612598e07d5bbf Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Thu, 3 Oct 2013 21:53:48 +0200 Subject: [PATCH 014/211] Solves #121 where `fileProgress` would be fired in cases before `fileAdded`. --- resumable.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/resumable.js b/resumable.js index dff76338..6363b6c1 100644 --- a/resumable.js +++ b/resumable.js @@ -278,11 +278,15 @@ var Resumable = function(opts){ $.chunks = []; $.abort = function(){ // Stop current uploads + var abortCount = 0; $h.each($.chunks, function(c){ - if(c.status()=='uploading') c.abort(); - }); - $.resumableObj.fire('fileProgress', $); - }; + if(c.status()=='uploading') { + c.abort(); + abortCount++; + } + }); + if(abortCount>0) $.resumableObj.fire('fileProgress', $); + } $.cancel = function(){ // Reset this file to be void var _chunks = $.chunks; From 277c22d56e64109474061cae121f84a4e1f289db Mon Sep 17 00:00:00 2001 From: Chris Hutchinson Date: Tue, 8 Oct 2013 03:08:13 -0400 Subject: [PATCH 015/211] AMD/requirejs support, encapsulate function and expose to window/requirejs/node --- resumable.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index 6363b6c1..724cd063 100644 --- a/resumable.js +++ b/resumable.js @@ -1,5 +1,3 @@ -"use strict"; - /* * MIT Licensed * http://www.23developer.com/opensource @@ -7,6 +5,9 @@ * Steffen Tiedemann Christensen, steffen@23company.com */ +(function(){ +"use strict"; + var Resumable = function(opts){ if ( !(this instanceof Resumable ) ) { return new Resumable( opts ); @@ -768,6 +769,17 @@ var Resumable = function(opts){ // Node.js-style export for Node and Component -if(typeof module != 'undefined') { +if (typeof module != 'undefined') { module.exports = Resumable; } +// AMD/requirejs: Define the module +else if (typeof define !== 'undefined') { + define(function(){ + return Resumable; + }); +} +// Browser: Expose to window +else { + window.Resumable = Resumable; +} +})(); From 7fabccb87abcb54a8ff903139965696780ea2d1e Mon Sep 17 00:00:00 2001 From: Bert Sinnema Date: Tue, 15 Oct 2013 12:30:00 +0200 Subject: [PATCH 016/211] Fixes IE10 double-click issue for browseButton IE10 file input buttons hover right. This makes the input field as wide as the assigned node --- resumable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resumable.js b/resumable.js index 724cd063..5fd8232c 100644 --- a/resumable.js +++ b/resumable.js @@ -660,6 +660,9 @@ var Resumable = function(opts){ input.style.position = 'absolute'; input.style.top = input.style.left = input.style.bottom = input.style.right = 0; input.style.opacity = 0; + //IE10 file input buttons hover right. + //This makes the input field as wide as the assigned node for browseButton + input.style.width = domNode.clientWidth + 'px'; input.style.cursor = 'pointer'; domNode.appendChild(input); } From 7b0454ccf910285453356f858ae71a59d2a97520 Mon Sep 17 00:00:00 2001 From: Bert Sinnema Date: Sat, 19 Oct 2013 18:16:31 +0200 Subject: [PATCH 017/211] Ruby MD initial --- samples/Backend on Ruby On Rails.md | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 samples/Backend on Ruby On Rails.md diff --git a/samples/Backend on Ruby On Rails.md b/samples/Backend on Ruby On Rails.md new file mode 100644 index 00000000..2d98c411 --- /dev/null +++ b/samples/Backend on Ruby On Rails.md @@ -0,0 +1,74 @@ +# Sample server implementation in Ruby on Rails + +[Bert Sinnema](https://attaching.it) has provided this sample implementation for Ruby on Rails. + +This is a sample backend controller for Ruby on Rails (3.2) + +Tested on ruby 2.0.0 + +######config/routes.rb +Add a chunk resource to the routes file + +```ruby +resource :chunk, :only => [:create, :show] +``` +######app/controllers/chunks_controller.rb +Add a chunks controller + +```ruby + + class ChunksController < ApplicationController + layout nil + + #GET /chunk + def show + #chunk folder path based on the parameters + dir = "/tmp/#{params[:resumableIdentifier]}" + #chunk path based on the parameters + chunk = "#{dir}/#{params[:resumableFilename]}.part#{params[:resumableChunkNumber]}" + + if File.exists?(chunk) + #Let resumable.js know this chunk already exists + render :nothing => true, :status => 200 + else + #Let resumable.js know this chunk doesnt exists and needs to be uploaded + render :nothing => true, :status => 404 + end + + end + + #POST /chunk + def create + + #chunk folder path based on the parameters + dir = "/tmp/#{params[:resumableIdentifier]}" + #chunk path based on the parameters + chunk = "#{dir}/#{params[:resumableFilename]}.part#{params[:resumableChunkNumber]}" + + + #Create chunks directory when not present on system + if !File.directory?(dir) + FileUtils.mkdir(dir, :mode => 0700) + end + + #Move the uploaded chunk to the directory + FileUtils.mv params[:file].tempfile, chunk + + #Concatenate all the partial files into the original file + + currentSize = params[:resumableChunkNumber].to_i * params[:resumableChunkSize].to_i + filesize = params[:resumableTotalSize].to_i + + if (currentSize + params[:resumableCurrentChunkSize].to_i) >= filesize + target = File.new() + for i in 1..params[:resumableChunkNumber].to_i + puts "Value of local variable is #{i}" + end + end + + + + render :nothing => true, :status => 200 + end + end +``` From 58e5cbf10f9b215866273b3e28d3c220ebfc2a20 Mon Sep 17 00:00:00 2001 From: Bert Sinnema Date: Sun, 20 Oct 2013 13:58:52 +0200 Subject: [PATCH 018/211] Updated ruby MD --- samples/Backend on Ruby On Rails.md | 48 +++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/samples/Backend on Ruby On Rails.md b/samples/Backend on Ruby On Rails.md index 2d98c411..f008ec09 100644 --- a/samples/Backend on Ruby On Rails.md +++ b/samples/Backend on Ruby On Rails.md @@ -1,6 +1,6 @@ # Sample server implementation in Ruby on Rails -[Bert Sinnema](https://attaching.it) has provided this sample implementation for Ruby on Rails. +[Bert Sinnema](https://github.com/bertsinnema) has provided this sample implementation for Ruby on Rails. This is a sample backend controller for Ruby on Rails (3.2) @@ -16,7 +16,7 @@ resource :chunk, :only => [:create, :show] Add a chunks controller ```ruby - + class ChunksController < ApplicationController layout nil @@ -35,7 +35,7 @@ Add a chunks controller render :nothing => true, :status => 404 end - end + end #POST /chunk def create @@ -45,7 +45,6 @@ Add a chunks controller #chunk path based on the parameters chunk = "#{dir}/#{params[:resumableFilename]}.part#{params[:resumableChunkNumber]}" - #Create chunks directory when not present on system if !File.directory?(dir) FileUtils.mkdir(dir, :mode => 0700) @@ -59,16 +58,45 @@ Add a chunks controller currentSize = params[:resumableChunkNumber].to_i * params[:resumableChunkSize].to_i filesize = params[:resumableTotalSize].to_i + #When all chunks are uploaded if (currentSize + params[:resumableCurrentChunkSize].to_i) >= filesize - target = File.new() - for i in 1..params[:resumableChunkNumber].to_i - puts "Value of local variable is #{i}" - end + + #Create a target file + File.open("#{dir}/#{params[:resumableFilename]}","a") do |target| + #Loop trough the chunks + for i in 1..params[:resumableChunkNumber].to_i + #Select the chunk + chunk = File.open("#{dir}/#{params[:resumableFilename]}.part#{i}", 'r').read + + #Write chunk into target file + chunk.each_line do |line| + target << line + end + + #Deleting chunk + FileUtils.rm "#{dir}/#{params[:resumableFilename]}.part#{i}", :force => true + end + puts "File saved to #{dir}/#{params[:resumableFilename]}" + end end - - render :nothing => true, :status => 200 end + end ``` + +###### Resumable.js configuration +Ruby on Rails needs X-CSRF-Token headers. You can pass this is in the headers option. The token should be in a meta tag of the application layout file. In this example is used coffeescript. + +```coffeescript + + jQuery -> + r = new Resumable + target: "/chunk" + headers: + 'X-CSRF-Token' : $('meta[name="csrf-token"]').attr('content') + + if !r.support + alert('No Support!!!!!') +``` \ No newline at end of file From 3d60bff552f70928787dc438d888a92763df59c8 Mon Sep 17 00:00:00 2001 From: Andreas Savvides Date: Wed, 23 Oct 2013 13:06:59 +0100 Subject: [PATCH 019/211] Add reference to HTLM5 File API --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fd2dcc2..b40dff8a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## What is Resumable.js -Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API. +Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the [HTML5 File API](http://www.w3.org/TR/FileAPI/). The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each file into small chunks. Then, 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, resume and even recover uploads without losing state because only the currently uploading chunks will be aborted, not the entire upload. From 3ef6c962a1ed4d119889fc1c2df20781f86e6321 Mon Sep 17 00:00:00 2001 From: Andreas Savvides Date: Wed, 23 Oct 2013 13:18:30 +0100 Subject: [PATCH 020/211] Minor changes to wording for drag & drop functionality --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b40dff8a..fa9b5544 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## What is Resumable.js -Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the [HTML5 File API](http://www.w3.org/TR/FileAPI/). +Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the [`HTML5 File API`](http://www.w3.org/TR/FileAPI/). The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each file into small chunks. Then, 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, resume and even recover uploads without losing state because only the currently uploading chunks will be aborted, not the entire upload. @@ -20,7 +20,7 @@ A new `Resumable` object is created with information of what and where to post: // Resumable.js isn't supported, fall back on a different method if(!r.support) location.href = '/some-old-crappy-uploader'; -To allow files to be either selected and drag-dropped, you'll assign drop target and a DOM item to be clicked for browsing: +To allow files to be selected and drag-dropped, you need to assign a drop target and a DOM item to be clicked for browsing: r.assignBrowse(document.getElementById('browseButton')); r.assignDrop(document.getElementById('dropTarget')); @@ -30,7 +30,7 @@ After this, interaction with Resumable.js is done by listening to events: r.on('fileAdded', function(file, event){ ... }); - r.on('fileSuccess', function(file,message){ + r.on('fileSuccess', function(file, message){ ... }); r.on('fileError', function(file, message){ From 5234a6171473b12963e37658d7c55af8740580df Mon Sep 17 00:00:00 2001 From: Andreas Savvides Date: Wed, 23 Oct 2013 13:22:08 +0100 Subject: [PATCH 021/211] Being a bit more clear about the simplicity of server-side handling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa9b5544..8ccdf6ff 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ After this, interaction with Resumable.js is done by listening to events: ## How do I set it up with my server? -Most of the magic for Resumable.js happens in the user's browser, but files still need to be reassembled from chunks on the server side. This should be a fairly simple task and can be achieved in any web framework or language, which is able to receive file uploads. +Most of the magic for Resumable.js happens in the user's browser, but files still need to be reassembled from chunks on the server side. This should be a fairly simple task, which and can be achieved using any web framework or language that is capable of handling file uploads. To handle the state of upload chunks, a number of extra parameters are sent along with all requests: From c5c8599ce0821aabc4b40d31a465969b81499861 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Thu, 24 Oct 2013 12:36:42 +0200 Subject: [PATCH 022/211] Fixes for indentations and inconsistent use of string quoting, close #131. --- resumable.js | 1279 +++++++++++++++++++++++++------------------------- 1 file changed, 639 insertions(+), 640 deletions(-) diff --git a/resumable.js b/resumable.js index 5fd8232c..c8bf6313 100644 --- a/resumable.js +++ b/resumable.js @@ -8,170 +8,170 @@ (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', - throttleProgressCallbacks:0.5, - query:{}, - headers:{}, - preprocess:null, - method:'multipart', - prioritizeFirstAndLastChunk:false, - target:'/', - testChunks:true, - generateUniqueIdentifier:null, - maxChunkRetries:undefined, - chunkRetryInterval:undefined, - permanentErrors:[404, 415, 500, 501], - maxFiles:undefined, - withCredentials:false, - maxFilesErrorCallback:function (files, errorCount) { - var maxFiles = $.getOpt('maxFiles'); - alert('Please upload ' + 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 $this = this; - // Get multiple option if passed an array - if(o instanceof Array) { - var options = {}; - $h.each(o, function(option){ - options[option] = $this.getOpt(option); - }); - return options; - } - // Otherwise, just return a simple option - if ($this instanceof ResumableChunk) { - if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; } - else { $this = $this.fileObj; } + var Resumable = function(opts){ + if ( !(this instanceof Resumable) ) { + return new Resumable(opts); } - if ($this instanceof ResumableFile) { - if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; } - else { $this = $this.resumableObj; } - } - if ($this instanceof Resumable) { - if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; } - else { return $this.defaults[o]; } - } - }; - - // EVENTS - // catchAll(event, ...) - // fileSuccess(file), fileProgress(file), fileAdded(file, event), 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 && !$h.contains(o.fileType, fileType)) { - o.fileTypeErrorCallback(file, errorCount++); - return false; + o.fileTypeErrorCallback(file, errorCount++); + return false; } if (typeof(o.minFileSize)!=='undefined' && file.sizeo.maxFileSize) { - o.maxFileSizeErrorCallback(file, errorCount++); - return false; + o.maxFileSizeErrorCallback(file, errorCount++); + return false; } // directories have size == 0 @@ -231,372 +231,372 @@ var Resumable = function(opts){ $.fire('fileAdded', f, event); } }); - $.fire('filesAdded', files); - }; - - // INTERNAL OBJECT TYPES - function ResumableFile(resumableObj, file){ - 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.webkitRelativePath || $.fileName; - $.uniqueIdentifier = $h.generateUniqueIdentifier(file); - var _error = false; - - // 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', $); - break; - case 'error': - $.abort(); - _error = true; - $.chunks = []; - $.resumableObj.fire('fileError', $, message); - break; - case 'success': - if(_error) return; - $.resumableObj.fire('fileProgress', $); // it's at least progress - if($.isComplete()) { - $.resumableObj.fire('fileSuccess', $, message); - } - break; - case 'retry': - $.resumableObj.fire('fileRetry', $); - break; - } + $.fire('filesAdded', files); }; - // 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++; + // INTERNAL OBJECT TYPES + function ResumableFile(resumableObj, file){ + 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.webkitRelativePath || $.fileName; + $.uniqueIdentifier = $h.generateUniqueIdentifier(file); + var _error = false; + + // 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', $); + break; + case 'error': + $.abort(); + _error = true; + $.chunks = []; + $.resumableObj.fire('fileError', $, message); + break; + case 'success': + if(_error) return; + $.resumableObj.fire('fileProgress', $); // it's at least progress + if($.isComplete()) { + $.resumableObj.fire('fileSuccess', $, message); + } + break; + case 'retry': + $.resumableObj.fire('fileRetry', $); + break; } - }); - if(abortCount>0) $.resumableObj.fire('fileProgress', $); - } - $.cancel = function(){ - // Reset this file to be void - var _chunks = $.chunks; + }; + + // Main code to set up a file object with chunks, + // packaged to be able to handle retries if needed. $.chunks = []; - // Stop current uploads - $h.each(_chunks, function(c){ + $.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(); - $.resumableObj.upload(); - }; - $.bootstrap = function(){ - $.abort(); + $.resumableObj.removeFile($); + $.resumableObj.fire('fileProgress', $); + }; + $.retry = function(){ + $.bootstrap(); + $.resumableObj.upload(); + }; + $.bootstrap = function(){ + $.abort(); _error = false; - // Rebuild stack of chunks from file - $.chunks = []; - $._prevProgress = 0; - var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; - for (var offset=0; offset0.999 ? 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; - $h.each($.chunks, function(chunk){ - var status = chunk.status(); - if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { - outstanding = true; - return(false); - } - }); - return(!outstanding); - }; - + ret = (error ? 1 : (ret>0.999 ? 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; + $h.each($.chunks, function(chunk){ + var status = chunk.status(); + if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) { + outstanding = true; + return(false); + } + }); + return(!outstanding); + }; - // Bootstrap and return - $.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 - - // 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; + // Bootstrap and return + $.bootstrap(); + return(this); } - $.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); - - // Add data from the query options - var params = []; - var customQuery = $.getOpt('query'); - if(typeof customQuery == "function") customQuery = customQuery($.fileObj, $); - $h.each(customQuery, function(k,v){ + + 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 + + // 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); + + // Add data from the query options + var params = []; + var customQuery = $.getOpt('query'); + if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); + $h.each(customQuery, function(k,v){ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); }); - // Add extra data to identify chunk - params.push(['resumableChunkNumber', encodeURIComponent($.offset+1)].join('=')); - params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); - params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); - params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); - params.push(['resumableType', encodeURIComponent($.fileObjType)].join('=')); - params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); - params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); - params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); - // Append the relevant chunk and send it - $.xhr.open("GET", $h.getTarget(params)); - $.xhr.withCredentials = $.getOpt('withCredentials'); - // Add data from header options - $h.each($.getOpt('headers'), function(k,v) { - $.xhr.setRequestHeader(k, v); - }); - $.xhr.send(null); - }; + // Add extra data to identify chunk + params.push(['resumableChunkNumber', encodeURIComponent($.offset+1)].join('=')); + params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); + params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); + params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); + params.push(['resumableType', encodeURIComponent($.fileObjType)].join('=')); + params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); + params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); + params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); + // Append the relevant chunk and send it + $.xhr.open('GET', $h.getTarget(params)); + $.xhr.withCredentials = $.getOpt('withCredentials'); + // Add data from header options + $h.each($.getOpt('headers'), function(k,v) { + $.xhr.setRequestHeader(k, v); + }); + $.xhr.send(null); + }; - $.preprocessFinished = function(){ - $.preprocessState = 2; - $.send(); - }; + $.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) { + // send() uploads the actual data in a POST call + $.send = function(){ + var preprocess = $.getOpt('preprocess'); + if(typeof preprocess === 'function') { + switch($.preprocessState) { case 0: preprocess($); $.preprocessState = 1; return; case 1: return; case 2: break; + } + } + if($.getOpt('testChunks') && !$.tested) { + $.test(); + return; } - } - if($.getOpt('testChunks') && !$.tested) { - $.test(); - return; - } - // Set up request and listen for event - $.xhr = new XMLHttpRequest(); + // Set up request and listen for event + $.xhr = new XMLHttpRequest(); - // Progress - $.xhr.upload.addEventListener("progress", function(e){ + // 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); + $.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 { - $.send(); + $.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); + + // Set up the basic query data from Resumable + var query = { + resumableChunkNumber: $.offset+1, + resumableChunkSize: $.getOpt('chunkSize'), + resumableCurrentChunkSize: $.endByte - $.startByte, + resumableTotalSize: $.fileObjSize, + resumableType: $.fileObjType, + resumableIdentifier: $.fileObj.uniqueIdentifier, + resumableFilename: $.fileObj.fileName, + resumableRelativePath: $.fileObj.relativePath, + resumableTotalChunks: $.fileObj.chunks.length + }; + // 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'))), + bytes = $.fileObj.file[func]($.startByte,$.endByte), + data = null, + target = $.getOpt('target'); + + if ($.getOpt('method') === 'octet') { + // Add data from the query options + data = bytes; + var params = []; + $h.each(query, function(k,v){ + params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); + }); + target = $h.getTarget(params); + } else { + // Add data from the query options + data = new FormData(); + $h.each(query, function(k,v){ + data.append(k,v); + }); + data.append($.getOpt('fileParameterName'), bytes); } + + $.xhr.open('POST', target); + $.xhr.withCredentials = $.getOpt('withCredentials'); + // Add data from header options + $h.each($.getOpt('headers'), function(k,v) { + $.xhr.setRequestHeader(k, v); + }); + $.xhr.send(data); }; - $.xhr.addEventListener("load", doneHandler, false); - $.xhr.addEventListener("error", doneHandler, false); - - // Set up the basic query data from Resumable - var query = { - resumableChunkNumber: $.offset+1, - resumableChunkSize: $.getOpt('chunkSize'), - resumableCurrentChunkSize: $.endByte - $.startByte, - resumableTotalSize: $.fileObjSize, - resumableType: $.fileObjType, - resumableIdentifier: $.fileObj.uniqueIdentifier, - resumableFilename: $.fileObj.fileName, - resumableRelativePath: $.fileObj.relativePath, - resumableTotalChunks: $.fileObj.chunks.length + $.abort = function(){ + // Abort and reset + if($.xhr) $.xhr.abort(); + $.xhr = null; }; - // 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'))), - bytes = $.fileObj.file[func]($.startByte,$.endByte), - data = null, - target = $.getOpt('target'); - - if ($.getOpt('method') === 'octet') { - // Add data from the query options - data = bytes; - var params = []; - $h.each(query, function(k,v){ - params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); - }); - target = $h.getTarget(params); - } else { - // Add data from the query options - data = new FormData(); - $h.each(query, function(k,v){ - data.append(k,v); - }); - data.append($.getOpt('fileParameterName'), bytes); - } - - $.xhr.open('POST', target); - $.xhr.withCredentials = $.getOpt('withCredentials'); - // Add data from header options - $h.each($.getOpt('headers'), function(k,v) { - $.xhr.setRequestHeader(k, v); - }); - $.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(!$.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) { - // HTTP 200, perfect - return('success'); - } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { - // HTTP 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(); + $.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(!$.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) { + // HTTP 200, perfect + return('success'); + } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { + // HTTP 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); - 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); - } + }; + $.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); + 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; + // 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){ + // 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; @@ -608,63 +608,63 @@ var Resumable = function(opts){ return(false); } }); - if(found) return(true); - } + if(found) return(true); + } - // Now, simply look for the next, best thing to upload - $h.each($.files, function(file){ + // Now, simply look for the next, best thing to upload + $h.each($.files, function(file){ $h.each(file.chunks, function(chunk){ - if(chunk.status()=='pending' && chunk.preprocessState === 0) { - chunk.send(); - found = true; - return(false); - } - }); + if(chunk.status()=='pending' && chunk.preprocessState === 0) { + chunk.send(); + found = true; + return(false); + } + }); if(found) return(false); }); - if(found) return(true); + 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){ + // 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); + outstanding = true; + return(false); } }); - if(!outstanding) { - // All chunks have been uploaded, complete - $.fire('complete'); - } - 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]; + // PUBLIC METHODS FOR RESUMABLE.JS + $.assignBrowse = function(domNodes, isDirectory){ + if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; - // We will create an and overlay it on the domNode - // (crappy, but since HTML5 doesn't have a cross-browser.browse() method we haven't a choice. - // FF4+ allows click() for this though: https://developer.mozilla.org/en/using_files_from_web_applications) - $h.each(domNodes, function(domNode) { + // We will create an and overlay it on the domNode + // (crappy, but since HTML5 doesn't have a cross-browser.browse() method we haven't a choice. + // FF4+ allows click() for this though: https://developer.mozilla.org/en/using_files_from_web_applications) + $h.each(domNodes, function(domNode) { var input; if(domNode.tagName==='INPUT' && domNode.type==='file'){ - input = domNode; + input = domNode; } else { - input = document.createElement('input'); - input.setAttribute('type', 'file'); - // Place with the dom node an position the input to fill the entire space - domNode.style.display = 'inline-block'; - domNode.style.position = 'relative'; - input.style.position = 'absolute'; - input.style.top = input.style.left = input.style.bottom = input.style.right = 0; - input.style.opacity = 0; - //IE10 file input buttons hover right. - //This makes the input field as wide as the assigned node for browseButton - input.style.width = domNode.clientWidth + 'px'; - input.style.cursor = 'pointer'; - domNode.appendChild(input); + input = document.createElement('input'); + input.setAttribute('type', 'file'); + // Place with the dom node an position the input to fill the entire space + domNode.style.display = 'inline-block'; + domNode.style.position = 'relative'; + input.style.position = 'absolute'; + input.style.top = input.style.left = input.style.bottom = input.style.right = 0; + input.style.opacity = 0; + //IE10 file input buttons hover right. + //This makes the input field as wide as the assigned node for browseButton + input.style.width = domNode.clientWidth + 'px'; + input.style.cursor = 'pointer'; + domNode.appendChild(input); } var maxFiles = $.getOpt('maxFiles'); if (typeof(maxFiles)==='undefined'||maxFiles!=1){ @@ -679,110 +679,109 @@ var Resumable = function(opts){ } // When new files are added, simply append them to the overall list input.addEventListener('change', function(e){ - appendFilesFromFileList(e.target.files); - e.target.value = ''; + appendFilesFromFileList(e.target.files); + e.target.value = ''; }, false); - }); - }; - $.assignDrop = function(domNodes){ - if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; + }); + }; + $.assignDrop = function(domNodes){ + if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; - $h.each(domNodes, function(domNode) { + $h.each(domNodes, function(domNode) { domNode.addEventListener('dragover', onDragOver, false); domNode.addEventListener('drop', onDrop, false); }); - }; - $.unAssignDrop = function(domNodes) { - if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; + }; + $.unAssignDrop = function(domNodes) { + if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; - $h.each(domNodes, function(domNode) { + $h.each(domNodes, function(domNode) { domNode.removeEventListener('dragover', onDragOver); domNode.removeEventListener('drop', onDrop); }); - }; - $.isUploading = function(){ - var uploading = false; - $h.each($.files, function(file){ - if (file.isUploading()) { - uploading = true; - return(false); + }; + $.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(); } - }); - 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){ + }; + $.pause = function(){ + // Resume all chunks currently being uploaded + $h.each($.files, function(file){ file.abort(); }); - $.fire('pause'); - }; - $.cancel = function(){ - 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){ + $.fire('pause'); + }; + $.cancel = function(){ + 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){ - appendFilesFromFileList([file]); - }; - $.removeFile = function(file){ - for(var i = $.files.length - 1; i >= 0; i--) { - if($.files[i] === file) { - $.files.splice(i, 1); + return(totalSize>0 ? totalDone/totalSize : 0); + }; + $.addFile = function(file){ + appendFilesFromFileList([file]); + }; + $.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){ + }; + $.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){ + return(ret); + }; + $.getSize = function(){ + var totalSize = 0; + $h.each($.files, function(file){ totalSize += file.size; }); - return(totalSize); + return(totalSize); + }; + + return(this); }; - return(this); -}; - - -// Node.js-style export for Node and Component -if (typeof module != 'undefined') { - module.exports = Resumable; -} -// AMD/requirejs: Define the module -else if (typeof define !== 'undefined') { - define(function(){ - return Resumable; - }); -} -// Browser: Expose to window -else { - window.Resumable = Resumable; -} + + // Node.js-style export for Node and Component + if (typeof module != 'undefined') { + module.exports = Resumable; + } else if (typeof define !== 'undefined') { + // AMD/requirejs: Define the module + define(function(){ + return Resumable; + }); + } else { + // Browser: Expose to window + window.Resumable = Resumable; + } + })(); From 6efdc350f261f14210fbb2f73f02fd04ebc3a756 Mon Sep 17 00:00:00 2001 From: Chris Hutchinson Date: Sat, 2 Nov 2013 01:41:06 -0400 Subject: [PATCH 023/211] r.js namespace support Fixes https://github.com/23/resumable.js/issues/132 --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index c8bf6313..9277fb3f 100644 --- a/resumable.js +++ b/resumable.js @@ -774,7 +774,7 @@ // Node.js-style export for Node and Component if (typeof module != 'undefined') { module.exports = Resumable; - } else if (typeof define !== 'undefined') { + } else if (typeof define === "function" && define.amd) { // AMD/requirejs: Define the module define(function(){ return Resumable; From 9f3afdbf88509fd6cf1bdd822264fd3f766b0a5e Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Tue, 17 Dec 2013 22:14:54 +0100 Subject: [PATCH 024/211] Close #137 by allowing a timeout to be set on requests. If a request times out, this will fire like any other error -- and trigger a retry --- resumable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resumable.js b/resumable.js index 9277fb3f..b2739b08 100644 --- a/resumable.js +++ b/resumable.js @@ -53,6 +53,7 @@ permanentErrors:[404, 415, 500, 501], maxFiles:undefined, withCredentials:false, + xhrTimeout:0, maxFilesErrorCallback:function (files, errorCount) { var maxFiles = $.getOpt('maxFiles'); alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); @@ -422,6 +423,7 @@ params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); // Append the relevant chunk and send it $.xhr.open('GET', $h.getTarget(params)); + $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { @@ -529,6 +531,7 @@ } $.xhr.open('POST', target); + $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options $h.each($.getOpt('headers'), function(k,v) { From bc983df9f8242ea3b16d6645023a3ddb59e3f8eb Mon Sep 17 00:00:00 2001 From: Michael Heuberger Date: Sun, 29 Dec 2013 14:46:51 +1300 Subject: [PATCH 025/211] add missing bower.json --- bower.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bower.json diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..62c3ad79 --- /dev/null +++ b/bower.json @@ -0,0 +1,14 @@ +{ + "name": "resumable.js", + "version": "1.0.0", + "main": "resumable.js", + "ignore": [ + ".gitignore", + "*.md" + ], + "keywords": [ + "HTML5 File API", + "Upload", + "Large files" + ] +} From b24b2ffaf97ebc74b2cefcdc60f7b40cabf6e8d9 Mon Sep 17 00:00:00 2001 From: Devin Tan Date: Mon, 13 Jan 2014 13:31:24 +0800 Subject: [PATCH 026/211] Update UploadServlet.java When i tried to upload a big file over 2.7G, the server give me an Exception " Negative Seek Offset". This issue is due to integer overflow. --- samples/java/src/resumable/js/upload/UploadServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/java/src/resumable/js/upload/UploadServlet.java b/samples/java/src/resumable/js/upload/UploadServlet.java index b10a3f57..6b9f8f63 100644 --- a/samples/java/src/resumable/js/upload/UploadServlet.java +++ b/samples/java/src/resumable/js/upload/UploadServlet.java @@ -27,7 +27,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) RandomAccessFile raf = new RandomAccessFile(info.resumableFilePath, "rw"); //Seek to position - raf.seek((resumableChunkNumber - 1) * info.resumableChunkSize); + raf.seek((resumableChunkNumber - 1) * (long)info.resumableChunkSize); //Save to file InputStream is = request.getInputStream(); From acbff6c2313b452bcb940b56d8968a9b3f16e84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Czy=C5=BCewski?= Date: Mon, 13 Jan 2014 20:27:47 +0100 Subject: [PATCH 027/211] Added pause() method for ResumableFile --- resumable.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/resumable.js b/resumable.js index b2739b08..9628cd0a 100644 --- a/resumable.js +++ b/resumable.js @@ -247,6 +247,7 @@ $.size = file.size; $.relativePath = file.webkitRelativePath || $.fileName; $.uniqueIdentifier = $h.generateUniqueIdentifier(file); + $._pause = false; var _error = false; // Callback when something happens within the chunk @@ -353,6 +354,16 @@ }); return(!outstanding); }; + $.pause = function(pause){ + if(typeof(pause)==='undefined'){ + $._pause = ($._pause ? false : true); + }else{ + $._pause = pause; + } + }; + $.isPaused = function() { + return $._pause; + }; // Bootstrap and return @@ -616,13 +627,15 @@ // Now, simply look for the next, best thing to upload $h.each($.files, function(file){ - $h.each(file.chunks, function(chunk){ - if(chunk.status()=='pending' && chunk.preprocessState === 0) { - chunk.send(); - found = true; - return(false); - } - }); + if(file.isPaused()===false){ + $h.each(file.chunks, function(chunk){ + if(chunk.status()=='pending' && chunk.preprocessState === 0) { + chunk.send(); + found = true; + return(false); + } + }); + } if(found) return(false); }); if(found) return(true); From b5ff4079a0cd184311a4f6b38c3d10349f559773 Mon Sep 17 00:00:00 2001 From: Axel Rasmussen Date: Tue, 21 Jan 2014 10:32:51 -0700 Subject: [PATCH 028/211] Make the assignBrowse implementation cleaner. --- resumable.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/resumable.js b/resumable.js index 9628cd0a..91930868 100644 --- a/resumable.js +++ b/resumable.js @@ -660,9 +660,6 @@ $.assignBrowse = function(domNodes, isDirectory){ if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; - // We will create an and overlay it on the domNode - // (crappy, but since HTML5 doesn't have a cross-browser.browse() method we haven't a choice. - // FF4+ allows click() for this though: https://developer.mozilla.org/en/using_files_from_web_applications) $h.each(domNodes, function(domNode) { var input; if(domNode.tagName==='INPUT' && domNode.type==='file'){ @@ -670,16 +667,10 @@ } else { input = document.createElement('input'); input.setAttribute('type', 'file'); - // Place with the dom node an position the input to fill the entire space - domNode.style.display = 'inline-block'; - domNode.style.position = 'relative'; - input.style.position = 'absolute'; - input.style.top = input.style.left = input.style.bottom = input.style.right = 0; - input.style.opacity = 0; - //IE10 file input buttons hover right. - //This makes the input field as wide as the assigned node for browseButton - input.style.width = domNode.clientWidth + 'px'; - input.style.cursor = 'pointer'; + input.style.display = 'none'; + domNode.addEventListener('click', function(){ + input.click(); + }, false); domNode.appendChild(input); } var maxFiles = $.getOpt('maxFiles'); From 4196d0d3f95e4af036ec733e94ba5c4b24f9ad49 Mon Sep 17 00:00:00 2001 From: Tobias Oetiker Date: Mon, 17 Feb 2014 00:13:53 +0100 Subject: [PATCH 029/211] For large files, the time spent in creating chunk objects is pretty substatial. This patch provides additional events for observing this process: +* `.chunkingStart(file)` Started preparing file for upload +* `.chunkingProgress(file,ratio)` Show progress in file preparation +* `.chunkingComplete(file) File is ready for upload The browser interface does not get updated during long running javascript programms (like the one which create all the chunk objects). This can be fixed by running parts of the long-running code via window.setTimeout so the second part of the patch does exactly this. --- README.md | 3 +++ resumable.js | 36 ++++++++++++++++++++++++------------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8ccdf6ff..629882f4 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,9 @@ Available configuration options are: * `.error(message, file)` An error, including fileError, occured. * `.pause()` Uploading was paused. * `.cancel()` Uploading was canceled. +* `.chunkingStart(file)` Started preparing file for upload +* `.chunkingProgress(file,ratio)` Show progress in file preparation +* `.chunkingComplete(file) File is ready for upload * `.catchAll(event, ...)` Listen to all the events listed above with the same callback function. ### ResumableFile diff --git a/resumable.js b/resumable.js index 91930868..275fd561 100644 --- a/resumable.js +++ b/resumable.js @@ -205,10 +205,10 @@ return false; } } - var files = [], fileName = '', fileType = ''; + var files = []; $h.each(fileList, function(file){ - fileName = file.name.split('.'); - fileType = fileName[fileName.length-1].toLowerCase(); + var fileName = file.name.split('.'); + var fileType = fileName[fileName.length-1].toLowerCase(); if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) { o.fileTypeErrorCallback(file, errorCount++); @@ -225,14 +225,18 @@ } // directories have size == 0 - if (!$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) { + if (!$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) {(function(){ var f = new ResumableFile($, file); - $.files.push(f); - files.push(f); - $.fire('fileAdded', f, event); - } + window.setTimeout(function(){ + $.files.push(f); + files.push(f); + $.fire('fileAdded', f, event) + },0); + })()}; }); - $.fire('filesAdded', files); + window.setTimeout(function(){ + $.fire('filesAdded', files) + },0); }; // INTERNAL OBJECT TYPES @@ -315,9 +319,16 @@ $.chunks = []; $._prevProgress = 0; var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; - for (var offset=0; offset Date: Tue, 4 Mar 2014 13:53:37 +0400 Subject: [PATCH 030/211] Added reference to container in file & improved Opera compatability --- resumable.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 275fd561..17cae92f 100644 --- a/resumable.js +++ b/resumable.js @@ -230,6 +230,7 @@ window.setTimeout(function(){ $.files.push(f); files.push(f); + f.container = event.srcElement; $.fire('fileAdded', f, event) },0); })()}; @@ -252,6 +253,7 @@ $.relativePath = file.webkitRelativePath || $.fileName; $.uniqueIdentifier = $h.generateUniqueIdentifier(file); $._pause = false; + $.container = ''; var _error = false; // Callback when something happens within the chunk @@ -681,7 +683,11 @@ 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); } @@ -698,7 +704,7 @@ } // When new files are added, simply append them to the overall list input.addEventListener('change', function(e){ - appendFilesFromFileList(e.target.files); + appendFilesFromFileList(e.target.files,e); e.target.value = ''; }, false); }); From d965de67b473e979219bb0a7938f4d454c4a54ee Mon Sep 17 00:00:00 2001 From: edtechd Date: Wed, 5 Mar 2014 23:44:18 +0400 Subject: [PATCH 031/211] Indentation fix --- resumable.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resumable.js b/resumable.js index 17cae92f..eee293f1 100644 --- a/resumable.js +++ b/resumable.js @@ -230,7 +230,7 @@ window.setTimeout(function(){ $.files.push(f); files.push(f); - f.container = event.srcElement; + f.container = event.srcElement; $.fire('fileAdded', f, event) },0); })()}; @@ -253,7 +253,7 @@ $.relativePath = file.webkitRelativePath || $.fileName; $.uniqueIdentifier = $h.generateUniqueIdentifier(file); $._pause = false; - $.container = ''; + $.container = ''; var _error = false; // Callback when something happens within the chunk @@ -683,11 +683,11 @@ input.setAttribute('type', 'file'); input.style.display = 'none'; domNode.addEventListener('click', function(){ - input.style.opacity = 0; - input.style.display='block'; - input.focus(); + input.style.opacity = 0; + input.style.display='block'; + input.focus(); input.click(); - input.style.display='none'; + input.style.display='none'; }, false); domNode.appendChild(input); } From ffb5626e8490997658da3602e622f0a6b39e191a Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Fri, 7 Mar 2014 08:46:32 +0100 Subject: [PATCH 032/211] Close #159 --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index eee293f1..8cbc511b 100644 --- a/resumable.js +++ b/resumable.js @@ -295,7 +295,7 @@ } }); if(abortCount>0) $.resumableObj.fire('fileProgress', $); - } + }; $.cancel = function(){ // Reset this file to be void var _chunks = $.chunks; From 78f12355173160cdee70c765f3688f5d0a5bb7c0 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Thu, 13 Mar 2014 00:07:33 +0100 Subject: [PATCH 033/211] Close #160 with 204 recommendation Recommend that `test()` requests return *204 No Content* instead of *404 Not Found* to avoid console notices for every chunk tested. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 629882f4..0ea345bc 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ For every request, you can confirm reception in HTTP status codes (can be change Enabling the `testChunks` option will allow uploads to be resumed after browser restarts and even across browsers (in theory you could even run the same file upload across multiple tabs or different browsers). The `POST` data requests listed are required to use Resumable.js to receive data, but you can extend support by implementing a corresponding `GET` request with the same parameters: * If this request returns a `200` HTTP code, the chunks is assumed to have been completed. -* If the request returns anything else, the chunk will be uploaded in the standard fashion. +* If the request returns anything else, the chunk will be uploaded in the standard fashion. (It is recommended to return *204 No Content* in these cases if possible to [avoid unwarrented notices in brower consoles](https://github.com/23/resumable.js/issues/160).) After this is done and `testChunks` enabled, an upload can quickly catch up even after a browser restart by simply verifying already uploaded chunks that do not need to be uploaded again. From 17e85a01cc46bf6f935f95879f3ea4951b36c80e Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Sun, 16 Mar 2014 20:37:38 +0100 Subject: [PATCH 034/211] Close #162, fix bug in preprocess handling --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 8cbc511b..f1af4af2 100644 --- a/resumable.js +++ b/resumable.js @@ -630,7 +630,7 @@ found = true; return(false); } - if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[0].preprocessState === 0) { + 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); From 73ff43dabac1f285eacaab59938ab27fe3a8903d Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Sun, 16 Mar 2014 21:52:43 +0100 Subject: [PATCH 035/211] Close #261, only run retry upload after chunks is done --- resumable.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index f1af4af2..2521afb9 100644 --- a/resumable.js +++ b/resumable.js @@ -312,7 +312,11 @@ }; $.retry = function(){ $.bootstrap(); - $.resumableObj.upload(); + var firedRetry = false; + $.resumableObj.on('chunkingComplete', function(){ + if(!firedRetry) $.resumableObj.upload(); + firedRetry = true; + }); }; $.bootstrap = function(){ $.abort(); From c5faad98666714d1510811c695b02fbf1a113d61 Mon Sep 17 00:00:00 2001 From: yangweixian Date: Thu, 27 Mar 2014 01:20:32 +0800 Subject: [PATCH 036/211] fixed checkIfUploadFinished bug --- samples/java/src/resumable/js/upload/ResumableInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/java/src/resumable/js/upload/ResumableInfo.java b/samples/java/src/resumable/js/upload/ResumableInfo.java index c04a0d70..342daab9 100644 --- a/samples/java/src/resumable/js/upload/ResumableInfo.java +++ b/samples/java/src/resumable/js/upload/ResumableInfo.java @@ -54,7 +54,7 @@ public boolean vaild(){ public boolean checkIfUploadFinished() { //check if upload finished int count = (int) Math.ceil(((double) resumableTotalSize) / ((double) resumableChunkSize)); - for(int i = 1; i < count + 1; i ++) { + for(int i = 1; i < count; i ++) { if (!uploadedChunks.contains(new ResumableChunkNumber(i))) { return false; } From c139ce6a521633915a21361fb3d0b6697afeeb63 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Fri, 4 Apr 2014 21:07:42 +0200 Subject: [PATCH 037/211] Close #2 with an update to readme about IE support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ea345bc..03e043a4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Resumable.js is a JavaScript library providing multiple simultaneous, stable and The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each file into small chunks. Then, 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, resume and even recover uploads without losing state because only the currently uploading chunks will be aborted, not the entire upload. -Resumable.js does not have any external dependencies other than the `HTML5 File API`. This is relied on for the ability to chunk files into smaller pieces. Currently, this means that support is limited to Firefox 4+, Chrome 11+ and Safari 6+. +Resumable.js does not have any external dependencies other than the `HTML5 File API`. This is relied on for the ability to chunk files into smaller pieces. Currently, this means that support is widely avaialble in to Firefox 4+, Chrome 11+, Safari 6+ and Internet Explorer 10+. Samples and examples are available in the `samples/` folder. Please push your own as Markdown to help document the project. From e38b2a76c7c55d75a623756f668f3517b91521de Mon Sep 17 00:00:00 2001 From: Dave Kiss Date: Sun, 4 May 2014 09:36:09 -0500 Subject: [PATCH 038/211] Add missing semicolon --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 2521afb9..687ed6d5 100644 --- a/resumable.js +++ b/resumable.js @@ -578,7 +578,7 @@ 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') + return('uploading'); } else if(!$.xhr) { return('pending'); } else if($.xhr.readyState<4) { From 478fe39a0c3818a18ae0f4aaf62884ecd6602a6e Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Mon, 19 May 2014 21:48:41 +0200 Subject: [PATCH 039/211] Close #171. Although: Good practise is probably to forward an event to `appendFilesFromFileList` and to `$.addFile()`. --- resumable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index 687ed6d5..add21ec8 100644 --- a/resumable.js +++ b/resumable.js @@ -230,7 +230,7 @@ window.setTimeout(function(){ $.files.push(f); files.push(f); - f.container = event.srcElement; + f.container = (typeof event != 'undefined' ? event.srcElement : null); $.fire('fileAdded', f, event) },0); })()}; @@ -771,8 +771,8 @@ }); return(totalSize>0 ? totalDone/totalSize : 0); }; - $.addFile = function(file){ - appendFilesFromFileList([file]); + $.addFile = function(file, event){ + appendFilesFromFileList([file], event); }; $.removeFile = function(file){ for(var i = $.files.length - 1; i >= 0; i--) { From 1f1721e8e20f3d2df5d5f477fd9a599eb96bc099 Mon Sep 17 00:00:00 2001 From: dehuagit Date: Fri, 30 May 2014 10:58:26 +0800 Subject: [PATCH 040/211] fix get opt for chunkRetryInterval Original code have a problem that doesn't get the member function --- samples/coffeescript/resumable.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/coffeescript/resumable.coffee b/samples/coffeescript/resumable.coffee index d1713cef..2f2f774b 100644 --- a/samples/coffeescript/resumable.coffee +++ b/samples/coffeescript/resumable.coffee @@ -415,7 +415,7 @@ window.ResumableChunk = class ResumableChunk @callback 'retry', @message() @abort() @retries++ - retryInterval = getOpt('chunkRetryInterval') + retryInterval = @getOpt('chunkRetryInterval') if retryInterval? setTimeout @send, retryInterval From a24350c90f6304f32b9df984d43130e0023379dc Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Mon, 30 Jun 2014 20:52:42 +0200 Subject: [PATCH 041/211] Close #176 --- resumable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resumable.js b/resumable.js index add21ec8..43790307 100644 --- a/resumable.js +++ b/resumable.js @@ -450,6 +450,7 @@ params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); + params.push(['resumableTotalChunks', encodeURIComponent($.fileObj.chunks.length)].join('=')); // Append the relevant chunk and send it $.xhr.open('GET', $h.getTarget(params)); $.xhr.timeout = $.getOpt('xhrTimeout'); From ddf1db0b7ba4cd184ebdffe33957d3e438053dc9 Mon Sep 17 00:00:00 2001 From: dann toliver Date: Thu, 17 Jul 2014 01:44:02 -0400 Subject: [PATCH 042/211] Typo fixes for README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 03e043a4..f6e9e005 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Resumable.js is a JavaScript library providing multiple simultaneous, stable and The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each file into small chunks. Then, 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, resume and even recover uploads without losing state because only the currently uploading chunks will be aborted, not the entire upload. -Resumable.js does not have any external dependencies other than the `HTML5 File API`. This is relied on for the ability to chunk files into smaller pieces. Currently, this means that support is widely avaialble in to Firefox 4+, Chrome 11+, Safari 6+ and Internet Explorer 10+. +Resumable.js does not have any external dependencies other than the `HTML5 File API`. This is relied on for the ability to chunk files into smaller pieces. Currently, this means that support is widely available in to Firefox 4+, Chrome 11+, Safari 6+ and Internet Explorer 10+. Samples and examples are available in the `samples/` folder. Please push your own as Markdown to help document the project. @@ -64,7 +64,7 @@ For every request, you can confirm reception in HTTP status codes (can be change Enabling the `testChunks` option will allow uploads to be resumed after browser restarts and even across browsers (in theory you could even run the same file upload across multiple tabs or different browsers). The `POST` data requests listed are required to use Resumable.js to receive data, but you can extend support by implementing a corresponding `GET` request with the same parameters: * If this request returns a `200` HTTP code, the chunks is assumed to have been completed. -* If the request returns anything else, the chunk will be uploaded in the standard fashion. (It is recommended to return *204 No Content* in these cases if possible to [avoid unwarrented notices in brower consoles](https://github.com/23/resumable.js/issues/160).) +* If the request returns anything else, the chunk will be uploaded in the standard fashion. (It is recommended to return *204 No Content* in these cases if possible to [avoid unwarranted notices in browser consoles](https://github.com/23/resumable.js/issues/160).) After this is done and `testChunks` enabled, an upload can quickly catch up even after a browser restart by simply verifying already uploaded chunks that do not need to be uploaded again. @@ -73,7 +73,7 @@ After this is done and `testChunks` enabled, an upload can quickly catch up even ### Resumable #### Configuration -The object is loaded with a configuation hash: +The object is loaded with a configuration hash: var r = new Resumable({opt1:'val', ...}); @@ -131,11 +131,11 @@ Available configuration options are: * `.fileAdded(file, event)` A new file was added. Optionally, you can use the browser `event` object from when the file was added. * `.filesAdded(array)` New files were added. * `.fileRetry(file)` Something went wrong during upload of a specific file, uploading is being retried. -* `.fileError(file, message)` An error occured during upload of a specific file. +* `.fileError(file, message)` An error occurred during upload of a specific file. * `.uploadStart()` Upload has been started on the Resumable object. * `.complete()` Uploading completed. * `.progress()` Uploading progress. -* `.error(message, file)` An error, including fileError, occured. +* `.error(message, file)` An error, including fileError, occurred. * `.pause()` Uploading was paused. * `.cancel()` Uploading was canceled. * `.chunkingStart(file)` Started preparing file for upload From 3c89b3e39eeb2f1271da31817a713e08c8490a06 Mon Sep 17 00:00:00 2001 From: Jason Monma Date: Mon, 21 Jul 2014 16:05:34 -0700 Subject: [PATCH 043/211] Update Node.js sample for modern versions of Node and Express. --- samples/Node.js/README.md | 4 ++-- samples/Node.js/app.js | 5 +++-- samples/Node.js/package.json | 11 +++++++++++ samples/Node.js/resumable-node.js | 8 ++++---- 4 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 samples/Node.js/package.json diff --git a/samples/Node.js/README.md b/samples/Node.js/README.md index 54f3d62a..bc6949e1 100644 --- a/samples/Node.js/README.md +++ b/samples/Node.js/README.md @@ -1,11 +1,11 @@ # Sample code for Node.js -This sample is written for [Node.js 0.6+](http://nodejs.org/) and requires [Express](http://expressjs.com/) to make the sample code cleaner. +This sample is written for [Node.js 0.10+](http://nodejs.org/) and requires [Express 4+](http://expressjs.com/) to make the sample code cleaner. To install and run: cd samples/Node.js - npm install express + npm install node app.js Then browse to [localhost:3000](http://localhost:3000). diff --git a/samples/Node.js/app.js b/samples/Node.js/app.js index 860b7786..d004202a 100644 --- a/samples/Node.js/app.js +++ b/samples/Node.js/app.js @@ -1,11 +1,12 @@ var express = require('express'); var resumable = require('./resumable-node.js')('/tmp/resumable.js/'); var app = express(); +var multipart = require('connect-multiparty'); // Host most stuff in the public folder app.use(express.static(__dirname + '/public')); -app.use(express.bodyParser()); +app.use(multipart()); // Handle uploads through Resumable.js app.post('/upload', function(req, res){ @@ -37,7 +38,7 @@ app.post('/upload', function(req, res){ app.get('/upload', function(req, res){ resumable.get(req, function(status, filename, original_filename, identifier){ console.log('GET', status); - res.send(status, (status == 'found' ? 200 : 404)); + res.send((status == 'found' ? 200 : 404), status); }); }); diff --git a/samples/Node.js/package.json b/samples/Node.js/package.json new file mode 100644 index 00000000..cdc11dff --- /dev/null +++ b/samples/Node.js/package.json @@ -0,0 +1,11 @@ +{ + "name": "resumable.js", + "version": "0.0.1", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "express": "~4.6.1", + "connect-multiparty": "~1.1.0" + } +} diff --git a/samples/Node.js/resumable-node.js b/samples/Node.js/resumable-node.js index 0c07ddf9..66191558 100644 --- a/samples/Node.js/resumable-node.js +++ b/samples/Node.js/resumable-node.js @@ -71,7 +71,7 @@ module.exports = resumable = function(temporaryFolder){ if(validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename)=='valid') { var chunkFilename = getChunkFilename(chunkNumber, identifier); - path.exists(chunkFilename, function(exists){ + fs.exists(chunkFilename, function(exists){ if(exists){ callback('found', chunkFilename, filename, identifier); } else { @@ -115,7 +115,7 @@ module.exports = resumable = function(temporaryFolder){ var currentTestChunk = 1; var numberOfChunks = Math.max(Math.floor(totalSize/(chunkSize*1.0)), 1); var testChunkExists = function(){ - path.exists(getChunkFilename(currentTestChunk, identifier), function(exists){ + fs.exists(getChunkFilename(currentTestChunk, identifier), function(exists){ if(exists){ currentTestChunk++; if(currentTestChunk>numberOfChunks) { @@ -153,7 +153,7 @@ module.exports = resumable = function(temporaryFolder){ var pipeChunk = function(number) { var chunkFilename = getChunkFilename(number, identifier); - path.exists(chunkFilename, function(exists) { + fs.exists(chunkFilename, function(exists) { if (exists) { // If the chunk with the current number exists, @@ -188,7 +188,7 @@ module.exports = resumable = function(temporaryFolder){ var chunkFilename = getChunkFilename(number, identifier); //console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename); - path.exists(chunkFilename, function(exists) { + fs.exists(chunkFilename, function(exists) { if (exists) { console.log('exist removing ', chunkFilename); From 06f58c80347cbaab04e9bce4f269e31b513b698c Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Wed, 10 Sep 2014 15:35:39 +0200 Subject: [PATCH 044/211] Tag current version as 1.0.1 with Bower to close #191 --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 62c3ad79..df975c83 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "resumable.js", - "version": "1.0.0", + "version": "1.0.1", "main": "resumable.js", "ignore": [ ".gitignore", From 703fc629feb01e8fe58f8faab62ceb2f24aaa1e6 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Wed, 10 Sep 2014 15:35:58 +0200 Subject: [PATCH 045/211] Tag current version as 1.0.1 --- component.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component.json b/component.json index 4e739855..854cc70a 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "name": "resumable.js", "repo": "23/resumable.js", - "version": "1.0.0", + "version": "1.0.1", "main": "resumable.js", "scripts": ["resumable.js"] } From 9a6c2d09a1d3b23d7c56499303cb6466757915c5 Mon Sep 17 00:00:00 2001 From: znap026 Date: Wed, 19 Nov 2014 12:53:56 +0000 Subject: [PATCH 046/211] fix for larger files Add extra permanent error and increased the percentage cutoff to help lager files --- resumable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 43790307..06d86c60 100644 --- a/resumable.js +++ b/resumable.js @@ -50,7 +50,7 @@ generateUniqueIdentifier:null, maxChunkRetries:undefined, chunkRetryInterval:undefined, - permanentErrors:[404, 415, 500, 501], + permanentErrors:[400, 404, 415, 500, 501], maxFiles:undefined, withCredentials:false, xhrTimeout:0, @@ -345,7 +345,7 @@ if(c.status()=='error') error = true; ret += c.progress(true); // get chunk progress relative to entire file }); - ret = (error ? 1 : (ret>0.999 ? 1 : ret)); + ret = (error ? 1 : (ret>0.99999 ? 1 : ret)); ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused $._prevProgress = ret; return(ret); From 9daf7c8e440739aa37d5e5ee1dc3e64971e212d9 Mon Sep 17 00:00:00 2001 From: Jim Date: Tue, 2 Dec 2014 19:04:18 -0500 Subject: [PATCH 047/211] Don't stall when a request times out. Resumable allows setting a timeout on AJAX requests but doesn't handle the ontimeout event when it gets thrown. This causes the uploader to hang whenever a timeout occurs. This change will handle timeout events the same way as a load or error event is handle in the test and send methods of a chunk. --- resumable.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resumable.js b/resumable.js index 06d86c60..5b358ab9 100644 --- a/resumable.js +++ b/resumable.js @@ -433,6 +433,7 @@ }; $.xhr.addEventListener('load', testHandler, false); $.xhr.addEventListener('error', testHandler, false); + $.xhr.addEventListener('timeout', testHandler, false); // Add data from the query options var params = []; @@ -518,6 +519,7 @@ }; $.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 = { From c3df18122c305a54bbc2d56a326f2d784f029e64 Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Mon, 19 Jan 2015 13:27:24 -0600 Subject: [PATCH 048/211] add support for traversing folders and subfolders in a drag and drop from Chrome --- resumable.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 5b358ab9..42fbf2c4 100644 --- a/resumable.js +++ b/resumable.js @@ -185,13 +185,96 @@ var onDrop = function(event){ $h.stopEvent(event); - appendFilesFromFileList(event.dataTransfer.files, event); + + //handle dropped things as items if we can (Chrome only at the moment) + if (event.dataTransfer && event.dataTransfer.items) { + loadFiles(event.dataTransfer.items, event); + } + //else fall back on treating them as files + else if (event.dataTransfer && event.dataTransfer.files) { + loadFiles(event.dataTransfer.files, event); + } }; var onDragOver = function(e) { e.preventDefault(); }; // INTERNAL METHODS (both handy and responsible for the heavy load) + var loadFiles = function (files, event, queue, path){ + //initialize the queue object if it doesn't exist + if (!queue) { + queue = { + total: 0, + files: [], + event: event + }; + } + + //update the total number of things we plan to process + queue.total += files.length; + + for (var i = 0; i < files.length; i++) { + var file = files[i]; + var entry, reader; + + if (file.isFile || file.isDirectory) { + entry = file; + } + else if (file.getAsEntry) { + entry = file.getAsEntry(); + } + else if (file.webkitGetAsEntry) { + entry = file.webkitGetAsEntry(); + } + else if (typeof file.getAsFile === 'function') { + enqueueFileAddition(file.getAsFile(), queue, path); + continue; + } + else if (File && file instanceof File) { + enqueueFileAddition(file, queue, path); + continue; + } + else { + queue.total -= 1; + continue; + } + + if (!entry) { + //there isn't anything we can do with this so shrink the total expected by 1 + queue.total -= 1; + } + else if (entry.isFile) { + entry.file(function(file) { + enqueueFileAddition(file, queue, path); + }, function(err) { + console.warn(err); + }); + } + else if (entry.isDirectory) { + reader = entry.createReader(); + + reader.readEntries(function(entries) { + //process each thing in this directory recursively + loadFiles(entries, event, queue, entry.fullPath); + //this was a directory rather than a file so decrement the expected file count + queue.total -= 1; + }, function(err) { + console.warn(err); + }); + } + } + }; + + var enqueueFileAddition = function(file, queue, path) { + file.relativePath = path + '/' + file.name; + queue.files.push(file); + + // If all the files we expect have shown up, then flush the queue. + if (queue.files.length === queue.total) { + appendFilesFromFileList(queue.files, queue.event); + } + }; + var appendFilesFromFileList = function(fileList, event){ // check for uploading too many files var errorCount = 0; @@ -250,7 +333,7 @@ $.file = file; $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox $.size = file.size; - $.relativePath = file.webkitRelativePath || $.fileName; + $.relativePath = file.webkitRelativePath || file.relativePath || $.fileName; $.uniqueIdentifier = $h.generateUniqueIdentifier(file); $._pause = false; $.container = ''; From e02b13e7a8fbb75635369ac70e26a3c9d4c0f3f5 Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Mon, 19 Jan 2015 13:39:55 -0600 Subject: [PATCH 049/211] fix issues with relative path --- resumable.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 42fbf2c4..3aa8c13e 100644 --- a/resumable.js +++ b/resumable.js @@ -251,11 +251,12 @@ }); } else if (entry.isDirectory) { + var directory=entry; reader = entry.createReader(); reader.readEntries(function(entries) { //process each thing in this directory recursively - loadFiles(entries, event, queue, entry.fullPath); + loadFiles(entries, event, queue, directory.fullPath); //this was a directory rather than a file so decrement the expected file count queue.total -= 1; }, function(err) { @@ -266,7 +267,8 @@ }; var enqueueFileAddition = function(file, queue, path) { - file.relativePath = path + '/' + file.name; + //store the path to this file if it came in as part of a folder + if (path) file.relativePath = path + '/' + file.name; queue.files.push(file); // If all the files we expect have shown up, then flush the queue. From 62f1bd75b77582da668070461bfa87bd7ed4f014 Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Mon, 19 Jan 2015 15:46:28 -0600 Subject: [PATCH 050/211] add more comments --- resumable.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index 3aa8c13e..605bde67 100644 --- a/resumable.js +++ b/resumable.js @@ -186,11 +186,11 @@ var onDrop = function(event){ $h.stopEvent(event); - //handle dropped things as items if we can (Chrome only at the moment) + //handle dropped things as items if we can (this lets us deal with folders nicer in some cases) if (event.dataTransfer && event.dataTransfer.items) { loadFiles(event.dataTransfer.items, event); } - //else fall back on treating them as files + //else handle them as files else if (event.dataTransfer && event.dataTransfer.files) { loadFiles(event.dataTransfer.files, event); } @@ -213,37 +213,48 @@ //update the total number of things we plan to process queue.total += files.length; + //loop over all the passed in objects checking if they are files or folders for (var i = 0; i < files.length; i++) { var file = files[i]; var entry, reader; if (file.isFile || file.isDirectory) { + //this is an object we can handle below with no extra work needed up front entry = file; } else if (file.getAsEntry) { + //get the file as an entry object if we can using the proposed HTML5 api (unlikely to get implemented by anyone) entry = file.getAsEntry(); } else if (file.webkitGetAsEntry) { + //get the file as an entry object if we can using the Chrome specific webkit implementation entry = file.webkitGetAsEntry(); } else if (typeof file.getAsFile === 'function') { + //if this is still a DataTransferItem object, get it as a file object enqueueFileAddition(file.getAsFile(), queue, path); + //we just added this file object to the queue so we can go to the next object in the loop and skip the processing below continue; } else if (File && file instanceof File) { + //this is already a file object so just queue it up and move on enqueueFileAddition(file, queue, path); + //we just added this file object to the queue so we can go to the next object in the loop and skip the processing below continue; } else { + //we can't do anything with this object, decrement the expected total and skip the processing below queue.total -= 1; continue; } if (!entry) { - //there isn't anything we can do with this so shrink the total expected by 1 + //there isn't anything we can do with this so decrement the total expected queue.total -= 1; } else if (entry.isFile) { + //this is handling to read an entry object representing a file, parsing the file object is asynchronous which is why we need the queue + //currently entry objects will only exist in this flow for Chrome entry.file(function(file) { enqueueFileAddition(file, queue, path); }, function(err) { @@ -251,6 +262,8 @@ }); } else if (entry.isDirectory) { + //this is handling to read an entry object representing a folder, parsing the directory object is asynchronous which is why we need the queue + //currently entry objects will only exist in this flow for Chrome var directory=entry; reader = entry.createReader(); From e2eb6d2596670e52e42b39a1ee7b774a2384092c Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Mon, 19 Jan 2015 16:09:05 -0600 Subject: [PATCH 051/211] add function headers and fix bug with empty subfolders --- resumable.js | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/resumable.js b/resumable.js index 605bde67..a91624f2 100644 --- a/resumable.js +++ b/resumable.js @@ -200,6 +200,16 @@ }; // INTERNAL METHODS (both handy and responsible for the heavy load) + /** + * @summary This funciton loops over the files passed in from a drag and drop operation and gets them ready for appendFilesFromFileList + * It attempts to use FileSystem API calls to extract files and subfolders if the dropped items include folders + * That capability is only currently available in Chrome, but if it isn't available it will just pass the items along to + * appendFilesFromFileList (via enqueueFileAddition to help with asyncronous processing.) + * @param files {Array} - the File or Entry objects to be processed depending on your browser support + * @param event {Object} - the drop event object + * @param [queue] {Object} - an object to keep track of our progress processing the dropped items + * @param [path] {String} - the relative path from the originally selected folder to the current files if extracting files from subfolders + */ var loadFiles = function (files, event, queue, path){ //initialize the queue object if it doesn't exist if (!queue) { @@ -211,7 +221,7 @@ } //update the total number of things we plan to process - queue.total += files.length; + updateQueueTotal(files.length, queue); //loop over all the passed in objects checking if they are files or folders for (var i = 0; i < files.length; i++) { @@ -244,13 +254,13 @@ } else { //we can't do anything with this object, decrement the expected total and skip the processing below - queue.total -= 1; + updateQueueTotal(-1, queue); continue; } if (!entry) { //there isn't anything we can do with this so decrement the total expected - queue.total -= 1; + updateQueueTotal(-1, queue); } else if (entry.isFile) { //this is handling to read an entry object representing a file, parsing the file object is asynchronous which is why we need the queue @@ -271,7 +281,7 @@ //process each thing in this directory recursively loadFiles(entries, event, queue, directory.fullPath); //this was a directory rather than a file so decrement the expected file count - queue.total -= 1; + updateQueueTotal(-1, queue); }, function(err) { console.warn(err); }); @@ -279,6 +289,27 @@ } }; + /** + * @summary Adjust the total number of files we are expecting to process + * if decrementing and the new expected total is equal to the number processed, flush the queue + * @param addition {Number} - the number of additional files we expect to process (may be negative) + * @param queue {Object} - an object to keep track of our progress processing the dropped items + */ + var updateQueueTotal = function(addition, queue){ + queue.total += addition; + + // If all the files we expect have shown up, then flush the queue. + if (queue.files.length === queue.total) { + appendFilesFromFileList(queue.files, queue.event); + } + }; + + /** + * @summary Add a file to the queue of processed files, if it brings the total up to the expected total, flush the queue + * @param file {Object} - File object to be passed along to appendFilesFromFileList eventually + * @param queue {Object} - an object to keep track of our progress processing the dropped items + * @param [path] {String} - the file's relative path from the originally dropped folder if we are parsing folder content (Chrome only for now) + */ var enqueueFileAddition = function(file, queue, path) { //store the path to this file if it came in as part of a folder if (path) file.relativePath = path + '/' + file.name; From e2895478959cabc298e2511780b3d83b4743c8db Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Mon, 19 Jan 2015 16:54:21 -0600 Subject: [PATCH 052/211] add function headers and fix relative paths when there are multiple folders in one level --- resumable.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/resumable.js b/resumable.js index a91624f2..9a5ff04b 100644 --- a/resumable.js +++ b/resumable.js @@ -201,10 +201,10 @@ // INTERNAL METHODS (both handy and responsible for the heavy load) /** - * @summary This funciton loops over the files passed in from a drag and drop operation and gets them ready for appendFilesFromFileList + * @summary This function loops over the files passed in from a drag and drop operation and gets them ready for appendFilesFromFileList * It attempts to use FileSystem API calls to extract files and subfolders if the dropped items include folders * That capability is only currently available in Chrome, but if it isn't available it will just pass the items along to - * appendFilesFromFileList (via enqueueFileAddition to help with asyncronous processing.) + * appendFilesFromFileList (via enqueueFileAddition to help with asynchronous processing.) * @param files {Array} - the File or Entry objects to be processed depending on your browser support * @param event {Object} - the drop event object * @param [queue] {Object} - an object to keep track of our progress processing the dropped items @@ -274,15 +274,19 @@ else if (entry.isDirectory) { //this is handling to read an entry object representing a folder, parsing the directory object is asynchronous which is why we need the queue //currently entry objects will only exist in this flow for Chrome - var directory=entry; reader = entry.createReader(); - reader.readEntries(function(entries) { - //process each thing in this directory recursively - loadFiles(entries, event, queue, directory.fullPath); - //this was a directory rather than a file so decrement the expected file count - updateQueueTotal(-1, queue); - }, function(err) { + //wrap the callback in another function so we can store the path in a closure + var readDir = function(path){ + return function(entries){ + //process each thing in this directory recursively + loadFiles(entries, event, queue, path); + //this was a directory rather than a file so decrement the expected file count + updateQueueTotal(-1, queue); + } + }; + + reader.readEntries(readDir(entry.fullPath), function(err) { console.warn(err); }); } From a4b05fe3b44e3f5b420e779dce2e0cd22fcf7840 Mon Sep 17 00:00:00 2001 From: basteyy Date: Mon, 16 Feb 2015 14:37:44 +0100 Subject: [PATCH 053/211] Update Backend on PHP.md --- samples/Backend on PHP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md index 66a9e9dc..f08f07b1 100644 --- a/samples/Backend on PHP.md +++ b/samples/Backend on PHP.md @@ -73,7 +73,7 @@ function rrmdir($dir) { * * Check if all the parts exist, and * gather all the parts of the file together - * @param string $dir - the temporary directory holding all the parts of the file + * @param string $temp_dir - the temporary directory holding all the parts of the file * @param string $fileName - the original file name * @param string $chunkSize - each chunk size (in bytes) * @param string $totalSize - original file size (in bytes) From 5a0c3e1b1622eef0f054307e8c9d8a2b76712d90 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 16 Mar 2015 12:21:39 -0400 Subject: [PATCH 054/211] Added support for 2 part file extensions --- resumable.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/resumable.js b/resumable.js index 9a5ff04b..d2d26601 100644 --- a/resumable.js +++ b/resumable.js @@ -340,13 +340,21 @@ } var files = []; $h.each(fileList, function(file){ - var fileName = file.name.split('.'); - var fileType = fileName[fileName.length-1].toLowerCase(); - - if (o.fileType.length > 0 && !$h.contains(o.fileType, fileType)) { - o.fileTypeErrorCallback(file, errorCount++); - return false; - } + var fileName = file.name; + if(typeof(o.fileType)==='array' && o.fileType.length > 0){ + var fileTypeFound = false; + for(var index in ){ + var extension = '.' + o.fileType[index]; + if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ + fileTypeFound = true; + break; + } + } + if (!fileTypeFound) { + o.fileTypeErrorCallback(file, errorCount++); + return false; + } + } if (typeof(o.minFileSize)!=='undefined' && file.size Date: Mon, 16 Mar 2015 12:25:46 -0400 Subject: [PATCH 055/211] Fixed error in iterating over file type --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index d2d26601..eb231d9f 100644 --- a/resumable.js +++ b/resumable.js @@ -343,7 +343,7 @@ var fileName = file.name; if(typeof(o.fileType)==='array' && o.fileType.length > 0){ var fileTypeFound = false; - for(var index in ){ + for(var index in o.fileType){ var extension = '.' + o.fileType[index]; if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ fileTypeFound = true; From ac81e1f5977362244c1b5585131c497f03ec2a29 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 16 Mar 2015 13:40:12 -0400 Subject: [PATCH 056/211] Fixed typeof check for array --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index eb231d9f..9b6c42c5 100644 --- a/resumable.js +++ b/resumable.js @@ -341,7 +341,7 @@ var files = []; $h.each(fileList, function(file){ var fileName = file.name; - if(typeof(o.fileType)==='array' && o.fileType.length > 0){ + if(o.fileType.length > 0){ var fileTypeFound = false; for(var index in o.fileType){ var extension = '.' + o.fileType[index]; From dec8b2f871cd59295f4df592dc39cc17c6be648c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Fagerstr=C3=B6m=20Christensen?= Date: Tue, 17 Mar 2015 05:57:07 +0100 Subject: [PATCH 057/211] Bump version to close #210 --- bower.json | 2 +- component.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index df975c83..b10d3741 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "resumable.js", - "version": "1.0.1", + "version": "1.0.2", "main": "resumable.js", "ignore": [ ".gitignore", diff --git a/component.json b/component.json index 854cc70a..b3a0506e 100644 --- a/component.json +++ b/component.json @@ -1,7 +1,7 @@ { "name": "resumable.js", "repo": "23/resumable.js", - "version": "1.0.1", + "version": "1.0.2", "main": "resumable.js", "scripts": ["resumable.js"] } From 702db5ffd7439bd9bf7389dfb7ce640766427e8b Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 24 Mar 2015 10:04:49 -0400 Subject: [PATCH 058/211] Added support for unique identifier promises --- resumable.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/resumable.js b/resumable.js index 9b6c42c5..0b781450 100644 --- a/resumable.js +++ b/resumable.js @@ -365,16 +365,32 @@ return false; } + function addFile(uniqueIdentifier){ + if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ + file.uniqueIdentifier = uniqueIdentifier; + var f = new ResumableFile($, file, uniqueIdentifier); + window.setTimeout(function(){ + $.files.push(f); + files.push(f); + f.container = (typeof event != 'undefined' ? event.srcElement : null); + $.fire('fileAdded', f, event) + },0); + })()}; + } // directories have size == 0 - if (!$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) {(function(){ - var f = new ResumableFile($, file); - window.setTimeout(function(){ - $.files.push(f); - files.push(f); - f.container = (typeof event != 'undefined' ? event.srcElement : null); - $.fire('fileAdded', f, event) - },0); - })()}; + var uniqueIdentifier = $h.generateUniqueIdentifier(file) + if(uniqueIdentifier && typeof uniqueIdentifier.done === 'function' && typeof uniqueIdentifier.fail === 'function'){ + uniqueIdentifier + .done(function(uniqueIdentifier){ + addFile(uniqueIdentifier); + }) + .fail(function(){ + addFile(); + }); + }else{ + addFile(uniqueIdentifier); + } + }); window.setTimeout(function(){ $.fire('filesAdded', files) @@ -382,7 +398,7 @@ }; // INTERNAL OBJECT TYPES - function ResumableFile(resumableObj, file){ + function ResumableFile(resumableObj, file, uniqueIdentifier){ var $ = this; $.opts = {}; $.getOpt = resumableObj.getOpt; @@ -392,10 +408,10 @@ $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox $.size = file.size; $.relativePath = file.webkitRelativePath || file.relativePath || $.fileName; - $.uniqueIdentifier = $h.generateUniqueIdentifier(file); + $.uniqueIdentifier = uniqueIdentifier; $._pause = false; $.container = ''; - var _error = false; + var _error = uniqueIdentifier !== undefined; // Callback when something happens within the chunk var chunkEvent = function(event, message){ @@ -530,6 +546,7 @@ return(this); } + function ResumableChunk(resumableObj, fileObj, offset, callback){ var $ = this; $.opts = {}; From 7d5c0904c188de76a2972e7ffa7b39ddef7817c0 Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Wed, 25 Mar 2015 15:47:01 -0500 Subject: [PATCH 059/211] fix order of operations bug with duplicate files in one drag/drop --- resumable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index 0b781450..7ccbd990 100644 --- a/resumable.js +++ b/resumable.js @@ -369,10 +369,10 @@ 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(){ - $.files.push(f); - files.push(f); - f.container = (typeof event != 'undefined' ? event.srcElement : null); $.fire('fileAdded', f, event) },0); })()}; From 393ed3ce0b85bd6c890a9f550633b60858db9261 Mon Sep 17 00:00:00 2001 From: Daniele Fognini Date: Tue, 24 Mar 2015 16:20:12 +0100 Subject: [PATCH 060/211] add parameter namespacing support --- README.md | 1 + resumable.js | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f6e9e005..d5bbf6f0 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Available configuration options are: * `simultaneousUploads` Number of simultaneous uploads (Default: `3`) * `fileParameterName` The name of the multipart POST parameter to use for the file chunk (Default: `file`) * `query` Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: `{}`) +* `parameterNamespace` Extra prefix added before the name of each parameter included in the multipart POST or in the test GET. (Default: `''`) * `headers` Extra headers to include in the multipart POST with data (Default: `{}`) * `method` Method to use when POSTing chunks to the server (`multipart` or `octet`) (Default: `multipart`) * `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`) diff --git a/resumable.js b/resumable.js index 7ccbd990..75f2642b 100644 --- a/resumable.js +++ b/resumable.js @@ -46,6 +46,7 @@ method:'multipart', prioritizeFirstAndLastChunk:false, target:'/', + parameterNamespace:'', testChunks:true, generateUniqueIdentifier:null, maxChunkRetries:undefined, @@ -595,21 +596,22 @@ // 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(k), encodeURIComponent(v)].join('=')); + params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); }); // Add extra data to identify chunk - params.push(['resumableChunkNumber', encodeURIComponent($.offset+1)].join('=')); - params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); - params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); - params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); - params.push(['resumableType', encodeURIComponent($.fileObjType)].join('=')); - params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); - params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); - params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); - params.push(['resumableTotalChunks', encodeURIComponent($.fileObj.chunks.length)].join('=')); + params.push([parameterNamespace+'resumableChunkNumber', encodeURIComponent($.offset+1)].join('=')); + params.push([parameterNamespace+'resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); + params.push([parameterNamespace+'resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); + params.push([parameterNamespace+'resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); + params.push([parameterNamespace+'resumableType', encodeURIComponent($.fileObjType)].join('=')); + params.push([parameterNamespace+'resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); + params.push([parameterNamespace+'resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); + params.push([parameterNamespace+'resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); + params.push([parameterNamespace+'resumableTotalChunks', encodeURIComponent($.fileObj.chunks.length)].join('=')); // Append the relevant chunk and send it $.xhr.open('GET', $h.getTarget(params)); $.xhr.timeout = $.getOpt('xhrTimeout'); @@ -702,22 +704,23 @@ bytes = $.fileObj.file[func]($.startByte,$.endByte), data = null, target = $.getOpt('target'); - + + var parameterNamespace = $.getOpt('parameterNamespace'); if ($.getOpt('method') === 'octet') { // Add data from the query options data = bytes; var params = []; $h.each(query, function(k,v){ - params.push([encodeURIComponent(k), encodeURIComponent(v)].join('=')); + params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); }); target = $h.getTarget(params); } else { // Add data from the query options data = new FormData(); $h.each(query, function(k,v){ - data.append(k,v); + data.append(parameterNamespace+k,v); }); - data.append($.getOpt('fileParameterName'), bytes); + data.append(parameterNamespace+$.getOpt('fileParameterName'), bytes); } $.xhr.open('POST', target); From 47a2434b6dcc7a9b8e219561d7e1357d542a7eda Mon Sep 17 00:00:00 2001 From: Ben Carpenter Date: Tue, 7 Apr 2015 10:06:13 -0500 Subject: [PATCH 061/211] fix dropping a folder with a large number of files in it --- resumable.js | 62 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/resumable.js b/resumable.js index 75f2642b..bc14a41a 100644 --- a/resumable.js +++ b/resumable.js @@ -97,7 +97,7 @@ else { return $opt.defaults[o]; } } }; - + // EVENTS // catchAll(event, ...) // fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file), fileError(file, message), @@ -119,8 +119,8 @@ if(event=='fileerror') $.fire('error', args[2], args[1]); if(event=='fileprogress') $.fire('progress'); }; - - + + // INTERNAL HELPER METHODS (handy, but ultimately not part of uploading) var $h = { stopEvent: function(e){ @@ -204,7 +204,7 @@ /** * @summary This function loops over the files passed in from a drag and drop operation and gets them ready for appendFilesFromFileList * It attempts to use FileSystem API calls to extract files and subfolders if the dropped items include folders - * That capability is only currently available in Chrome, but if it isn't available it will just pass the items along to + * That capability is only currently available in Chrome, but if it isn't available it will just pass the items along to * appendFilesFromFileList (via enqueueFileAddition to help with asynchronous processing.) * @param files {Array} - the File or Entry objects to be processed depending on your browser support * @param event {Object} - the drop event object @@ -277,19 +277,33 @@ //currently entry objects will only exist in this flow for Chrome reader = entry.createReader(); - //wrap the callback in another function so we can store the path in a closure - var readDir = function(path){ - return function(entries){ - //process each thing in this directory recursively - loadFiles(entries, event, queue, path); - //this was a directory rather than a file so decrement the expected file count - updateQueueTotal(-1, queue); - } - }; - - reader.readEntries(readDir(entry.fullPath), function(err) { - console.warn(err); - }); + var newEntries = []; + //wrap the callback in another function so we can store the path in a closure + var readDir = function(path){ + reader.readEntries( + //success callback: read entries out of the directory + function(entries){ + if (entries.length>0){ + //add these results to the array of all the new stuff + for (var i=0; i Date: Tue, 12 May 2015 15:49:51 -0700 Subject: [PATCH 062/211] Fixes proprocessState bug. This bug causes the preprocessState to be reset to 1 even after preprocess runs and sets it to 2. This results in chunks not thinking they are complete and causing issues later in the event chain. --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index bc14a41a..1f5a77a8 100644 --- a/resumable.js +++ b/resumable.js @@ -647,7 +647,7 @@ var preprocess = $.getOpt('preprocess'); if(typeof preprocess === 'function') { switch($.preprocessState) { - case 0: preprocess($); $.preprocessState = 1; return; + case 0: $.preprocessState = 1; preprocess($); return; case 1: return; case 2: break; } From c996111a000c22a5bc149960f73c419266c4ce18 Mon Sep 17 00:00:00 2001 From: Greg Donarum Date: Fri, 15 May 2015 13:35:06 -0400 Subject: [PATCH 063/211] Converted java example to a maven project --- .gitignore | 20 +++++++ samples/java/pom.xml | 49 ++++++++++++++++++ .../java}/resumable/js/upload/HttpUtils.java | 0 .../resumable/js/upload/ResumableInfo.java | 0 .../js/upload/ResumableInfoStorage.java | 0 .../resumable/js/upload/UploadServlet.java | 2 +- .../{web => src/main/webapp}/WEB-INF/web.xml | 0 .../java/{web => src/main/webapp}/index.html | 2 +- .../java/{web => src/main/webapp}/pause.png | Bin .../{web => src/main/webapp}/resumable.js | 0 .../java/{web => src/main/webapp}/resume.png | Bin .../java/{web => src/main/webapp}/style.css | 0 12 files changed, 71 insertions(+), 2 deletions(-) create mode 100755 samples/java/pom.xml rename samples/java/src/{ => main/java}/resumable/js/upload/HttpUtils.java (100%) rename samples/java/src/{ => main/java}/resumable/js/upload/ResumableInfo.java (100%) rename samples/java/src/{ => main/java}/resumable/js/upload/ResumableInfoStorage.java (100%) rename samples/java/src/{ => main/java}/resumable/js/upload/UploadServlet.java (98%) rename samples/java/{web => src/main/webapp}/WEB-INF/web.xml (100%) rename samples/java/{web => src/main/webapp}/index.html (99%) rename samples/java/{web => src/main/webapp}/pause.png (100%) rename samples/java/{web => src/main/webapp}/resumable.js (100%) rename samples/java/{web => src/main/webapp}/resume.png (100%) rename samples/java/{web => src/main/webapp}/style.css (100%) diff --git a/.gitignore b/.gitignore index 88c10166..785f8f55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,22 @@ *~ samples/Node.js/node_modules/ + +# Eclipse +.classpath +.project +.settings/ + +# Intellij +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +log/ +target/ + +# Gradle +.gradle \ No newline at end of file diff --git a/samples/java/pom.xml b/samples/java/pom.xml new file mode 100755 index 00000000..a357f7b7 --- /dev/null +++ b/samples/java/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + resumable.js + java-example + war + 0.0.1-SNAPSHOT + Java Example + http://maven.apache.org + + + UTF-8 + + + + javax.servlet + servlet-api + 2.5 + + + + + junit + junit + 3.8.1 + test + + + + + resumable.js + + + org.mortbay.jetty + maven-jetty-plugin + 6.1.10 + + 10 + + + 8080 + 60000 + + + + + + + diff --git a/samples/java/src/resumable/js/upload/HttpUtils.java b/samples/java/src/main/java/resumable/js/upload/HttpUtils.java similarity index 100% rename from samples/java/src/resumable/js/upload/HttpUtils.java rename to samples/java/src/main/java/resumable/js/upload/HttpUtils.java diff --git a/samples/java/src/resumable/js/upload/ResumableInfo.java b/samples/java/src/main/java/resumable/js/upload/ResumableInfo.java similarity index 100% rename from samples/java/src/resumable/js/upload/ResumableInfo.java rename to samples/java/src/main/java/resumable/js/upload/ResumableInfo.java diff --git a/samples/java/src/resumable/js/upload/ResumableInfoStorage.java b/samples/java/src/main/java/resumable/js/upload/ResumableInfoStorage.java similarity index 100% rename from samples/java/src/resumable/js/upload/ResumableInfoStorage.java rename to samples/java/src/main/java/resumable/js/upload/ResumableInfoStorage.java diff --git a/samples/java/src/resumable/js/upload/UploadServlet.java b/samples/java/src/main/java/resumable/js/upload/UploadServlet.java similarity index 98% rename from samples/java/src/resumable/js/upload/UploadServlet.java rename to samples/java/src/main/java/resumable/js/upload/UploadServlet.java index 6b9f8f63..8deaf195 100644 --- a/samples/java/src/resumable/js/upload/UploadServlet.java +++ b/samples/java/src/main/java/resumable/js/upload/UploadServlet.java @@ -17,7 +17,7 @@ */ public class UploadServlet extends HttpServlet { - public static final String UPLOAD_DIR = "e:\\"; + public static final String UPLOAD_DIR = "upload_dir"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int resumableChunkNumber = getResumableChunkNumber(request); diff --git a/samples/java/web/WEB-INF/web.xml b/samples/java/src/main/webapp/WEB-INF/web.xml similarity index 100% rename from samples/java/web/WEB-INF/web.xml rename to samples/java/src/main/webapp/WEB-INF/web.xml diff --git a/samples/java/web/index.html b/samples/java/src/main/webapp/index.html similarity index 99% rename from samples/java/web/index.html rename to samples/java/src/main/webapp/index.html index 5724b5a7..5a6bbd44 100644 --- a/samples/java/web/index.html +++ b/samples/java/src/main/webapp/index.html @@ -46,7 +46,7 @@

Demo

From 5c65475462d428beb6f7343f410e13171aa4b1d7 Mon Sep 17 00:00:00 2001 From: ChadTaljaardt Date: Mon, 12 Oct 2015 12:38:48 +0100 Subject: [PATCH 076/211] Update PHP Sample to fix recreating file before file is finished uploading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A problem was discovered in the PHP sample where the server would start building the file before all the file’s chunks where received and therefore corrupted the file. I have edited the code to do a check to see if the server has the files equal to the size of the file being uploaded before building the file. --- samples/Backend on PHP.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md index f08f07b1..a5d8f73d 100644 --- a/samples/Backend on PHP.md +++ b/samples/Backend on PHP.md @@ -81,16 +81,19 @@ function rrmdir($dir) { function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { // count all the parts of this file - $total_files = 0; + $total_files_on_server_size = 0; + $temp_total = 0; foreach(scandir($temp_dir) as $file) { + $temp_total = $total_files_on_server_size; + $tempfilesize = filesize($temp_dir.'/'.$file); + $total_files_on_server_size = $temp_total + $tempfilesize; if (stripos($file, $fileName) !== false) { $total_files++; } } - // check that all the parts are present - // the size of the last part is between chunkSize and 2*$chunkSize - if ($total_files * $chunkSize >= ($totalSize - $chunkSize + 1)) { + // If the Size of all the chunks on the server is equal to the size of the file uploaded. + if ($total_files_on_server_size >= $totalSize) { // create the final destination file if (($fp = fopen('temp/'.$fileName, 'w')) !== false) { From af7158e713146892966b933e58e8185cb35f03d4 Mon Sep 17 00:00:00 2001 From: ChadTaljaardt Date: Mon, 12 Oct 2015 13:03:55 +0100 Subject: [PATCH 077/211] Update on requested fixes Here is the update :) --- samples/Backend on PHP.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md index a5d8f73d..b5520b4b 100644 --- a/samples/Backend on PHP.md +++ b/samples/Backend on PHP.md @@ -87,15 +87,11 @@ function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { $temp_total = $total_files_on_server_size; $tempfilesize = filesize($temp_dir.'/'.$file); $total_files_on_server_size = $temp_total + $tempfilesize; - if (stripos($file, $fileName) !== false) { - $total_files++; - } } // check that all the parts are present // If the Size of all the chunks on the server is equal to the size of the file uploaded. - if ($total_files_on_server_size >= $totalSize) { - - // create the final destination file + if ($total_files_on_server_size >= $totalSize) { + // create the final destination file if (($fp = fopen('temp/'.$fileName, 'w')) !== false) { for ($i=1; $i<=$total_files; $i++) { fwrite($fp, file_get_contents($temp_dir.'/'.$fileName.'.part'.$i)); From eac1f0d76d6b550ebc5c35d267e3343282148405 Mon Sep 17 00:00:00 2001 From: Damien Klinnert Date: Tue, 27 Oct 2015 23:51:18 +1100 Subject: [PATCH 078/211] Allow to pass in a function for headers that creates the header values on the fly --- README.md | 2 +- resumable.js | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c69cec79..23aa3d2e 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Available configuration options are: * `testMethod` Method for chunk test request. (Default: `'GET'`) * `uploadMethod` Method for chunk upload request. (Default: `'POST'`) * `parameterNamespace` Extra prefix added before the name of each parameter included in the multipart POST or in the test GET. (Default: `''`) -* `headers` Extra headers to include in the multipart POST with data (Default: `{}`) +* `headers` Extra headers to include in the multipart POST with data. This can be an `object` or a `function` that allows you to construct and return a value, based on supplied `file` (Default: `{}`) * `method` Method to use when POSTing chunks to the server (`multipart` or `octet`) (Default: `multipart`) * `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`) * `testChunks` Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: `true`) diff --git a/resumable.js b/resumable.js index 3b1e138d..c9c5379e 100644 --- a/resumable.js +++ b/resumable.js @@ -638,7 +638,11 @@ $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options - $h.each($.getOpt('headers'), function(k,v) { + var customHeaders = $.getOpt('headers'); + if(typeof customHeaders === 'function') { + customHeaders = customHeaders($.fileObj, $); + } + $h.each(customHeaders, function(k,v) { $.xhr.setRequestHeader(k, v); }); $.xhr.send(null); @@ -752,7 +756,11 @@ $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options - $h.each($.getOpt('headers'), function(k,v) { + var customHeaders = $.getOpt('headers'); + if(typeof customHeaders === 'function') { + customHeaders = customHeaders($.fileObj, $); + } + $h.each(customHeaders, function(k,v) { $.xhr.setRequestHeader(k, v); }); $.xhr.send(data); From 1b4c1d62c421547351cea893e80a6020e5d750b9 Mon Sep 17 00:00:00 2001 From: Damien Klinnert Date: Tue, 3 Nov 2015 11:26:20 +1100 Subject: [PATCH 079/211] Clarify maxFiles error message --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index c9c5379e..0697b83c 100644 --- a/resumable.js +++ b/resumable.js @@ -60,7 +60,7 @@ xhrTimeout:0, maxFilesErrorCallback:function (files, errorCount) { var maxFiles = $.getOpt('maxFiles'); - alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); + alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); }, minFileSize:1, minFileSizeErrorCallback:function(file, errorCount) { From 723293217e051e19bc062fe12810e7d20a52d49f Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Wed, 11 Nov 2015 13:16:03 +0100 Subject: [PATCH 080/211] Added package.json for NPM --- package.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..d199bf1f --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "resumablejs", + "version": "1.0.2", + "description": "A JavaScript library for providing multiple simultaneous, stable, fault-tolerant and resumable/restartable uploads via the HTML5 File API.", + "main": "resumable.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/23/resumable.js.git" + }, + "keywords": [ + "html5", + "file", + "upload" + ], + "author": "https://github.com/23/resumable.js/graphs/contributors", + "license": "MIT", + "bugs": { + "url": "https://github.com/23/resumable.js/issues" + }, + "homepage": "https://github.com/23/resumable.js#readme" +} From 0864fb3e119797548e74db606e205d2d5e5d1396 Mon Sep 17 00:00:00 2001 From: Matthew de Nobrega Date: Fri, 20 Nov 2015 10:53:33 +0100 Subject: [PATCH 081/211] Exposed API for manually handling change and drop events --- resumable.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/resumable.js b/resumable.js index 0697b83c..a91f202e 100644 --- a/resumable.js +++ b/resumable.js @@ -994,6 +994,13 @@ }); return(totalSize); }; + $.handleDropEvent = function (e) { + onDrop(e); + }; + $.handleChangeEvent = function (e) { + appendFilesFromFileList(e.target.files, e); + e.target.value = ''; + }; return(this); }; From c49da524b31576fa4923f4b4ed56491cea654238 Mon Sep 17 00:00:00 2001 From: Nikhil Ballaney Date: Sat, 21 Nov 2015 16:59:55 -0800 Subject: [PATCH 082/211] Fix error reporting when removing temp files --- samples/Node.js/resumable-node.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Node.js/resumable-node.js b/samples/Node.js/resumable-node.js index 66191558..4a1fd567 100644 --- a/samples/Node.js/resumable-node.js +++ b/samples/Node.js/resumable-node.js @@ -193,7 +193,7 @@ module.exports = resumable = function(temporaryFolder){ console.log('exist removing ', chunkFilename); fs.unlink(chunkFilename, function(err) { - if (options.onError) opentions.onError(err); + if (err && options.onError) options.onError(err); }); pipeChunkRm(number + 1); @@ -209,4 +209,4 @@ module.exports = resumable = function(temporaryFolder){ } return $; -} \ No newline at end of file +} From 5c99a719aa4ecfcd57afd0f152b7b1be110b5fc8 Mon Sep 17 00:00:00 2001 From: claudio Date: Mon, 23 Nov 2015 13:30:53 -0500 Subject: [PATCH 083/211] Backend on PHP.md file modified to sort warnings and an error --- dlfk | 75 +++++++++++++++++++++++++++++++++++++++ samples/Backend on PHP.md | 29 +++++++++------ 2 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 dlfk diff --git a/dlfk b/dlfk new file mode 100644 index 00000000..7d538eed --- /dev/null +++ b/dlfk @@ -0,0 +1,75 @@ +diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md +index b5520b4..d0106c0 100644 +--- a/samples/Backend on PHP.md ++++ b/samples/Backend on PHP.md +@@ -21,6 +21,9 @@ It's a sample implementation to illustrate chunking. It should probably not be u + * + * @author Gregory Chris (http://online-php.com) + * @email www.online.php@gmail.com ++ *  ++ * @editor Bivek Joshi (http://www.bivekjoshi.com.np)  ++ * @email meetbivek@gmail.com  + */ +  +  +@@ -78,7 +81,7 @@ function rrmdir($dir) { + * @param string $chunkSize - each chunk size (in bytes) + * @param string $totalSize - original file size (in bytes) + */ +-function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { ++function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize,$total_files) {  +  + // count all the parts of this file + $total_files_on_server_size = 0; +@@ -122,17 +125,23 @@ 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') { +  ++ if(!(isset($_GET['resumableIdentifier']) && trim($_GET['resumableIdentifier'])!='')){  ++ $_GET['resumableIdentifier']='';  ++ }  + $temp_dir = 'temp/'.$_GET['resumableIdentifier']; ++ if(!(isset($_GET['resumableFilename']) && trim($_GET['resumableFilename'])!='')){  ++ $_GET['resumableFilename']='';  ++ }  ++ if(!(isset($_GET['resumableChunkNumber']) && trim($_GET['resumableChunkNumber'])!='')){  ++ $_GET['resumableChunkNumber']='';  ++ }  + $chunk_file = $temp_dir.'/'.$_GET['resumableFilename'].'.part'.$_GET['resumableChunkNumber']; + if (file_exists($chunk_file)) { + header("HTTP/1.0 200 Ok"); +- } else +- { ++ } else {  + header("HTTP/1.0 404 Not Found"); + } +- } +- +- ++}  +  + // loop through files and move the chunks to a temporarily created directory + if (!empty($_FILES)) foreach ($_FILES as $file) { +@@ -145,7 +154,9 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { +  + // init the destination file (format .part<#chunk> + // the file is stored in a temporary directory +- $temp_dir = 'temp/'.$_POST['resumableIdentifier']; ++ if(isset($_POST['resumableIdentifier']) && trim($_POST['resumableIdentifier'])!=''){  ++ $temp_dir = 'temp/'.$_POST['resumableIdentifier'];  ++ }  + $dest_file = $temp_dir.'/'.$_POST['resumableFilename'].'.part'.$_POST['resumableChunkNumber']; +  + // create the temporary directory +@@ -157,10 +168,8 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { + if (!move_uploaded_file($file['tmp_name'], $dest_file)) { + _log('Error saving (move_uploaded_file) chunk '.$_POST['resumableChunkNumber'].' for file '.$_POST['resumableFilename']); + } else { +- + // check if all the parts present, and create the final destination file +- createFileFromChunks($temp_dir, $_POST['resumableFilename'],  +- $_POST['resumableChunkSize'], $_POST['resumableTotalSize']); ++ createFileFromChunks($temp_dir, $_POST['resumableFilename'],$_POST['resumableChunkSize'], $_POST['resumableTotalSize'],$_POST['resumableTotalChunks']);  + } + } + ``` diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md index b5520b4b..d0106c0c 100644 --- a/samples/Backend on PHP.md +++ b/samples/Backend on PHP.md @@ -21,6 +21,9 @@ It's a sample implementation to illustrate chunking. It should probably not be u * * @author Gregory Chris (http://online-php.com) * @email www.online.php@gmail.com + * + * @editor Bivek Joshi (http://www.bivekjoshi.com.np) + * @email meetbivek@gmail.com */ @@ -78,7 +81,7 @@ function rrmdir($dir) { * @param string $chunkSize - each chunk size (in bytes) * @param string $totalSize - original file size (in bytes) */ -function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { +function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize,$total_files) { // count all the parts of this file $total_files_on_server_size = 0; @@ -122,17 +125,23 @@ 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') { + if(!(isset($_GET['resumableIdentifier']) && trim($_GET['resumableIdentifier'])!='')){ + $_GET['resumableIdentifier']=''; + } $temp_dir = 'temp/'.$_GET['resumableIdentifier']; + if(!(isset($_GET['resumableFilename']) && trim($_GET['resumableFilename'])!='')){ + $_GET['resumableFilename']=''; + } + if(!(isset($_GET['resumableChunkNumber']) && trim($_GET['resumableChunkNumber'])!='')){ + $_GET['resumableChunkNumber']=''; + } $chunk_file = $temp_dir.'/'.$_GET['resumableFilename'].'.part'.$_GET['resumableChunkNumber']; if (file_exists($chunk_file)) { header("HTTP/1.0 200 Ok"); - } else - { + } else { header("HTTP/1.0 404 Not Found"); } - } - - +} // loop through files and move the chunks to a temporarily created directory if (!empty($_FILES)) foreach ($_FILES as $file) { @@ -145,7 +154,9 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { // init the destination file (format .part<#chunk> // the file is stored in a temporary directory - $temp_dir = 'temp/'.$_POST['resumableIdentifier']; + if(isset($_POST['resumableIdentifier']) && trim($_POST['resumableIdentifier'])!=''){ + $temp_dir = 'temp/'.$_POST['resumableIdentifier']; + } $dest_file = $temp_dir.'/'.$_POST['resumableFilename'].'.part'.$_POST['resumableChunkNumber']; // create the temporary directory @@ -157,10 +168,8 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { if (!move_uploaded_file($file['tmp_name'], $dest_file)) { _log('Error saving (move_uploaded_file) chunk '.$_POST['resumableChunkNumber'].' for file '.$_POST['resumableFilename']); } else { - // check if all the parts present, and create the final destination file - createFileFromChunks($temp_dir, $_POST['resumableFilename'], - $_POST['resumableChunkSize'], $_POST['resumableTotalSize']); + createFileFromChunks($temp_dir, $_POST['resumableFilename'],$_POST['resumableChunkSize'], $_POST['resumableTotalSize'],$_POST['resumableTotalChunks']); } } ``` From 008d0f1494f0a8e3ad96d4fac59b9ad9c96d6942 Mon Sep 17 00:00:00 2001 From: Matthew de Nobrega Date: Tue, 24 Nov 2015 20:33:27 +0100 Subject: [PATCH 084/211] Added first-draft .d.ts file --- resumable.d.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 resumable.d.ts diff --git a/resumable.d.ts b/resumable.d.ts new file mode 100644 index 00000000..a8ae4a28 --- /dev/null +++ b/resumable.d.ts @@ -0,0 +1,31 @@ +declare class Resumable { + addFile(file: File, event: any): void + assignBrowse(domNodes, isDirectory: boolean): void + assignDrop(domNodes): void + cancel(): void + fire(): void + getFromUniqueIdentifier(uniqueIdentifier: string): any + getOpt(o: string): any + getSize(): number + handleChangeEvent(e: any): void + handleDropEvent(e: any): void + isUploading(): boolean + on(event: string, callback: any): void + pause(): void + progress(): number + removeFile(file: File): void + unAssignDrop(domNodes: any): void + upload(): void + uploadNextChunk(): void + + defaults: Object + events: Array + files: Array + opts: Object + support: boolean + version: number + + constructor(opts: Object) +} + +export = Resumable \ No newline at end of file From e232ba87e014950d1d21e206aa41223015a0c95e Mon Sep 17 00:00:00 2001 From: claudio Date: Tue, 24 Nov 2015 17:29:09 -0500 Subject: [PATCH 085/211] remove dflk file --- dlfk | 75 ------------------------------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 dlfk diff --git a/dlfk b/dlfk deleted file mode 100644 index 7d538eed..00000000 --- a/dlfk +++ /dev/null @@ -1,75 +0,0 @@ -diff --git a/samples/Backend on PHP.md b/samples/Backend on PHP.md -index b5520b4..d0106c0 100644 ---- a/samples/Backend on PHP.md -+++ b/samples/Backend on PHP.md -@@ -21,6 +21,9 @@ It's a sample implementation to illustrate chunking. It should probably not be u - * - * @author Gregory Chris (http://online-php.com) - * @email www.online.php@gmail.com -+ *  -+ * @editor Bivek Joshi (http://www.bivekjoshi.com.np)  -+ * @email meetbivek@gmail.com  - */ -  -  -@@ -78,7 +81,7 @@ function rrmdir($dir) { - * @param string $chunkSize - each chunk size (in bytes) - * @param string $totalSize - original file size (in bytes) - */ --function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { -+function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize,$total_files) {  -  - // count all the parts of this file - $total_files_on_server_size = 0; -@@ -122,17 +125,23 @@ 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') { -  -+ if(!(isset($_GET['resumableIdentifier']) && trim($_GET['resumableIdentifier'])!='')){  -+ $_GET['resumableIdentifier']='';  -+ }  - $temp_dir = 'temp/'.$_GET['resumableIdentifier']; -+ if(!(isset($_GET['resumableFilename']) && trim($_GET['resumableFilename'])!='')){  -+ $_GET['resumableFilename']='';  -+ }  -+ if(!(isset($_GET['resumableChunkNumber']) && trim($_GET['resumableChunkNumber'])!='')){  -+ $_GET['resumableChunkNumber']='';  -+ }  - $chunk_file = $temp_dir.'/'.$_GET['resumableFilename'].'.part'.$_GET['resumableChunkNumber']; - if (file_exists($chunk_file)) { - header("HTTP/1.0 200 Ok"); -- } else -- { -+ } else {  - header("HTTP/1.0 404 Not Found"); - } -- } -- -- -+}  -  - // loop through files and move the chunks to a temporarily created directory - if (!empty($_FILES)) foreach ($_FILES as $file) { -@@ -145,7 +154,9 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { -  - // init the destination file (format .part<#chunk> - // the file is stored in a temporary directory -- $temp_dir = 'temp/'.$_POST['resumableIdentifier']; -+ if(isset($_POST['resumableIdentifier']) && trim($_POST['resumableIdentifier'])!=''){  -+ $temp_dir = 'temp/'.$_POST['resumableIdentifier'];  -+ }  - $dest_file = $temp_dir.'/'.$_POST['resumableFilename'].'.part'.$_POST['resumableChunkNumber']; -  - // create the temporary directory -@@ -157,10 +168,8 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { - if (!move_uploaded_file($file['tmp_name'], $dest_file)) { - _log('Error saving (move_uploaded_file) chunk '.$_POST['resumableChunkNumber'].' for file '.$_POST['resumableFilename']); - } else { -- - // check if all the parts present, and create the final destination file -- createFileFromChunks($temp_dir, $_POST['resumableFilename'],  -- $_POST['resumableChunkSize'], $_POST['resumableTotalSize']); -+ createFileFromChunks($temp_dir, $_POST['resumableFilename'],$_POST['resumableChunkSize'], $_POST['resumableTotalSize'],$_POST['resumableTotalChunks']);  - } - } - ``` From 8a7dd2fd7b8ecb5611000c1df3fc285dc470f45b Mon Sep 17 00:00:00 2001 From: Matthew de Nobrega Date: Mon, 30 Nov 2015 15:46:50 +0100 Subject: [PATCH 086/211] Renamed type def file as per basarat's recommendation Added test file with example usage --- index.d.ts | 31 +++++++++++++++++++++++++++++++ resumable-tests.ts | 30 ++++++++++++++++++++++++++++++ resumable.d.ts | 31 ------------------------------- 3 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 index.d.ts create mode 100644 resumable-tests.ts delete mode 100644 resumable.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..ea534b0b --- /dev/null +++ b/index.d.ts @@ -0,0 +1,31 @@ +declare class Resumable { + addFile(file: File, event: any): void; + assignBrowse(domNodes: any, isDirectory: boolean): void; + assignDrop(domNodes: any): void; + cancel(): void; + fire(): void; + getFromUniqueIdentifier(uniqueIdentifier: string): any; + getOpt(o: string): any; + getSize(): number; + handleChangeEvent(e: any): void; + handleDropEvent(e: any): void; + isUploading(): boolean; + on(event: string, callback: any): void; + pause(): void; + progress(): number; + removeFile(file: any): void; + unAssignDrop(domNodes: any): void; + upload(): void; + uploadNextChunk(): void; + + defaults: Object; + events: Array; + files: Array; + opts: Object; + support: boolean; + version: number; + + constructor(opts: Object); +} + +export = Resumable \ No newline at end of file diff --git a/resumable-tests.ts b/resumable-tests.ts new file mode 100644 index 00000000..69ce1516 --- /dev/null +++ b/resumable-tests.ts @@ -0,0 +1,30 @@ +import Resumable = require('./index'); + +let resumable: Resumable = new Resumable({chunkSize: 123}); + +resumable.addFile(new File([], 'test.tmp'), {}); +resumable.assignBrowse(document, true); +resumable.assignBrowse([document], true); +resumable.assignDrop(document); +resumable.assignDrop([document]); +resumable.cancel(); +let defaults: Object = resumable.defaults; +let events: any[] = resumable.events; +let files: File[] = resumable.files; +resumable.fire(); +let {} = resumable.getFromUniqueIdentifier('test'); +let {} = resumable.getOpt('test'); +let size:number = resumable.getSize(); +resumable.handleChangeEvent({}); +resumable.handleDropEvent({}); +let isUploading: boolean = resumable.isUploading(); +resumable.on('test', function() {}); +let opts: Object = resumable.opts; +resumable.pause(); +let progress:number = resumable.progress(); +resumable.removeFile('TODO'); +let support: boolean = resumable.support; +resumable.unAssignDrop({}); +resumable.upload(); +resumable.uploadNextChunk(); +let version:number = resumable.version; \ No newline at end of file diff --git a/resumable.d.ts b/resumable.d.ts deleted file mode 100644 index a8ae4a28..00000000 --- a/resumable.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -declare class Resumable { - addFile(file: File, event: any): void - assignBrowse(domNodes, isDirectory: boolean): void - assignDrop(domNodes): void - cancel(): void - fire(): void - getFromUniqueIdentifier(uniqueIdentifier: string): any - getOpt(o: string): any - getSize(): number - handleChangeEvent(e: any): void - handleDropEvent(e: any): void - isUploading(): boolean - on(event: string, callback: any): void - pause(): void - progress(): number - removeFile(file: File): void - unAssignDrop(domNodes: any): void - upload(): void - uploadNextChunk(): void - - defaults: Object - events: Array - files: Array - opts: Object - support: boolean - version: number - - constructor(opts: Object) -} - -export = Resumable \ No newline at end of file From 3d0dd2332ff4f16b94804c925b5cac1c755bab37 Mon Sep 17 00:00:00 2001 From: Matthew de Nobrega Date: Mon, 30 Nov 2015 15:51:48 +0100 Subject: [PATCH 087/211] Added header Tweaked signatures --- index.d.ts | 9 +++++++-- resumable-tests.ts | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index ea534b0b..99138404 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,8 @@ +// Type definitions for Resumable.js +// Project: https://github.com/23/resumable.js +// Definitions by: Matthew de Nobrega +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + declare class Resumable { addFile(file: File, event: any): void; assignBrowse(domNodes: any, isDirectory: boolean): void; @@ -20,12 +25,12 @@ declare class Resumable { defaults: Object; events: Array; - files: Array; + files: Array; opts: Object; support: boolean; version: number; - constructor(opts: Object); + constructor(opts?: Object); } export = Resumable \ No newline at end of file diff --git a/resumable-tests.ts b/resumable-tests.ts index 69ce1516..ea200695 100644 --- a/resumable-tests.ts +++ b/resumable-tests.ts @@ -1,6 +1,7 @@ import Resumable = require('./index'); let resumable: Resumable = new Resumable({chunkSize: 123}); +let resumableNoOpts: Resumable = new Resumable(); resumable.addFile(new File([], 'test.tmp'), {}); resumable.assignBrowse(document, true); @@ -10,7 +11,7 @@ resumable.assignDrop([document]); resumable.cancel(); let defaults: Object = resumable.defaults; let events: any[] = resumable.events; -let files: File[] = resumable.files; +let files: any[] = resumable.files; resumable.fire(); let {} = resumable.getFromUniqueIdentifier('test'); let {} = resumable.getOpt('test'); From c360cc799883a5284ed1077a66c4cb90b3092a73 Mon Sep 17 00:00:00 2001 From: Renzo Date: Mon, 14 Dec 2015 10:35:48 +0000 Subject: [PATCH 088/211] Added beforeCancel event This event might be necessary processing the files being cancelled specially for server side. currently we can't use the cancels since by the time it fires the r.files object is already empty --- resumable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resumable.js b/resumable.js index a91f202e..1afd7d51 100644 --- a/resumable.js +++ b/resumable.js @@ -955,6 +955,7 @@ $.fire('pause'); }; $.cancel = function(){ + $.fire('beforeCancel'); for(var i = $.files.length - 1; i >= 0; i--) { $.files[i].cancel(); } From 6c7e0afdfffbe56991610ccee600e02cfd7c4ed2 Mon Sep 17 00:00:00 2001 From: Renzo Date: Mon, 14 Dec 2015 14:13:44 +0000 Subject: [PATCH 089/211] Added description for beforeBancel event Added documentation for beforeCancel envent --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 23aa3d2e..d2a691a9 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ Available configuration options are: * `.progress()` Uploading progress. * `.error(message, file)` An error, including fileError, occurred. * `.pause()` Uploading was paused. +* `.beforeCancel()` Triggers before the items are cancelled allowing to do any processing on uploading files. * `.cancel()` Uploading was canceled. * `.chunkingStart(file)` Started preparing file for upload * `.chunkingProgress(file,ratio)` Show progress in file preparation From d86cbf930f1e2f635791167f22344d3d0252c52f Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Thu, 4 Feb 2016 11:49:07 +0100 Subject: [PATCH 090/211] Make sure `fileSuccess` event are not fired before the server has returned. Do you remember installing Windows 95? How it got to 95% done and then hung for the next few hours without any progress. This commit brings the awesomeness of this to Resumable.js as well. Before, we use the upload progress as an indicator of the full upload process -- this meant that `fileSuccess` would fire before the server had returned its status code in some cases. Since we rely on that status code, a simple padding has been made of the last 5% of the progress of each individual chunks is actually assigned to the server returning -- instead of to the pure bytes being updated. Given small enough chunk sizes, this should now affect the user experience of using resumable. --- resumable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resumable.js b/resumable.js index 1afd7d51..fcc16134 100644 --- a/resumable.js +++ b/resumable.js @@ -803,6 +803,7 @@ if(typeof(relative)==='undefined') relative = false; var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1); if($.pendingRetry) return(0); + if(!$.xhr || !$.xhr.status) factor*=.95; var s = $.status(); switch(s){ case 'success': From 8242cc862abb38f9246a72fc61515d9f7d2e6672 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Wed, 10 Feb 2016 21:25:21 +0100 Subject: [PATCH 091/211] Allow `HTTP 204` to communicate success --- resumable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index fcc16134..389a560d 100644 --- a/resumable.js +++ b/resumable.js @@ -782,8 +782,8 @@ // 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 or 201 (created) perfect + if($.xhr.status == 200 || $.xhr.status == 201 || $.xhr.status == 204) { + // HTTP 200, 201 (created), 204 (no content) return('success'); } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { // HTTP 415/500/501, permanent error From d2a74b036c2e4db13f44ceb156569a999df0ea56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bazyli=20Brz=C3=B3ska?= Date: Sun, 14 Feb 2016 01:36:56 +0100 Subject: [PATCH 092/211] Proper TypeScript definitions Fixes https://github.com/23/resumable.js/issues/262 --- index.d.ts | 36 ------ resumable.d.ts | 326 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 326 insertions(+), 36 deletions(-) delete mode 100644 index.d.ts create mode 100644 resumable.d.ts diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index 99138404..00000000 --- a/index.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Type definitions for Resumable.js -// Project: https://github.com/23/resumable.js -// Definitions by: Matthew de Nobrega -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare class Resumable { - addFile(file: File, event: any): void; - assignBrowse(domNodes: any, isDirectory: boolean): void; - assignDrop(domNodes: any): void; - cancel(): void; - fire(): void; - getFromUniqueIdentifier(uniqueIdentifier: string): any; - getOpt(o: string): any; - getSize(): number; - handleChangeEvent(e: any): void; - handleDropEvent(e: any): void; - isUploading(): boolean; - on(event: string, callback: any): void; - pause(): void; - progress(): number; - removeFile(file: any): void; - unAssignDrop(domNodes: any): void; - upload(): void; - uploadNextChunk(): void; - - defaults: Object; - events: Array; - files: Array; - opts: Object; - support: boolean; - version: number; - - constructor(opts?: Object); -} - -export = Resumable \ No newline at end of file diff --git a/resumable.d.ts b/resumable.d.ts new file mode 100644 index 00000000..200ed28d --- /dev/null +++ b/resumable.d.ts @@ -0,0 +1,326 @@ +// Type definitions for Resumable.js +// Project: https://github.com/23/resumable.js +// Definitions by: Bazyli Brzóska +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare module Resumable { + export interface ConfigurationHash { + /** + * The target URL for the multipart POST request. This can be a string or a function that allows you you to construct and return a value, based on supplied params. (Default: /) + **/ + target?: string; + /** + * The size in bytes of each uploaded chunk of data. The last uploaded chunk will be at least this size and up to two the size, see Issue #51 for details and reasons. (Default: 1*1024*1024) + **/ + chunkSize?: number; + /** + * Force all chunks to be less or equal than chunkSize. Otherwise, the last chunk will be greater than or equal to chunkSize. (Default: false) + **/ + forceChunkSize?: boolean; + /** + * Number of simultaneous uploads (Default: 3) + **/ + simultaneousUploads?: number; + /** + * The name of the multipart POST parameter to use for the file chunk (Default: file) + **/ + fileParameterName?: string; + /** + * Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: {}) + **/ + query?: Object; + /** + * Method for chunk test request. (Default: 'GET') + **/ + testMethod?: 'GET'|'POST'|'OPTIONS'|'PUT'|'DELETE'; + /** + * Method for chunk upload request. (Default: 'POST') + **/ + uploadMethod?: 'GET'|'POST'|'OPTIONS'|'PUT'|'DELETE'; + /** + * Extra prefix added before the name of each parameter included in the multipart POST or in the test GET. (Default: '') + **/ + parameterNamespace?: string; + /** + * Extra headers to include in the multipart POST with data. This can be an object or a function that allows you to construct and return a value, based on supplied file (Default: {}) + **/ + headers?: Object | ((file) => Object); + /** + * Method to use when POSTing chunks to the server (multipart or octet) (Default: multipart) + **/ + method?: 'multipart' | 'octet'; + /** + * Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: false) + **/ + prioritizeFirstAndLastChunk?: boolean; + /** + * Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: true) + **/ + testChunks?: boolean; + /** + * Optional function to process each chunk before testing & sending. Function is passed the chunk as parameter, and should call the preprocessFinished method on the chunk when finished. (Default: null) + **/ + preprocess?: (chunk:ResumableChunk) => ResumableChunk; + /** + * Override the function that generates unique identifiers for each file. (Default: null) + **/ + generateUniqueIdentifier?: () => string; + /** + * Indicates how many files can be uploaded in a single session. Valid values are any positive integer and undefined for no limit. (Default: undefined) + **/ + maxFiles?: number; + /** + * A function which displays the please upload n file(s) at a time message. (Default: displays an alert box with the message Please n one file(s) at a time.) + **/ + maxFilesErrorCallback?: (files, errorCount) => void; + /** + * The minimum allowed file size. (Default: undefined) + **/ + minFileSize?: boolean; + /** + * A function which displays an error a selected file is smaller than allowed. (Default: displays an alert for every bad file.) + **/ + minFileSizeErrorCallback?:(file, errorCount) => void; + /** + * The maximum allowed file size. (Default: undefined) + **/ + maxFileSize?: boolean; + /** + * A function which displays an error a selected file is larger than allowed. (Default: displays an alert for every bad file.) + **/ + maxFileSizeErrorCallback?: (file, errorCount) => void; + /** + * The file types allowed to upload. An empty array allow any file type. (Default: []) + **/ + fileType?: Array; + /** + * A function which displays an error a selected file has type not allowed. (Default: displays an alert for every bad file.) + **/ + fileTypeErrorCallback?: (file, errorCount) => void; + /** + * The maximum number of retries for a chunk before the upload is failed. Valid values are any positive integer and undefined for no limit. (Default: undefined) + **/ + maxChunkRetries?: number; + /** + * The number of milliseconds to wait before retrying a chunk on a non-permanent error. Valid values are any positive integer and undefined for immediate retry. (Default: undefined) + **/ + chunkRetryInterval?: number; + /** + * Standard CORS requests do not send or set any cookies by default. In order to include cookies as part of the request, you need to set the withCredentials property to true. (Default: false) + **/ + withCredentials?: boolean; + } + + export class Resumable { + constructor(options:ConfigurationHash); + + /** + * A boolean value indicator whether or not Resumable.js is supported by the current browser. + **/ + support: boolean; + /** + * A hash object of the configuration of the Resumable.js instance. + **/ + opts: ConfigurationHash; + /** + * An array of ResumableFile file objects added by the user (see full docs for this object type below). + **/ + files: Array; + + events: Array; + version: number; + + /** + * Assign a browse action to one or more DOM nodes. Pass in true to allow directories to be selected (Chrome only). + **/ + assignBrowse(domNode: Element, isDirectory: boolean): void; + assignBrowse(domNodes: Array, isDirectory: boolean): void; + /** + * Assign one or more DOM nodes as a drop target. + **/ + assignDrop(domNode: Element): void; + assignDrop(domNodes: Array): void; + unAssignDrop(domNode: Element): void; + unAssignDrop(domNodes: Array): void; + /** + * Start or resume uploading. + **/ + upload(): void; + uploadNextChunk(): void; + /** + * Pause uploading. + **/ + pause(): void; + /** + * Cancel upload of all ResumableFile objects and remove them from the list. + **/ + cancel(): void; + fire(): void; + /** + * Returns a float between 0 and 1 indicating the current upload progress of all files. + **/ + progress(): number; + /** + * Returns a boolean indicating whether or not the instance is currently uploading anything. + **/ + isUploading(): boolean; + /** + * Add a HTML5 File object to the list of files. + **/ + addFile(file: File): void; + /** + * Cancel upload of a specific ResumableFile object on the list from the list. + **/ + removeFile(file: ResumableFile): void; + /** + * Look up a ResumableFile object by its unique identifier. + **/ + getFromUniqueIdentifier(uniqueIdentifier: string): void; + /** + * Returns the total size of the upload in bytes. + **/ + getSize(): void; + getOpt(o: string): any; + + // Events + /** + * Listen for event from Resumable.js (see below) + **/ + on(event: string, callback: Function): void; + /** + * A specific file was completed. + **/ + on(event: 'fileSuccess', callback: (file: ResumableFile) => void); void; + /** + * Uploading progressed for a specific file. + **/ + on(event: 'fileProgress', callback: (file: ResumableFile) => void): void; + /** + * A new file was added. Optionally, you can use the browser event object from when the file was added. + **/ + on(event: 'fileAdded', callback: (file: ResumableFile, event: DragEvent) => void): void; + /** + * New files were added. + **/ + on(event: 'filesAdded', callback: (files: Array) => void): void; + /** + * Something went wrong during upload of a specific file, uploading is being retried. + **/ + on(event: 'fileRetry', callback: (file: ResumableFile) => void): void; + /** + * An error occurred during upload of a specific file. + **/ + on(event: 'fileError', callback: (file: ResumableFile, message: string) => void): void; + /** + * Upload has been started on the Resumable object. + **/ + on(event: 'uploadStart', callback: () => void): void; + /** + * Uploading completed. + **/ + on(event: 'complete', callback: () => void): void; + /** + * Uploading progress. + **/ + on(event: 'progress', callback: () => void): void; + /** + * An error, including fileError, occurred. + **/ + on(event: 'error', callback: (message: string, file: ResumableFile) => void): void; + /** + * Uploading was paused. + **/ + on(event: 'pause', callback: () => void): void; + /** + * Triggers before the items are cancelled allowing to do any processing on uploading files. + **/ + on(event: 'beforeCancel', callback: () => void): void; + /** + * Uploading was canceled. + **/ + on(event: 'cancel', callback: () => void): void; + /** + * Started preparing file for upload + **/ + on(event: 'chunkingStart', callback: (file: ResumableFile) => void): void; + /** + * Show progress in file preparation + **/ + on(event: 'chunkingProgress', callback: (file: ResumableFile, ratio) => void): void; + /** + * File is ready for upload + **/ + on(event: 'chunkingComplete', callback: (file: ResumableFile) => void): void; + /** + * Listen to all the events listed above with the same callback function. + **/ + on(event: 'catchAll', callback: () => void); + } + + export interface ResumableFile { + /** + * A back-reference to the parent Resumable object. + **/ + resumableObj: Resumable; + /** + * The correlating HTML5 File object. + **/ + file: File; + /** + * The name of the file. + **/ + fileName: string; + /** + * The relative path to the file (defaults to file name if relative path doesn't exist) + **/ + relativePath: string; + /** + * Size in bytes of the file. + **/ + size: number; + /** + * A unique identifier assigned to this file object. This value is included in uploads to the server for reference, but can also be used in CSS classes etc when building your upload UI. + **/ + uniqueIdentifier: string; + /** + * An array of ResumableChunk items. You shouldn't need to dig into these. + **/ + chunks: Array; + + + /** + * Returns a float between 0 and 1 indicating the current upload progress of the file. If relative is true, the value is returned relative to all files in the Resumable.js instance. + **/ + progress: (relative: boolean) => number; + /** + * Abort uploading the file. + **/ + abort: () => void; + /** + * Abort uploading the file and delete it from the list of files to upload. + **/ + cancel: () => void; + /** + * Retry uploading the file. + **/ + retry: () => void; + /** + * Rebuild the state of a ResumableFile object, including reassigning chunks and XMLHttpRequest instances. + **/ + bootstrap: () => void; + /** + * Returns a boolean indicating whether file chunks is uploading. + **/ + isUploading: () => boolean; + /** + * Returns a boolean indicating whether the file has completed uploading and received a server response. + **/ + isComplete: () => boolean; + } + + class ResumableChunk {} +} + +declare module 'resumablejs' { + export = Resumable.Resumable; +} From a570a43d2fef6f34fa362be2edf54fa282b88b1d Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Thu, 25 Feb 2016 23:45:28 +0100 Subject: [PATCH 093/211] Roll back previous blunder on usage of `HTTP 204` to close #282 --- resumable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 389a560d..938c8ec1 100644 --- a/resumable.js +++ b/resumable.js @@ -782,8 +782,8 @@ // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening return('uploading'); } else { - if($.xhr.status == 200 || $.xhr.status == 201 || $.xhr.status == 204) { - // HTTP 200, 201 (created), 204 (no content) + 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 415/500/501, permanent error From 44f7ed66b59f1eb22c471151fd4a27c968b7baee Mon Sep 17 00:00:00 2001 From: Dagomar Paulides Date: Wed, 2 Mar 2016 09:31:23 +0100 Subject: [PATCH 094/211] file input shows "no file chosen" This adds an option to clear the input field. It defaultsto true, since that is the default right now. --- resumable.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 938c8ec1..813ae71a 100644 --- a/resumable.js +++ b/resumable.js @@ -58,6 +58,7 @@ maxFiles:undefined, withCredentials:false, xhrTimeout:0, + clearInput:true, maxFilesErrorCallback:function (files, errorCount) { var maxFiles = $.getOpt('maxFiles'); alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); @@ -907,7 +908,10 @@ // When new files are added, simply append them to the overall list input.addEventListener('change', function(e){ appendFilesFromFileList(e.target.files,e); - e.target.value = ''; + var clearInput = $.getOpt('clearInput'); + if (clearInput) { + e.target.value = ''; + } }, false); }); }; From ef962ffbceee6b85801efea8a7221562fcdbc53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guzm=C3=A1n=20Fern=C3=A1ndez=20Garc=C3=ADa?= Date: Fri, 1 Apr 2016 10:46:13 +0200 Subject: [PATCH 095/211] Add options to Resumable obj configuration to choose POST/GET parameter names (e.g. chunkNumberParameterName: "resumableChunkNumber") --- README.md | 9 + resumable.d.ts | 36 +++ resumable.js | 50 ++-- samples/coffeescript/resumable.coffee | 45 ++-- samples/coffeescript/resumable.js | 349 +++++++++++++------------- 5 files changed, 283 insertions(+), 206 deletions(-) diff --git a/README.md b/README.md index d2a691a9..960ffd7b 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,15 @@ Available configuration options are: * `forceChunkSize` Force all chunks to be less or equal than chunkSize. Otherwise, the last chunk will be greater than or equal to `chunkSize`. (Default: `false`) * `simultaneousUploads` Number of simultaneous uploads (Default: `3`) * `fileParameterName` The name of the multipart POST parameter to use for the file chunk (Default: `file`) +* `chunkNumberParameterName` The name of the chunk index (base-1) in the current upload POST parameter to use for the file chunk (Default: `resumableChunkNumber`) +* `totalChunksParameterName` The name of the total number of chunks POST parameter to use for the file chunk (Default: `resumableTotalChunks`) +* `chunkSizeParameterName` The name of the general chunk size POST parameter to use for the file chunk (Default: `resumableChunkSize`) +* `totalSizeParameterName` The name of the total file size number POST parameter to use for the file chunk (Default: `resumableTotalSize`) +* `identifierParameterName` The name of the unique identifier POST parameter to use for the file chunk (Default: `resumableIdentifier`) +* `fileNameParameterName` The name of the original file name POST parameter to use for the file chunk (Default: `resumableFilename`) +* `relativePathParameterName` The name of the file's relative path POST parameter to use for the file chunk (Default: `resumableRelativePath`) +* `currentChunkSizeParameterName` The name of the current chunk size POST parameter to use for the file chunk (Default: `resumableCurrentChunkSize`) +* `typeParameterName` The name of the file type POST parameter to use for the file chunk (Default: `resumableType`) * `query` Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: `{}`) * `testMethod` Method for chunk test request. (Default: `'GET'`) * `uploadMethod` Method for chunk upload request. (Default: `'POST'`) diff --git a/resumable.d.ts b/resumable.d.ts index 200ed28d..74360dfe 100644 --- a/resumable.d.ts +++ b/resumable.d.ts @@ -25,6 +25,42 @@ declare module Resumable { * The name of the multipart POST parameter to use for the file chunk (Default: file) **/ fileParameterName?: string; + /** + * The name of the chunk index (base-1) in the current upload POST parameter to use for the file chunk (Default: resumableChunkNumber) + */ + chunkNumberParameterName?: string; + /** + * The name of the total number of chunks POST parameter to use for the file chunk (Default: resumableTotalChunks) + */ + totalChunksParameterName?: string; + /** + * The name of the general chunk size POST parameter to use for the file chunk (Default: resumableChunkSize) + */ + chunkSizeParameterName?: string; + /** + * The name of the total file size number POST parameter to use for the file chunk (Default: resumableTotalSize) + */ + totalSizeParameterName?: string; + /** + * The name of the unique identifier POST parameter to use for the file chunk (Default: resumableIdentifier) + */ + identifierParameterName?: string; + /** + * The name of the original file name POST parameter to use for the file chunk (Default: resumableFilename) + */ + fileNameParameterName?: string; + /** + * The name of the file's relative path POST parameter to use for the file chunk (Default: resumableRelativePath) + */ + relativePathParameterName?: string; + /** + * The name of the current chunk size POST parameter to use for the file chunk (Default: resumableCurrentChunkSize) + */ + currentChunkSizeParameterName?: string; + /** + * The name of the file type POST parameter to use for the file chunk (Default: resumableType) + */ + typeParameterName?: string; /** * Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: {}) **/ diff --git a/resumable.js b/resumable.js index 813ae71a..4d5fd195 100644 --- a/resumable.js +++ b/resumable.js @@ -39,7 +39,16 @@ forceChunkSize:false, simultaneousUploads:3, fileParameterName:'file', - throttleProgressCallbacks:0.5, + chunkNumberParameterName: 'resumableChunkNumber', + chunkSizeParameterName: 'resumableChunkSize', + currentChunkSizeParameterName: 'resumableCurrentChunkSize', + totalSizeParameterName: 'resumableTotalSize', + typeParameterName: 'resumableType', + identifierParameterName: 'resumableIdentifier', + fileNameParameterName: 'resumableFilename', + relativePathParameterName: 'resumableRelativePath', + totalChunksParameterName: 'resumableTotalChunks', + throttleProgressCallbacks:0.5, query:{}, headers:{}, preprocess:null, @@ -625,15 +634,15 @@ params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); }); // Add extra data to identify chunk - params.push([parameterNamespace+'resumableChunkNumber', encodeURIComponent($.offset+1)].join('=')); - params.push([parameterNamespace+'resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('=')); - params.push([parameterNamespace+'resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('=')); - params.push([parameterNamespace+'resumableTotalSize', encodeURIComponent($.fileObjSize)].join('=')); - params.push([parameterNamespace+'resumableType', encodeURIComponent($.fileObjType)].join('=')); - params.push([parameterNamespace+'resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); - params.push([parameterNamespace+'resumableFilename', encodeURIComponent($.fileObj.fileName)].join('=')); - params.push([parameterNamespace+'resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('=')); - params.push([parameterNamespace+'resumableTotalChunks', encodeURIComponent($.fileObj.chunks.length)].join('=')); + params.push([parameterNamespace + $.getOpt('chunkNumberParameterName'), encodeURIComponent($.offset + 1)].join('=')); + params.push([parameterNamespace + $.getOpt('chunkSizeParameterName'), encodeURIComponent($.getOpt('chunkSize'))].join('=')); + params.push([parameterNamespace + $.getOpt('currentChunkSizeParameterName'), encodeURIComponent($.endByte - $.startByte)].join('=')); + params.push([parameterNamespace + $.getOpt('totalSizeParameterName'), encodeURIComponent($.fileObjSize)].join('=')); + params.push([parameterNamespace + $.getOpt('typeParameterName'), encodeURIComponent($.fileObjType)].join('=')); + params.push([parameterNamespace + $.getOpt('identifierParameterName'), encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); + params.push([parameterNamespace + $.getOpt('fileNameParameterName'), encodeURIComponent($.fileObj.fileName)].join('=')); + params.push([parameterNamespace + $.getOpt('relativePathParameterName'), encodeURIComponent($.fileObj.relativePath)].join('=')); + params.push([parameterNamespace + $.getOpt('totalChunksParameterName'), encodeURIComponent($.fileObj.chunks.length)].join('=')); // Append the relevant chunk and send it $.xhr.open($.getOpt('testMethod'), $h.getTarget(params)); $.xhr.timeout = $.getOpt('xhrTimeout'); @@ -708,17 +717,16 @@ $.xhr.addEventListener('timeout', doneHandler, false); // Set up the basic query data from Resumable - var query = { - resumableChunkNumber: $.offset+1, - resumableChunkSize: $.getOpt('chunkSize'), - resumableCurrentChunkSize: $.endByte - $.startByte, - resumableTotalSize: $.fileObjSize, - resumableType: $.fileObjType, - resumableIdentifier: $.fileObj.uniqueIdentifier, - resumableFilename: $.fileObj.fileName, - resumableRelativePath: $.fileObj.relativePath, - resumableTotalChunks: $.fileObj.chunks.length - }; + var query = {}; + query[$.getOpt('chunkNumberParameterName')] = $.offset+1; + query[$.getOpt('chunkSizeParameterName')] = $.getOpt('chunkSize'); + query[$.getOpt('currentChunkSizeParameterName')] = $.endByte - $.startByte; + query[$.getOpt('totalSizeParameterName')] = $.fileObjSize; + query[$.getOpt('typeParameterName')] = $.fileObjType; + query[$.getOpt('identifierParameterName')] = $.fileObj.uniqueIdentifier; + query[$.getOpt('filenameParameterName')] = $.fileObj.fileName; + query[$.getOpt('relativePathParameterName')] = $.fileObj.relativePath; + query[$.getOpt('totalChunksParameterName')] = $.fileObj.chunks.lengt; // Mix in custom data var customQuery = $.getOpt('query'); if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); diff --git a/samples/coffeescript/resumable.coffee b/samples/coffeescript/resumable.coffee index 2f2f774b..d376fb84 100644 --- a/samples/coffeescript/resumable.coffee +++ b/samples/coffeescript/resumable.coffee @@ -11,6 +11,15 @@ window.Resumable = class Resumable forceChunkSize: false simultaneousUploads: 3 fileParameterName: 'file' + chunkNumberParameterName: 'resumableChunkNumber' + chunkSizeParameterName: 'resumableChunkSize' + currentChunkSizeParameterName: 'resumableCurrentChunkSize' + totalSizeParameterName: 'resumableTotalSize' + typeParameterName: 'resumableType' + identifierParameterName: 'resumableIdentifier' + fileNameParameterName: 'resumableFilename' + relativePathParameterName: 'resumableRelativePath' + totalChunksParameterName: 'resumableTotalChunks' throttleProgressCallbacks: 0.5 query: {} headers: {} @@ -352,13 +361,15 @@ window.ResumableChunk = class ResumableChunk pushParams key, value #Add extra data to identify chunk - @pushParams params, 'resumableChunkNumber', (@offset + 1) - @pushParams params, 'resumableChunkSize', @chunkSize - @pushParams params, 'resumableCurrentChunkSize', (@endByte - @startByte) - @pushParams params, 'resumableTotalSize', @fileObjSize - @pushParams params, 'resumableIdentifier', @fileObj.uniqueIdentifier - @pushParams params, 'resumableFilename', @fileObj.fileName - @pushParams params, 'resumableRelativePath', @fileObj.relativePath + @pushParams params, (@getOpt 'chunkNumberParameterName'), (@offset + 1) + @pushParams params, (@getOpt 'chunkSizeParameterName'), @chunkSize + @pushParams params, (@getOpt 'currentChunkSizeParameterName'), (@endByte - @startByte) + @pushParams params, (@getOpt 'totalSizeParameterName'), @fileObjSize + #TODO: @pushParams params, (@getOpt 'typeParameterName'), + @pushParams params, (@getOpt 'identifierParameterName'), @fileObj.uniqueIdentifier + @pushParams params, (@getOpt 'fileNameParameterName'), @fileObj.fileName + @pushParams params, (@getOpt 'relativePathParameterName'), @fileObj.relativePath + #TODO: @pushParams params, (@getOpt 'totalChunksParameterName'), #Append the relevant chunk and send it @xhr.open 'GET', @getOpt('target') + '?' + params.join('&') @@ -442,15 +453,17 @@ window.ResumableChunk = class ResumableChunk target = @getOpt 'target' #Set up the basic query data from Resumable - query = - resumableChunkNumber: @offset+1 - resumableChunkSize: @getOpt('chunkSize') - resumableCurrentChunkSize: @endByte - @startByte - resumableTotalSize: @fileObjSize - resumableIdentifier: @fileObj.uniqueIdentifier - resumableFilename: @fileObj.fileName - resumableRelativePath: @fileObj.relativePath - + query = {} + + query[(@getOpt 'chunkNumber')] = @offset+1 + query[(@getOpt 'chunkSize')] = @getOpt('chunkSize') + query[(@getOpt 'currentChunkSize')] = @endByte - @startByte + query[(@getOpt 'totalSize')] = @fileObjSize + #TODO: query[(@getOpt 'typeParameterName')] = + query[(@getOpt 'identifier')] = @fileObj.uniqueIdentifier + query[(@getOpt 'filename')] = @fileObj.fileName + query[(@getOpt 'relativePath')] = @fileObj.relativePath + #TODO: query[(@getOpt 'totalChunksParameterName')] = customQuery = @getOpt 'query' customQuery = customQuery(@fileObj, @) if typeof customQuery is 'function' diff --git a/samples/coffeescript/resumable.js b/samples/coffeescript/resumable.js index cc1056e9..5a6c7649 100644 --- a/samples/coffeescript/resumable.js +++ b/samples/coffeescript/resumable.js @@ -1,12 +1,10 @@ -//@ sourceMappingURL=resumable.map -// Generated by CoffeeScript 1.6.1 +// Generated by CoffeeScript 1.10.0 (function() { var Resumable, ResumableChunk, ResumableFile, - __slice = [].slice, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + slice = [].slice, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; window.Resumable = Resumable = (function() { - function Resumable(opt) { this.opt = opt; console.log('constructor'); @@ -17,6 +15,15 @@ forceChunkSize: false, simultaneousUploads: 3, fileParameterName: 'file', + chunkNumberParameterName: 'resumableChunkNumber', + chunkSizeParameterName: 'resumableChunkSize', + currentChunkSizeParameterName: 'resumableCurrentChunkSize', + totalSizeParameterName: 'resumableTotalSize', + typeParameterName: 'resumableType', + identifierParameterName: 'resumableIdentifier', + fileNameParameterName: 'resumableFilename', + relativePathParameterName: 'resumableRelativePath', + totalChunksParameterName: 'resumableTotalChunks', throttleProgressCallbacks: 0.5, query: {}, headers: {}, @@ -31,9 +38,9 @@ permanentErrors: [415, 500, 501], maxFiles: void 0, maxFilesErrorCallback: function(files, errorCount) { - var maxFiles, _ref; + var maxFiles, ref; maxFiles = this.getOpt('maxFiles'); - return alert('Please upload ' + maxFiles + ' file' + ((_ref = maxFiles === 1) != null ? _ref : { + return alert('Please upload ' + maxFiles + ' file' + ((ref = maxFiles === 1) != null ? ref : { '': 's' }) + ' at a time.'); }, @@ -53,11 +60,11 @@ } Resumable.prototype.getOpt = function(o) { - var item, opts, _i, _len; + var i, item, len, opts; if (o instanceof Array) { opts = {}; - for (_i = 0, _len = o.length; _i < _len; _i++) { - item = o[_i]; + for (i = 0, len = o.length; i < len; i++) { + item = o[i]; opts[item] = this.getOpt(item); } return opts; @@ -110,13 +117,13 @@ }; Resumable.prototype.fire = function() { - var args, e, event, _i, _len, _ref; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + var args, e, event, i, len, ref; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; console.log("fire: " + args[0]); event = args[0].toLowerCase(); - _ref = this.events; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - e = _ref[_i]; + ref = this.events; + for (i = 0, len = ref.length; i < len; i++) { + e = ref[i]; if (e.event.toLowerCase() === event) { e.callback.apply(this, args.slice(1)); } @@ -144,17 +151,17 @@ }; Resumable.prototype.appendFilesFromFileList = function(fileList, event) { - var errorCount, file, files, maxFileSize, maxFileSizeErrorCallback, maxFiles, maxFilesErrorCallback, minFileSize, minFileSizeErrorCallback, resumableFile, _i, _len, _ref; + var errorCount, file, files, i, len, maxFileSize, maxFileSizeErrorCallback, maxFiles, maxFilesErrorCallback, minFileSize, minFileSizeErrorCallback, ref, resumableFile; console.log("appendFilesFromFileList"); errorCount = 0; - _ref = this.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback']), maxFiles = _ref[0], minFileSize = _ref[1], maxFileSize = _ref[2], maxFilesErrorCallback = _ref[3], minFileSizeErrorCallback = _ref[4], maxFileSizeErrorCallback = _ref[5]; + ref = this.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback']), maxFiles = ref[0], minFileSize = ref[1], maxFileSize = ref[2], maxFilesErrorCallback = ref[3], minFileSizeErrorCallback = ref[4], maxFileSizeErrorCallback = ref[5]; if ((maxFiles != null) && maxFiles < (fileList.length + this.files.length)) { maxFilesErrorCallback(fileList, errorCount++); return false; } files = []; - for (_i = 0, _len = fileList.length; _i < _len; _i++) { - file = fileList[_i]; + for (i = 0, len = fileList.length; i < len; i++) { + file = fileList[i]; file.name = file.fileName = file.name || file.fileName; if ((minFileSize != null) && file.size < minFileSize) { minFileSizeErrorCallback(file, errorCount++); @@ -175,13 +182,13 @@ }; Resumable.prototype.uploadNextChunk = function() { - var chunk, file, found, outstanding, status, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4; + var chunk, file, found, i, j, k, l, len, len1, len2, len3, len4, m, outstanding, ref, ref1, ref2, ref3, ref4, status; console.log("uploadNextChunk"); found = false; if (this.getOpt('prioritizeFirstAndLastChunk')) { - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; if (file.chunks.length && file.chunks[0].status() === 'pending' && file.chunks[0].preprocessState === 0) { file.chunks[0].send(); found = true; @@ -197,12 +204,12 @@ return true; } } - _ref1 = this.files; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - file = _ref1[_j]; - _ref2 = file.chunks; - for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { - chunk = _ref2[_k]; + ref1 = this.files; + for (j = 0, len1 = ref1.length; j < len1; j++) { + file = ref1[j]; + ref2 = file.chunks; + for (k = 0, len2 = ref2.length; k < len2; k++) { + chunk = ref2[k]; if (chunk.status() === 'pending' && chunk.preprocessState === 0) { chunk.send(); found = true; @@ -216,13 +223,13 @@ if (found) { return true; } - _ref3 = this.files; - for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { - file = _ref3[_l]; + ref3 = this.files; + for (l = 0, len3 = ref3.length; l < len3; l++) { + file = ref3[l]; outstanding = false; - _ref4 = file.chunks; - for (_m = 0, _len4 = _ref4.length; _m < _len4; _m++) { - chunk = _ref4[_m]; + ref4 = file.chunks; + for (m = 0, len4 = ref4.length; m < len4; m++) { + chunk = ref4[m]; status = chunk.status(); if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) { outstanding = true; @@ -240,14 +247,13 @@ }; Resumable.prototype.assignBrowse = function(domNodes, isDirectory) { - var changeHandler, dn, input, maxFiles, _i, _len, - _this = this; + var changeHandler, dn, i, input, len, maxFiles; console.log("assignBrowse"); if (domNodes.length == null) { domNodes = [domNodes]; } - for (_i = 0, _len = domNodes.length; _i < _len; _i++) { - dn = domNodes[_i]; + for (i = 0, len = domNodes.length; i < len; i++) { + dn = domNodes[i]; if (dn.tagName === 'INPUT' && dn.type === 'file') { input = dn; } else { @@ -273,52 +279,54 @@ } else { input.removeAttribute('webkitdirectory'); } - changeHandler = function(e) { - _this.appendFilesFromFileList(e.target.files); - return e.target.value = ''; - }; + changeHandler = (function(_this) { + return function(e) { + _this.appendFilesFromFileList(e.target.files); + return e.target.value = ''; + }; + })(this); return input.addEventListener('change', changeHandler, false); }; Resumable.prototype.assignDrop = function(domNodes) { - var dn, _i, _len, _results; + var dn, i, len, results; console.log("assignDrop"); if (domNodes.length == null) { domNodes = [domNodes]; } - _results = []; - for (_i = 0, _len = domNodes.length; _i < _len; _i++) { - dn = domNodes[_i]; + results = []; + for (i = 0, len = domNodes.length; i < len; i++) { + dn = domNodes[i]; dn.addEventListener('dragover', this.onDragOver, false); - _results.push(dn.addEventListener('drop', this.onDrop, false)); + results.push(dn.addEventListener('drop', this.onDrop, false)); } - return _results; + return results; }; Resumable.prototype.unAssignDrop = function(domNodes) { - var dn, _i, _len, _results; + var dn, i, len, results; console.log("unAssignDrop"); if (domNodes.length == null) { domNodes = [domNodes]; } - _results = []; - for (_i = 0, _len = domNodes.length; _i < _len; _i++) { - dn = domNodes[_i]; + results = []; + for (i = 0, len = domNodes.length; i < len; i++) { + dn = domNodes[i]; dn.removeEventListener('dragover', this.onDragOver); - _results.push(dn.removeEventListener('drop', this.onDrop)); + results.push(dn.removeEventListener('drop', this.onDrop)); } - return _results; + return results; }; Resumable.prototype.isUploading = function() { - var chunk, file, uploading, _i, _j, _len, _len1, _ref, _ref1; + var chunk, file, i, j, len, len1, ref, ref1, uploading; uploading = false; - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; - _ref1 = file.chunks; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - chunk = _ref1[_j]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; + ref1 = file.chunks; + for (j = 0, len1 = ref1.length; j < len1; j++) { + chunk = ref1[j]; if (chunk.status() === 'uploading') { uploading = true; break; @@ -332,49 +340,49 @@ }; Resumable.prototype.upload = function() { - var num, _i, _ref, _results; + var i, num, ref, results; console.log("upload"); if (this.isUploading()) { return; } this.fire('uploadStart'); - _results = []; - for (num = _i = 0, _ref = this.getOpt('simultaneousUploads'); 0 <= _ref ? _i <= _ref : _i >= _ref; num = 0 <= _ref ? ++_i : --_i) { - _results.push(this.uploadNextChunk()); + results = []; + for (num = i = 0, ref = this.getOpt('simultaneousUploads'); 0 <= ref ? i <= ref : i >= ref; num = 0 <= ref ? ++i : --i) { + results.push(this.uploadNextChunk()); } - return _results; + return results; }; Resumable.prototype.pause = function() { - var file, _i, _len, _ref; + var file, i, len, ref; console.log("pause"); - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; file.abort(); } return this.fire('pause'); }; Resumable.prototype.cancel = function() { - var file, _i, _len, _ref; + var file, i, len, ref; console.log("cancel"); - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; file.cancel(); } return this.fire('cancel'); }; Resumable.prototype.progress = function() { - var file, totalDone, totalSize, _i, _len, _ref; + var file, i, len, ref, totalDone, totalSize; console.log("progress"); totalDone = 0; totalSize = 0; - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; totalDone += file.progress() * file.size; totalSize += file.size; } @@ -387,12 +395,12 @@ }; Resumable.prototype.removeFile = function(file) { - var f, files, _i, _len, _ref; + var f, files, i, len, ref; console.log("removeFile"); files = []; - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - f = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + f = ref[i]; if (f !== file) { files.push(f); } @@ -401,11 +409,11 @@ }; Resumable.prototype.getFromUniqueIdentifier = function(uniqueIdentifier) { - var f, _i, _len, _ref; + var f, i, len, ref; console.log("getFromUniqueIdentifier"); - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - f = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + f = ref[i]; if (f.uniqueIdentifier === uniqueIdentifier) { return f; } @@ -414,12 +422,12 @@ }; Resumable.prototype.getSize = function() { - var file, totalSize, _i, _len, _ref; + var file, i, len, ref, totalSize; console.log("getSize"); totalSize = 0; - _ref = this.files; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - file = _ref[_i]; + ref = this.files; + for (i = 0, len = ref.length; i < len; i++) { + file = ref[i]; totalSize += file.size; } return totalSize; @@ -430,12 +438,11 @@ })(); window.ResumableChunk = ResumableChunk = (function() { - - function ResumableChunk(resumableObj, fileObj, offset, callback) { + function ResumableChunk(resumableObj, fileObj, offset1, callback1) { this.resumableObj = resumableObj; this.fileObj = fileObj; - this.offset = offset; - this.callback = callback; + this.offset = offset1; + this.callback = callback1; this.opt = {}; this.fileObjSize = this.fileObj.size; this.lastProgressCallback = new Date; @@ -461,20 +468,21 @@ }; ResumableChunk.prototype.test = function() { - var customQuery, headers, key, params, testHandler, value, - _this = this; + var customQuery, headers, key, params, testHandler, value; this.xhr = new XMLHttpRequest(); - testHandler = function(e) { - var status; - _this.tested = true; - status = _this.status(); - if (status === 'success') { - _this.callback(status, _this.message()); - return _this.resumableObj.uploadNextChunk(); - } else { - return _this.send(); - } - }; + testHandler = (function(_this) { + return function(e) { + var status; + _this.tested = true; + status = _this.status(); + if (status === 'success') { + _this.callback(status, _this.message()); + return _this.resumableObj.uploadNextChunk(); + } else { + return _this.send(); + } + }; + })(this); this.xhr.addEventListener('load', testHandler, false); this.xhr.addEventListener('error', testHandler, false); params = []; @@ -488,13 +496,13 @@ pushParams(key, value); } } - this.pushParams(params, 'resumableChunkNumber', this.offset + 1); - this.pushParams(params, 'resumableChunkSize', this.chunkSize); - this.pushParams(params, 'resumableCurrentChunkSize', this.endByte - this.startByte); - this.pushParams(params, 'resumableTotalSize', this.fileObjSize); - this.pushParams(params, 'resumableIdentifier', this.fileObj.uniqueIdentifier); - this.pushParams(params, 'resumableFilename', this.fileObj.fileName); - this.pushParams(params, 'resumableRelativePath', this.fileObj.relativePath); + this.pushParams(params, this.getOpt('chunkNumberParameterName'), this.offset + 1); + this.pushParams(params, this.getOpt('chunkSizeParameterName'), this.chunkSize); + this.pushParams(params, this.getOpt('currentChunkSizeParameterName'), this.endByte - this.startByte); + this.pushParams(params, this.getOpt('totalSizeParameterName'), this.fileObjSize); + this.pushParams(params, this.getOpt('identifierParameterName'), this.fileObj.uniqueIdentifier); + this.pushParams(params, this.getOpt('fileNameParameterName'), this.fileObj.fileName); + this.pushParams(params, this.getOpt('relativePathParameterName'), this.fileObj.relativePath); this.xhr.open('GET', this.getOpt('target') + '?' + params.join('&')); headers = this.getOpt('headers'); if (headers == null) { @@ -513,8 +521,7 @@ }; ResumableChunk.prototype.send = function() { - var bytes, customQuery, data, doneHandler, func, headers, key, params, preprocess, progressHandler, query, ret, target, value, - _this = this; + var bytes, customQuery, data, doneHandler, func, headers, key, params, preprocess, progressHandler, query, ret, target, value; preprocess = this.getOpt('preprocess'); if (typeof preprocess === 'function') { ret = false; @@ -540,31 +547,35 @@ } this.xhr = new XMLHttpRequest(); this.loaded = 0; - progressHandler = function(e) { - if ((new Date) - _this.lastProgressCallback > _this.getOpt('throttleProgressCallbacks') * 1000) { - _this.callback('progress'); - _this.lastProgressCallback = new Date; - } - return _this.loaded = e.loaded || 0; - }; + progressHandler = (function(_this) { + return function(e) { + if ((new Date) - _this.lastProgressCallback > _this.getOpt('throttleProgressCallbacks') * 1000) { + _this.callback('progress'); + _this.lastProgressCallback = new Date; + } + return _this.loaded = e.loaded || 0; + }; + })(this); this.xhr.upload.addEventListener('progress', progressHandler, false); this.callback('progress'); - doneHandler = function(e) { - var retryInterval, status; - status = _this.status(); - if (status === 'success' || status === 'error') { - _this.callback(status, _this.message()); - return _this.resumableObj.uploadNextChunk(); - } else { - _this.callback('retry', _this.message()); - _this.abort(); - _this.retries++; - retryInterval = getOpt('chunkRetryInterval'); - if (retryInterval != null) { - return setTimeout(_this.send, retryInterval); + doneHandler = (function(_this) { + return function(e) { + var retryInterval, status; + status = _this.status(); + if (status === 'success' || status === 'error') { + _this.callback(status, _this.message()); + return _this.resumableObj.uploadNextChunk(); + } else { + _this.callback('retry', _this.message()); + _this.abort(); + _this.retries++; + retryInterval = _this.getOpt('chunkRetryInterval'); + if (retryInterval != null) { + return setTimeout(_this.send, retryInterval); + } } - } - }; + }; + })(this); this.xhr.addEventListener('load', doneHandler, false); this.xhr.addEventListener('error', doneHandler, false); headers = this.getOpt('headers'); @@ -587,15 +598,14 @@ bytes = this.fileObj.file[func](this.startByte, this.endByte); data = null; target = this.getOpt('target'); - query = { - resumableChunkNumber: this.offset + 1, - resumableChunkSize: this.getOpt('chunkSize'), - resumableCurrentChunkSize: this.endByte - this.startByte, - resumableTotalSize: this.fileObjSize, - resumableIdentifier: this.fileObj.uniqueIdentifier, - resumableFilename: this.fileObj.fileName, - resumableRelativePath: this.fileObj.relativePath - }; + query = {}; + query[this.getOpt('chunkNumber')] = this.offset + 1; + query[this.getOpt('chunkSize')] = this.getOpt('chunkSize'); + query[this.getOpt('currentChunkSize')] = this.endByte - this.startByte; + query[this.getOpt('totalSize')] = this.fileObjSize; + query[this.getOpt('identifier')] = this.fileObj.uniqueIdentifier; + query[this.getOpt('filename')] = this.fileObj.fileName; + query[this.getOpt('relativePath')] = this.fileObj.relativePath; customQuery = this.getOpt('query'); if (typeof customQuery === 'function') { customQuery = customQuery(this.fileObj, this); @@ -635,7 +645,7 @@ }; ResumableChunk.prototype.status = function() { - var maxChunkRetries, permanentErrors, _ref; + var maxChunkRetries, permanentErrors, ref; permanentErrors = this.getOpt('permanentErrors'); maxChunkRetries = this.getOpt('maxChunkRetries'); if (permanentErrors == null) { @@ -650,7 +660,7 @@ return 'uploading'; } else if (this.xhr.status === 200) { return 'success'; - } else if ((_ref = this.xhr.status, __indexOf.call(permanentErrors, _ref) >= 0) || (this.retries >= maxChunkRetries)) { + } else if ((ref = this.xhr.status, indexOf.call(permanentErrors, ref) >= 0) || (this.retries >= maxChunkRetries)) { return 'error'; } else { this.abort(); @@ -681,10 +691,9 @@ })(); window.ResumableFile = ResumableFile = (function() { - - function ResumableFile(resumableObj, file) { + function ResumableFile(resumableObj, file1) { this.resumableObj = resumableObj; - this.file = file; + this.file = file1; this.opt = {}; this._prevProgress = 0; this.fileName = this.file.fileName || this.file.name; @@ -723,10 +732,10 @@ }; ResumableFile.prototype.abort = function() { - var c, _i, _len, _ref; - _ref = this.chunks; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - c = _ref[_i]; + var c, i, len, ref; + ref = this.chunks; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; if (c.status() === 'uploading') { c.abort(); } @@ -735,11 +744,11 @@ }; ResumableFile.prototype.cancel = function() { - var c, _chunks, _i, _len; + var _chunks, c, i, len; _chunks = this.chunks; this.chunks = []; - for (_i = 0, _len = _chunks.length; _i < _len; _i++) { - c = _chunks[_i]; + for (i = 0, len = _chunks.length; i < len; i++) { + c = _chunks[i]; if (c.status() === 'uploading') { c.abort(); this.resumableObj.uploadNextChunk(); @@ -755,7 +764,7 @@ }; ResumableFile.prototype.bootstrap = function() { - var max, offset, round, _i, _ref, _results; + var i, max, offset, ref, results, round; this.abort(); this._error = false; this.chunks = []; @@ -767,23 +776,23 @@ } offset = 0; max = Math.max(round(this.file.size / this.getOpt('chunkSize')), 1); - _results = []; - for (offset = _i = 0, _ref = max - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; offset = 0 <= _ref ? ++_i : --_i) { - _results.push(this.chunks.push(new ResumableChunk(this.resumableObj, this, offset, this.chunkEvent))); + results = []; + for (offset = i = 0, ref = max - 1; 0 <= ref ? i <= ref : i >= ref; offset = 0 <= ref ? ++i : --i) { + results.push(this.chunks.push(new ResumableChunk(this.resumableObj, this, offset, this.chunkEvent))); } - return _results; + return results; }; ResumableFile.prototype.progress = function() { - var c, error, ret, _i, _len, _ref; + var c, error, i, len, ref, ret; if (this._error) { return 1.; } ret = 0; error = false; - _ref = this.chunks; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - c = _ref[_i]; + ref = this.chunks; + for (i = 0, len = ref.length; i < len; i++) { + c = ref[i]; error = c.status() === 'error'; ret += c.progress(true); } @@ -798,3 +807,5 @@ })(); }).call(this); + +//# sourceMappingURL=resumable.js.map From f0faf6a4f18a19110a92fe509207ca89b736481a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guzm=C3=A1n=20Fern=C3=A1ndez=20Garc=C3=ADa?= Date: Fri, 1 Apr 2016 10:51:03 +0200 Subject: [PATCH 096/211] Review of new code indentation --- resumable.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/resumable.js b/resumable.js index 4d5fd195..21093c0e 100644 --- a/resumable.js +++ b/resumable.js @@ -39,16 +39,16 @@ forceChunkSize:false, simultaneousUploads:3, fileParameterName:'file', - chunkNumberParameterName: 'resumableChunkNumber', - chunkSizeParameterName: 'resumableChunkSize', - currentChunkSizeParameterName: 'resumableCurrentChunkSize', - totalSizeParameterName: 'resumableTotalSize', - typeParameterName: 'resumableType', - identifierParameterName: 'resumableIdentifier', - fileNameParameterName: 'resumableFilename', - relativePathParameterName: 'resumableRelativePath', - totalChunksParameterName: 'resumableTotalChunks', - throttleProgressCallbacks:0.5, + chunkNumberParameterName: 'resumableChunkNumber', + chunkSizeParameterName: 'resumableChunkSize', + currentChunkSizeParameterName: 'resumableCurrentChunkSize', + totalSizeParameterName: 'resumableTotalSize', + typeParameterName: 'resumableType', + identifierParameterName: 'resumableIdentifier', + fileNameParameterName: 'resumableFilename', + relativePathParameterName: 'resumableRelativePath', + totalChunksParameterName: 'resumableTotalChunks', + throttleProgressCallbacks: 0.5, query:{}, headers:{}, preprocess:null, @@ -634,15 +634,15 @@ params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); }); // Add extra data to identify chunk - params.push([parameterNamespace + $.getOpt('chunkNumberParameterName'), encodeURIComponent($.offset + 1)].join('=')); - params.push([parameterNamespace + $.getOpt('chunkSizeParameterName'), encodeURIComponent($.getOpt('chunkSize'))].join('=')); - params.push([parameterNamespace + $.getOpt('currentChunkSizeParameterName'), encodeURIComponent($.endByte - $.startByte)].join('=')); - params.push([parameterNamespace + $.getOpt('totalSizeParameterName'), encodeURIComponent($.fileObjSize)].join('=')); - params.push([parameterNamespace + $.getOpt('typeParameterName'), encodeURIComponent($.fileObjType)].join('=')); - params.push([parameterNamespace + $.getOpt('identifierParameterName'), encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); - params.push([parameterNamespace + $.getOpt('fileNameParameterName'), encodeURIComponent($.fileObj.fileName)].join('=')); - params.push([parameterNamespace + $.getOpt('relativePathParameterName'), encodeURIComponent($.fileObj.relativePath)].join('=')); - params.push([parameterNamespace + $.getOpt('totalChunksParameterName'), encodeURIComponent($.fileObj.chunks.length)].join('=')); + params.push([parameterNamespace + $.getOpt('chunkNumberParameterName'), encodeURIComponent($.offset + 1)].join('=')); + params.push([parameterNamespace + $.getOpt('chunkSizeParameterName'), encodeURIComponent($.getOpt('chunkSize'))].join('=')); + params.push([parameterNamespace + $.getOpt('currentChunkSizeParameterName'), encodeURIComponent($.endByte - $.startByte)].join('=')); + params.push([parameterNamespace + $.getOpt('totalSizeParameterName'), encodeURIComponent($.fileObjSize)].join('=')); + params.push([parameterNamespace + $.getOpt('typeParameterName'), encodeURIComponent($.fileObjType)].join('=')); + params.push([parameterNamespace + $.getOpt('identifierParameterName'), encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); + params.push([parameterNamespace + $.getOpt('fileNameParameterName'), encodeURIComponent($.fileObj.fileName)].join('=')); + params.push([parameterNamespace + $.getOpt('relativePathParameterName'), encodeURIComponent($.fileObj.relativePath)].join('=')); + params.push([parameterNamespace + $.getOpt('totalChunksParameterName'), encodeURIComponent($.fileObj.chunks.length)].join('=')); // Append the relevant chunk and send it $.xhr.open($.getOpt('testMethod'), $h.getTarget(params)); $.xhr.timeout = $.getOpt('xhrTimeout'); @@ -718,7 +718,7 @@ // Set up the basic query data from Resumable var query = {}; - query[$.getOpt('chunkNumberParameterName')] = $.offset+1; + query[$.getOpt('chunkNumberParameterName')] = $.offset + 1; query[$.getOpt('chunkSizeParameterName')] = $.getOpt('chunkSize'); query[$.getOpt('currentChunkSizeParameterName')] = $.endByte - $.startByte; query[$.getOpt('totalSizeParameterName')] = $.fileObjSize; From 6ded00bdc781b1d1493826b9e826474e13e9041e Mon Sep 17 00:00:00 2001 From: Vaughn Iverson Date: Fri, 8 Apr 2016 15:26:44 -0700 Subject: [PATCH 097/211] Fix typo in latest upstream master that breaks resumableTotalChunks for POST --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 21093c0e..d63fc2e0 100644 --- a/resumable.js +++ b/resumable.js @@ -726,7 +726,7 @@ query[$.getOpt('identifierParameterName')] = $.fileObj.uniqueIdentifier; query[$.getOpt('filenameParameterName')] = $.fileObj.fileName; query[$.getOpt('relativePathParameterName')] = $.fileObj.relativePath; - query[$.getOpt('totalChunksParameterName')] = $.fileObj.chunks.lengt; + query[$.getOpt('totalChunksParameterName')] = $.fileObj.chunks.length; // Mix in custom data var customQuery = $.getOpt('query'); if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); From c9279475e968a6898d9fe9d82ea9ca14bec9a400 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Sat, 9 Apr 2016 12:27:39 +0200 Subject: [PATCH 098/211] Fix parameter typo to resolve #295. --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index d63fc2e0..73b5cb23 100644 --- a/resumable.js +++ b/resumable.js @@ -724,7 +724,7 @@ query[$.getOpt('totalSizeParameterName')] = $.fileObjSize; query[$.getOpt('typeParameterName')] = $.fileObjType; query[$.getOpt('identifierParameterName')] = $.fileObj.uniqueIdentifier; - query[$.getOpt('filenameParameterName')] = $.fileObj.fileName; + query[$.getOpt('fileNameParameterName')] = $.fileObj.fileName; query[$.getOpt('relativePathParameterName')] = $.fileObj.relativePath; query[$.getOpt('totalChunksParameterName')] = $.fileObj.chunks.length; // Mix in custom data From aaa2f94a2442de295579e715f73e2b45a71fb915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guzm=C3=A1n=20Fern=C3=A1ndez=20Garc=C3=ADa?= Date: Tue, 12 Apr 2016 09:09:47 +0200 Subject: [PATCH 099/211] Add .NET sample --- .../DotNET/Controllers/ResumableController.cs | 230 ++++++++++++++++++ .../DotNET/Models/ResumableConfiguration.cs | 37 +++ 2 files changed, 267 insertions(+) create mode 100644 samples/DotNET/Controllers/ResumableController.cs create mode 100644 samples/DotNET/Models/ResumableConfiguration.cs diff --git a/samples/DotNET/Controllers/ResumableController.cs b/samples/DotNET/Controllers/ResumableController.cs new file mode 100644 index 00000000..aa3cfa0c --- /dev/null +++ b/samples/DotNET/Controllers/ResumableController.cs @@ -0,0 +1,230 @@ +using Resumable.Models; +using System; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Web.Http; + +namespace Resumable.Controllers +{ + [RoutePrefix("api/File")] + public class FileUploadController : ApiController + { + protected string root = System.Web.Hosting.HostingEnvironment.MapPath("~/upload"); + + [Route("Upload"), HttpOptions] + public virtual object UploadFileOptions() + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + [Route("Upload"), HttpGet] + public virtual object Upload(int resumableChunkNumber, string resumableIdentifier) + { + return ChunkIsHere(resumableChunkNumber, resumableIdentifier) ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateResponse(HttpStatusCode.NoContent); + } + + [Route("Upload"), HttpPost] + public virtual async Task Upload() + { + // Check if the request contains multipart/form-data. + if (!Request.Content.IsMimeMultipartContent()) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + if (!Directory.Exists(root)) Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + if (await readPart(provider)) + { + // Success + return Request.CreateResponse(HttpStatusCode.OK); + } + else + { + // Fail + var message = DeleteInvalidChunkData(provider) ? "Cannot read multi part file data." : "Cannot delete temporary file chunk data."; + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, new System.Exception(message)); + } + } + + private static bool DeleteInvalidChunkData(MultipartFormDataStreamProvider provider) + { + try + { + var localFileName = provider.FileData[0].LocalFileName; + if (File.Exists(localFileName)) + { + File.Delete(localFileName); + } + return true; + } + catch { + return false; + } + } + + private async Task readPart(MultipartFormDataStreamProvider provider) + { + try + { + await Request.Content.ReadAsMultipartAsync(provider); + ResumableConfiguration configuration = GetUploadConfiguration(provider); + int chunkNumber = GetChunkNumber(provider); + + // Rename generated file + MultipartFileData chunk = provider.FileData[0]; // Only one file in multipart message + RenameChunk(chunk, chunkNumber, configuration.Identifier); + + // Assemble chunks into single file if they're all here + TryAssembleFile(configuration); + return true; + } + catch { + return false; + } + } + + #region Get configuration + + [NonAction] + private ResumableConfiguration GetUploadConfiguration(MultipartFormDataStreamProvider provider) + { + return ResumableConfiguration.Create(identifier: GetId(provider), filename: GetFileName(provider), chunks: GetTotalChunks(provider)); + } + + + [NonAction] + protected virtual string GetFileName(MultipartFormDataStreamProvider provider) + { + var filename = provider.FormData["resumableFilename"]; + return !String.IsNullOrEmpty(filename) ? filename : provider.FileData[0].Headers.ContentDisposition.FileName.Trim('\"'); + } + + [NonAction] + protected virtual string GetId(MultipartFormDataStreamProvider provider) + { + var id = provider.FormData["resumableIdentifier"]; + return !String.IsNullOrEmpty(id) ? id : Guid.NewGuid().ToString(); + } + + [NonAction] + protected virtual int GetTotalChunks(MultipartFormDataStreamProvider provider) + { + var total = provider.FormData["resumableTotalChunks"]; + return !String.IsNullOrEmpty(total) ? Convert.ToInt32(total) : 1; + } + + [NonAction] + protected virtual int GetChunkNumber(MultipartFormDataStreamProvider provider) + { + var chunk = provider.FormData["resumableChunkNumber"]; + return !String.IsNullOrEmpty(chunk) ? Convert.ToInt32(chunk) : 1; + } + + #endregion + + #region Chunk methods + + [NonAction] + protected virtual string GetChunkFileName(int chunkNumber, string identifier) + { + return Path.Combine(root, string.Format("{0}_{1}", identifier, chunkNumber.ToString())); + } + + [NonAction] + protected virtual void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier) + { + string generatedFileName = chunk.LocalFileName; + string chunkFileName = GetChunkFileName(chunkNumber, identifier); + if (File.Exists(chunkFileName)) File.Delete(chunkFileName); + File.Move(generatedFileName, chunkFileName); + + } + + [NonAction] + protected virtual string GetFilePath(ResumableConfiguration configuration) + { + return Path.Combine(root, configuration.Identifier); + } + + [NonAction] + protected virtual bool ChunkIsHere(int chunkNumber, string identifier) + { + string fileName = GetChunkFileName(chunkNumber, identifier); + return File.Exists(fileName); + } + + [NonAction] + protected virtual bool AllChunksAreHere(ResumableConfiguration configuration) + { + for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) + if (!ChunkIsHere(chunkNumber, configuration.Identifier)) return false; + return true; + } + + + [NonAction] + protected virtual void TryAssembleFile(ResumableConfiguration configuration) + { + if (AllChunksAreHere(configuration)) + { + // Create a single file + var path = ConsolidateFile(configuration); + + // Rename consolidated with original name of upload + RenameFile(path, Path.Combine(root, configuration.FileName)); + + // Delete chunk files + DeleteChunks(configuration); + } + } + + [NonAction] + protected void DeleteChunks(ResumableConfiguration configuration) + { + for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) + { + var chunkFileName = GetChunkFileName(chunkNumber, configuration.Identifier); + // FIXME: batch test is throwing an IO error + File.Delete(chunkFileName); + } + } + + + [NonAction] + protected string ConsolidateFile(ResumableConfiguration configuration) + { + var path = GetFilePath(configuration); + + // FIXME: batch test is throwing an IO error + using (var destStream = File.Create(path, 15000)) + { + for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) + { + var chunkFileName = GetChunkFileName(chunkNumber, configuration.Identifier); + using (var sourceStream = File.OpenRead(chunkFileName)) + { + sourceStream.CopyTo(destStream); + } + } + destStream.Close(); + } + + return path; + } + + #endregion + + [NonAction] + private string RenameFile(string sourceName, string targetName) + { + targetName = Path.GetFileName(targetName); // Strip to filename if directory is specified (avoid cross-directory attack) + string realFileName = Path.Combine(root, targetName); + if (File.Exists(realFileName)) File.Delete(realFileName); + File.Move(sourceName, realFileName); + return targetName; + } + } +} \ No newline at end of file diff --git a/samples/DotNET/Models/ResumableConfiguration.cs b/samples/DotNET/Models/ResumableConfiguration.cs new file mode 100644 index 00000000..034dfbb8 --- /dev/null +++ b/samples/DotNET/Models/ResumableConfiguration.cs @@ -0,0 +1,37 @@ +namespace Resumable.Models +{ + public class ResumableConfiguration + { + /// + /// Gets or sets number of expected chunks in this upload. + /// + public int Chunks { get; set; } + + /// + /// Gets or sets unique identifier for current upload. + /// + public string Identifier { get; set; } + + /// + /// Gets or sets file name. + /// + public string FileName { get; set; } + + public ResumableConfiguration() + { + + } + + /// + /// Creates an object with file upload configuration. + /// + /// Upload unique identifier. + /// File name. + /// Number of file chunks. + /// File upload configuration. + public static ResumableConfiguration Create(string identifier, string filename, int chunks) + { + return new ResumableConfiguration { Identifier = identifier, FileName = filename, Chunks = chunks }; + } + } +} \ No newline at end of file From 58e85bfc3bcee89317360b34c69d9392bc826c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guzm=C3=A1n=20Fern=C3=A1ndez=20Garc=C3=ADa?= Date: Tue, 12 Apr 2016 09:17:18 +0200 Subject: [PATCH 100/211] Cleaning code --- samples/DotNET/Controllers/ResumableController.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/samples/DotNET/Controllers/ResumableController.cs b/samples/DotNET/Controllers/ResumableController.cs index aa3cfa0c..8be5563e 100644 --- a/samples/DotNET/Controllers/ResumableController.cs +++ b/samples/DotNET/Controllers/ResumableController.cs @@ -94,7 +94,6 @@ private ResumableConfiguration GetUploadConfiguration(MultipartFormDataStreamPro return ResumableConfiguration.Create(identifier: GetId(provider), filename: GetFileName(provider), chunks: GetTotalChunks(provider)); } - [NonAction] protected virtual string GetFileName(MultipartFormDataStreamProvider provider) { @@ -163,8 +162,7 @@ protected virtual bool AllChunksAreHere(ResumableConfiguration configuration) if (!ChunkIsHere(chunkNumber, configuration.Identifier)) return false; return true; } - - + [NonAction] protected virtual void TryAssembleFile(ResumableConfiguration configuration) { @@ -187,18 +185,14 @@ protected void DeleteChunks(ResumableConfiguration configuration) for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) { var chunkFileName = GetChunkFileName(chunkNumber, configuration.Identifier); - // FIXME: batch test is throwing an IO error File.Delete(chunkFileName); } } - - + [NonAction] protected string ConsolidateFile(ResumableConfiguration configuration) { var path = GetFilePath(configuration); - - // FIXME: batch test is throwing an IO error using (var destStream = File.Create(path, 15000)) { for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) From e5e78c11915570f8c03b13d6c2e0c07ad80c180e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guzm=C3=A1n=20Fern=C3=A1ndez=20Garc=C3=ADa?= Date: Tue, 12 Apr 2016 09:18:53 +0200 Subject: [PATCH 101/211] Refactor sample code --- .../DotNET/Controllers/ResumableController.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/samples/DotNET/Controllers/ResumableController.cs b/samples/DotNET/Controllers/ResumableController.cs index 8be5563e..d590ab3f 100644 --- a/samples/DotNET/Controllers/ResumableController.cs +++ b/samples/DotNET/Controllers/ResumableController.cs @@ -11,22 +11,22 @@ namespace Resumable.Controllers [RoutePrefix("api/File")] public class FileUploadController : ApiController { - protected string root = System.Web.Hosting.HostingEnvironment.MapPath("~/upload"); + private string root = System.Web.Hosting.HostingEnvironment.MapPath("~/upload"); [Route("Upload"), HttpOptions] - public virtual object UploadFileOptions() + public object UploadFileOptions() { return Request.CreateResponse(HttpStatusCode.OK); } [Route("Upload"), HttpGet] - public virtual object Upload(int resumableChunkNumber, string resumableIdentifier) + public object Upload(int resumableChunkNumber, string resumableIdentifier) { return ChunkIsHere(resumableChunkNumber, resumableIdentifier) ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateResponse(HttpStatusCode.NoContent); } [Route("Upload"), HttpPost] - public virtual async Task Upload() + public async Task Upload() { // Check if the request contains multipart/form-data. if (!Request.Content.IsMimeMultipartContent()) @@ -95,28 +95,28 @@ private ResumableConfiguration GetUploadConfiguration(MultipartFormDataStreamPro } [NonAction] - protected virtual string GetFileName(MultipartFormDataStreamProvider provider) + private string GetFileName(MultipartFormDataStreamProvider provider) { var filename = provider.FormData["resumableFilename"]; return !String.IsNullOrEmpty(filename) ? filename : provider.FileData[0].Headers.ContentDisposition.FileName.Trim('\"'); } [NonAction] - protected virtual string GetId(MultipartFormDataStreamProvider provider) + private string GetId(MultipartFormDataStreamProvider provider) { var id = provider.FormData["resumableIdentifier"]; return !String.IsNullOrEmpty(id) ? id : Guid.NewGuid().ToString(); } [NonAction] - protected virtual int GetTotalChunks(MultipartFormDataStreamProvider provider) + private int GetTotalChunks(MultipartFormDataStreamProvider provider) { var total = provider.FormData["resumableTotalChunks"]; return !String.IsNullOrEmpty(total) ? Convert.ToInt32(total) : 1; } [NonAction] - protected virtual int GetChunkNumber(MultipartFormDataStreamProvider provider) + private int GetChunkNumber(MultipartFormDataStreamProvider provider) { var chunk = provider.FormData["resumableChunkNumber"]; return !String.IsNullOrEmpty(chunk) ? Convert.ToInt32(chunk) : 1; @@ -127,13 +127,13 @@ protected virtual int GetChunkNumber(MultipartFormDataStreamProvider provider) #region Chunk methods [NonAction] - protected virtual string GetChunkFileName(int chunkNumber, string identifier) + private string GetChunkFileName(int chunkNumber, string identifier) { return Path.Combine(root, string.Format("{0}_{1}", identifier, chunkNumber.ToString())); } [NonAction] - protected virtual void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier) + private void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier) { string generatedFileName = chunk.LocalFileName; string chunkFileName = GetChunkFileName(chunkNumber, identifier); @@ -143,20 +143,20 @@ protected virtual void RenameChunk(MultipartFileData chunk, int chunkNumber, str } [NonAction] - protected virtual string GetFilePath(ResumableConfiguration configuration) + private string GetFilePath(ResumableConfiguration configuration) { return Path.Combine(root, configuration.Identifier); } [NonAction] - protected virtual bool ChunkIsHere(int chunkNumber, string identifier) + private bool ChunkIsHere(int chunkNumber, string identifier) { string fileName = GetChunkFileName(chunkNumber, identifier); return File.Exists(fileName); } [NonAction] - protected virtual bool AllChunksAreHere(ResumableConfiguration configuration) + private bool AllChunksAreHere(ResumableConfiguration configuration) { for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) if (!ChunkIsHere(chunkNumber, configuration.Identifier)) return false; @@ -164,7 +164,7 @@ protected virtual bool AllChunksAreHere(ResumableConfiguration configuration) } [NonAction] - protected virtual void TryAssembleFile(ResumableConfiguration configuration) + private void TryAssembleFile(ResumableConfiguration configuration) { if (AllChunksAreHere(configuration)) { @@ -180,7 +180,7 @@ protected virtual void TryAssembleFile(ResumableConfiguration configuration) } [NonAction] - protected void DeleteChunks(ResumableConfiguration configuration) + private void DeleteChunks(ResumableConfiguration configuration) { for (int chunkNumber = 1; chunkNumber <= configuration.Chunks; chunkNumber++) { @@ -190,7 +190,7 @@ protected void DeleteChunks(ResumableConfiguration configuration) } [NonAction] - protected string ConsolidateFile(ResumableConfiguration configuration) + private string ConsolidateFile(ResumableConfiguration configuration) { var path = GetFilePath(configuration); using (var destStream = File.Create(path, 15000)) From 9c599764675cee88cd423ec9fd7e82dbc339de0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Palmh=C3=B8j?= Date: Tue, 3 May 2016 10:14:08 +0200 Subject: [PATCH 102/211] Update node.js sample app to be compatible with newer node.js versions --- samples/Node.js/README.md | 13 +++++++++---- samples/Node.js/app.js | 29 +++++++++-------------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/samples/Node.js/README.md b/samples/Node.js/README.md index bc6949e1..63df4561 100644 --- a/samples/Node.js/README.md +++ b/samples/Node.js/README.md @@ -1,6 +1,7 @@ # Sample code for Node.js -This sample is written for [Node.js 0.10+](http://nodejs.org/) and requires [Express 4+](http://expressjs.com/) to make the sample code cleaner. +This sample is written for [Node.js 0.10+](http://nodejs.org/) and requires +[Express 4+](http://expressjs.com/) to make the sample code cleaner. To install and run: @@ -10,9 +11,13 @@ To install and run: Then browse to [localhost:3000](http://localhost:3000). - ## Enabling Cross-domain Uploads -If you would like to load the resumable.js library from one domain and have your Node.js reside on another, you must allow 'Access-Control-Allow-Origin' from '*'. Please remember, there are some potential security risks with enabling this functionality. If you would still like to implement cross-domain uploads, open app.js and uncomment lines 24-31 and uncomment line 17. +If you would like to load the resumable.js library from one domain and have your +Node.js reside on another, you must set the header +`Access-Control-Allow-Origin: *`. Please remember, there are some potential +security risks with enabling this functionality. If you would still like to +implement cross-domain uploads, open app.js and uncomment lines 12-15. -Then in public/index.html, on line 49, update the target with your server's address. For example: target:'http://www.example.com/upload' +Then in public/index.html, on line 49, update the target with your servers +address. For example: target:'http://www.example.com/upload' diff --git a/samples/Node.js/app.js b/samples/Node.js/app.js index d004202a..268fb4f0 100644 --- a/samples/Node.js/app.js +++ b/samples/Node.js/app.js @@ -8,39 +8,28 @@ app.use(express.static(__dirname + '/public')); app.use(multipart()); +// Uncomment to allow CORS +// app.use(function (req, res, next) { +// res.header('Access-Control-Allow-Origin', '*'); +// next(); +// }); + // Handle uploads through Resumable.js app.post('/upload', function(req, res){ - - // console.log(req); - resumable.post(req, function(status, filename, original_filename, identifier){ console.log('POST', status, original_filename, identifier); - res.send(status, { - // NOTE: Uncomment this funciton to enable cross-domain request. - //'Access-Control-Allow-Origin': '*' - }); + res.send(status); }); }); -// Handle cross-domain requests -// NOTE: Uncomment this funciton to enable cross-domain request. -/* - app.options('/upload', function(req, res){ - console.log('OPTIONS'); - res.send(true, { - 'Access-Control-Allow-Origin': '*' - }, 200); - }); -*/ - // Handle status checks on chunks through Resumable.js app.get('/upload', function(req, res){ resumable.get(req, function(status, filename, original_filename, identifier){ console.log('GET', status); res.send((status == 'found' ? 200 : 404), status); - }); - }); + }); +}); app.get('/download/:identifier', function(req, res){ resumable.write(req.params.identifier, res); From ad7bea15b62a9e6f71601feb24ece5866c480e22 Mon Sep 17 00:00:00 2001 From: nickdatum Date: Tue, 10 May 2016 08:22:31 +0100 Subject: [PATCH 103/211] Update resumable.js Fixed relative paths submitting incorrectly ref #304 --- resumable.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 73b5cb23..fe92e199 100644 --- a/resumable.js +++ b/resumable.js @@ -34,6 +34,7 @@ // PROPERTIES var $ = this; $.files = []; + $.paths = []; $.defaults = { chunkSize:1*1024*1024, forceChunkSize:false, @@ -280,6 +281,8 @@ updateQueueTotal(-1, queue); } else if (entry.isFile) { + //build paths array, to retrieve later + $.paths.push(entry.fullPath); //this is handling to read an entry object representing a file, parsing the file object is asynchronous which is why we need the queue //currently entry objects will only exist in this flow for Chrome entry.file(function(file) { @@ -400,6 +403,7 @@ function addFile(uniqueIdentifier){ if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ file.uniqueIdentifier = uniqueIdentifier; + file.sendPath = $.paths[pathIndex]; var f = new ResumableFile($, file, uniqueIdentifier); $.files.push(f); files.push(f); @@ -407,7 +411,9 @@ window.setTimeout(function(){ $.fire('fileAdded', f, event) },0); - })()}; + })()} else { + $.paths.splice(pathIndex, 1); + }; } // directories have size == 0 var uniqueIdentifier = $h.generateUniqueIdentifier(file) @@ -992,6 +998,9 @@ if($.files[i] === file) { $.files.splice(i, 1); } + if($.paths[i]) { + $.paths.splice(i, 1); + } } }; $.getFromUniqueIdentifier = function(uniqueIdentifier){ From ab1152eb9f1794fd956cac76bffb597787f215e0 Mon Sep 17 00:00:00 2001 From: nickdatum Date: Tue, 10 May 2016 08:31:40 +0100 Subject: [PATCH 104/211] Update resumable.js --- resumable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/resumable.js b/resumable.js index fe92e199..a5509677 100644 --- a/resumable.js +++ b/resumable.js @@ -401,6 +401,7 @@ } function addFile(uniqueIdentifier){ + var pathIndex = $.files.length ? $.files.length : 0; if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ file.uniqueIdentifier = uniqueIdentifier; file.sendPath = $.paths[pathIndex]; From 46299812e6bfd0908c1db874326fb096e472c4d1 Mon Sep 17 00:00:00 2001 From: nickdatum Date: Tue, 10 May 2016 08:36:43 +0100 Subject: [PATCH 105/211] Update resumable.js --- resumable.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/resumable.js b/resumable.js index a5509677..1ed9b671 100644 --- a/resumable.js +++ b/resumable.js @@ -73,7 +73,7 @@ var maxFiles = $.getOpt('maxFiles'); alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); }, - minFileSize:1, + minFileSize:0, minFileSizeErrorCallback:function(file, errorCount) { alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); }, @@ -281,7 +281,6 @@ updateQueueTotal(-1, queue); } else if (entry.isFile) { - //build paths array, to retrieve later $.paths.push(entry.fullPath); //this is handling to read an entry object representing a file, parsing the file object is asynchronous which is why we need the queue //currently entry objects will only exist in this flow for Chrome @@ -377,19 +376,19 @@ $h.each(fileList, function(file){ var fileName = file.name; if(o.fileType.length > 0){ - var fileTypeFound = false; - for(var index in o.fileType){ - var extension = '.' + o.fileType[index]; - if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ - fileTypeFound = true; - break; - } - } - if (!fileTypeFound) { - o.fileTypeErrorCallback(file, errorCount++); - return false; - } - } + var fileTypeFound = false; + for(var index in o.fileType){ + var extension = '.' + o.fileType[index]; + if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ + fileTypeFound = true; + break; + } + } + if (!fileTypeFound) { + o.fileTypeErrorCallback(file, errorCount++); + return false; + } + } if (typeof(o.minFileSize)!=='undefined' && file.size Date: Tue, 10 May 2016 08:38:34 +0100 Subject: [PATCH 106/211] Update resumable.js --- resumable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index 1ed9b671..9abc463b 100644 --- a/resumable.js +++ b/resumable.js @@ -403,7 +403,7 @@ var pathIndex = $.files.length ? $.files.length : 0; if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ file.uniqueIdentifier = uniqueIdentifier; - file.sendPath = $.paths[pathIndex]; + file.relativePath = $.paths[pathIndex]; var f = new ResumableFile($, file, uniqueIdentifier); $.files.push(f); files.push(f); @@ -445,7 +445,7 @@ $.file = file; $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox $.size = file.size; - $.relativePath = file.sendPath || file.webkitRelativePath || file.relativePath || $.fileName; + $.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; $.uniqueIdentifier = uniqueIdentifier; $._pause = false; $.container = ''; From d0a1e859daf5a0e7ef0a6b24591b58a5af3750f9 Mon Sep 17 00:00:00 2001 From: nickdatum Date: Tue, 10 May 2016 08:43:50 +0100 Subject: [PATCH 107/211] Update resumable.js --- resumable.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/resumable.js b/resumable.js index 9abc463b..34dca2e1 100644 --- a/resumable.js +++ b/resumable.js @@ -376,19 +376,19 @@ $h.each(fileList, function(file){ var fileName = file.name; if(o.fileType.length > 0){ - var fileTypeFound = false; - for(var index in o.fileType){ - var extension = '.' + o.fileType[index]; - if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ - fileTypeFound = true; - break; + var fileTypeFound = false; + for(var index in o.fileType){ + var extension = '.' + o.fileType[index]; + if(fileName.indexOf(extension, fileName.length - extension.length) !== -1){ + fileTypeFound = true; + break; + } + } + if (!fileTypeFound) { + o.fileTypeErrorCallback(file, errorCount++); + return false; + } } - } - if (!fileTypeFound) { - o.fileTypeErrorCallback(file, errorCount++); - return false; - } - } if (typeof(o.minFileSize)!=='undefined' && file.size Date: Tue, 10 May 2016 08:44:43 +0100 Subject: [PATCH 108/211] Update resumable.js --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 34dca2e1..aa1f2c18 100644 --- a/resumable.js +++ b/resumable.js @@ -73,7 +73,7 @@ var maxFiles = $.getOpt('maxFiles'); alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); }, - minFileSize:0, + minFileSize:1, minFileSizeErrorCallback:function(file, errorCount) { alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); }, From 481ab9da804a0940b4a10568cb2cce0ffa122e8e Mon Sep 17 00:00:00 2001 From: nickdatum Date: Wed, 11 May 2016 14:32:20 +0100 Subject: [PATCH 109/211] Update resumable.js Rewrote part of the functionality to improve performance with large number of folders/files. This also fixed the paths being incorrect. --- resumable.js | 185 +++++++++++++++------------------------------------ 1 file changed, 54 insertions(+), 131 deletions(-) diff --git a/resumable.js b/resumable.js index aa1f2c18..c0b79d68 100644 --- a/resumable.js +++ b/resumable.js @@ -34,7 +34,6 @@ // PROPERTIES var $ = this; $.files = []; - $.paths = []; $.defaults = { chunkSize:1*1024*1024, forceChunkSize:false, @@ -217,145 +216,75 @@ e.preventDefault(); }; - // INTERNAL METHODS (both handy and responsible for the heavy load) - /** - * @summary This function loops over the files passed in from a drag and drop operation and gets them ready for appendFilesFromFileList - * It attempts to use FileSystem API calls to extract files and subfolders if the dropped items include folders - * That capability is only currently available in Chrome, but if it isn't available it will just pass the items along to - * appendFilesFromFileList (via enqueueFileAddition to help with asynchronous processing.) - * @param files {Array} - the File or Entry objects to be processed depending on your browser support - * @param event {Object} - the drop event object - * @param [queue] {Object} - an object to keep track of our progress processing the dropped items - * @param [path] {String} - the relative path from the originally selected folder to the current files if extracting files from subfolders - */ - var loadFiles = function (files, event, queue, path){ - //initialize the queue object if it doesn't exist - if (!queue) { - queue = { - total: 0, - files: [], - event: event - }; - } - - //update the total number of things we plan to process - updateQueueTotal(files.length, queue); - - //loop over all the passed in objects checking if they are files or folders - for (var i = 0; i < files.length; i++) { - var file = files[i]; - var entry, reader; - - if (file.isFile || file.isDirectory) { - //this is an object we can handle below with no extra work needed up front - entry = file; - } - else if (file.getAsEntry) { - //get the file as an entry object if we can using the proposed HTML5 api (unlikely to get implemented by anyone) - entry = file.getAsEntry(); - } - else if (file.webkitGetAsEntry) { - //get the file as an entry object if we can using the Chrome specific webkit implementation - entry = file.webkitGetAsEntry(); - } - else if (typeof file.getAsFile === 'function') { - //if this is still a DataTransferItem object, get it as a file object - enqueueFileAddition(file.getAsFile(), queue, path); - //we just added this file object to the queue so we can go to the next object in the loop and skip the processing below - continue; - } - else if (File && file instanceof File) { - //this is already a file object so just queue it up and move on - enqueueFileAddition(file, queue, path); - //we just added this file object to the queue so we can go to the next object in the loop and skip the processing below - continue; - } - else { - //we can't do anything with this object, decrement the expected total and skip the processing below - updateQueueTotal(-1, queue); - continue; - } + var queueLength = 0; - if (!entry) { - //there isn't anything we can do with this so decrement the total expected - updateQueueTotal(-1, queue); - } - else if (entry.isFile) { - $.paths.push(entry.fullPath); - //this is handling to read an entry object representing a file, parsing the file object is asynchronous which is why we need the queue - //currently entry objects will only exist in this flow for Chrome - entry.file(function(file) { - enqueueFileAddition(file, queue, path); - }, function(err) { - console.warn(err); - }); - } - else if (entry.isDirectory) { - //this is handling to read an entry object representing a folder, parsing the directory object is asynchronous which is why we need the queue - //currently entry objects will only exist in this flow for Chrome - reader = entry.createReader(); + var loadFiles = function (items, event) { + $.fire('beforeAdd'); - var newEntries = []; - //wrap the callback in another function so we can store the path in a closure - var readDir = function(path){ - reader.readEntries( - //success callback: read entries out of the directory - function(entries){ - if (entries.length>0){ - //add these results to the array of all the new stuff - for (var i=0; i 0) { + for (i = 0; i < entries.length; i++) { + entry = entries[i]; + if (entry.isFile) { + queueLength++; + entry.file(function(file) { + file.relativePath = '' + path + '/' + file.name; + return enqueueFileAddition(file, event); + }); + } else if (entry.isDirectory) { + processDirectory(entry, '' + path + '/' + entry.name, event); + } + } + readEntries(); + } + return null; + }); + }; + })(this); - // If all the files we expect have shown up, then flush the queue. - if (queue.files.length === queue.total) { - appendFilesFromFileList(queue.files, queue.event); - } + return readEntries(); }; + var queueFiles = []; /** * @summary Add a file to the queue of processed files, if it brings the total up to the expected total, flush the queue * @param file {Object} - File object to be passed along to appendFilesFromFileList eventually - * @param queue {Object} - an object to keep track of our progress processing the dropped items * @param [path] {String} - the file's relative path from the originally dropped folder if we are parsing folder content (Chrome only for now) */ - var enqueueFileAddition = function(file, queue, path) { - //store the path to this file if it came in as part of a folder - if (path) file.relativePath = path + '/' + file.name; - queue.files.push(file); + var enqueueFileAddition = function(file, event){ + if (!file.relativePath) file.fullPath; + queueFiles.push(file); // If all the files we expect have shown up, then flush the queue. - if (queue.files.length === queue.total) { - appendFilesFromFileList(queue.files, queue.event); + if (queueFiles.length == queueLength) { + appendFilesFromFileList(queueFiles, event); } }; @@ -403,7 +332,6 @@ var pathIndex = $.files.length ? $.files.length : 0; if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ file.uniqueIdentifier = uniqueIdentifier; - file.relativePath = $.paths[pathIndex]; var f = new ResumableFile($, file, uniqueIdentifier); $.files.push(f); files.push(f); @@ -411,9 +339,7 @@ window.setTimeout(function(){ $.fire('fileAdded', f, event) },0); - })()} else { - $.paths.splice(pathIndex, 1); - }; + })()}; } // directories have size == 0 var uniqueIdentifier = $h.generateUniqueIdentifier(file) @@ -998,9 +924,6 @@ if($.files[i] === file) { $.files.splice(i, 1); } - if($.paths[i]) { - $.paths.splice(i, 1); - } } }; $.getFromUniqueIdentifier = function(uniqueIdentifier){ From 6a7ab19f858bd350bcb58d9abd3768fd6d1a5782 Mon Sep 17 00:00:00 2001 From: nickdatum Date: Wed, 11 May 2016 15:10:44 +0100 Subject: [PATCH 110/211] Update resumable.js Added decrementing of queue increase of entry error --- resumable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resumable.js b/resumable.js index c0b79d68..f94ae244 100644 --- a/resumable.js +++ b/resumable.js @@ -257,6 +257,8 @@ entry.file(function(file) { file.relativePath = '' + path + '/' + file.name; return enqueueFileAddition(file, event); + }, function() { // Error + queueLength--; }); } else if (entry.isDirectory) { processDirectory(entry, '' + path + '/' + entry.name, event); @@ -281,6 +283,7 @@ var enqueueFileAddition = function(file, event){ if (!file.relativePath) file.fullPath; queueFiles.push(file); + console.log(queueFiles.length + '-' + queueLength); // If all the files we expect have shown up, then flush the queue. if (queueFiles.length == queueLength) { From f48a5ef8c56176a4a7c43e5e16fc2867eadf45fe Mon Sep 17 00:00:00 2001 From: nickdatum Date: Wed, 11 May 2016 15:22:22 +0100 Subject: [PATCH 111/211] Update resumable.js Added prepending forward slash to relativePath --- resumable.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resumable.js b/resumable.js index f94ae244..fc315858 100644 --- a/resumable.js +++ b/resumable.js @@ -255,13 +255,13 @@ if (entry.isFile) { queueLength++; entry.file(function(file) { - file.relativePath = '' + path + '/' + file.name; + file.relativePath = '/' + path + '/' + file.name; return enqueueFileAddition(file, event); }, function() { // Error queueLength--; }); } else if (entry.isDirectory) { - processDirectory(entry, '' + path + '/' + entry.name, event); + processDirectory(entry, path + '/' + entry.name, event); } } readEntries(); @@ -283,7 +283,6 @@ var enqueueFileAddition = function(file, event){ if (!file.relativePath) file.fullPath; queueFiles.push(file); - console.log(queueFiles.length + '-' + queueLength); // If all the files we expect have shown up, then flush the queue. if (queueFiles.length == queueLength) { From 8d0b57bcd100f80ed51afdc967d731d4f76f5519 Mon Sep 17 00:00:00 2001 From: Adam Zmenak Date: Fri, 24 Jun 2016 12:31:57 -0400 Subject: [PATCH 112/211] Use application/octet-stream for content type fixes #360 --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index fc315858..a58f37d9 100644 --- a/resumable.js +++ b/resumable.js @@ -694,7 +694,7 @@ var method = $.getOpt('uploadMethod'); $.xhr.open(method, target); if ($.getOpt('method') === 'octet') { - $.xhr.setRequestHeader('Content-Type', 'binary/octet-stream'); + $.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); } $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); From 1bd80f1e4cfb868b398d483baa29c56b1b6008d5 Mon Sep 17 00:00:00 2001 From: Adam Zmenak Date: Mon, 27 Jun 2016 13:41:15 -0400 Subject: [PATCH 113/211] Add message argument to docs for fileSuccess event --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 960ffd7b..14c8a95a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Available configuration options are: #### Events -* `.fileSuccess(file)` A specific file was completed. +* `.fileSuccess(file, message)` A specific file was completed. `message` is the response body from the server. * `.fileProgress(file)` Uploading progressed for a specific file. * `.fileAdded(file, event)` A new file was added. Optionally, you can use the browser `event` object from when the file was added. * `.filesAdded(array)` New files were added. From e7062261a075e3cae4d13c1a879bba89a33caf02 Mon Sep 17 00:00:00 2001 From: Richard Gass Date: Wed, 24 Aug 2016 10:27:08 -0700 Subject: [PATCH 114/211] Add public method updateQuery which allows you to update the url after initialization. Useful when you want to grab more information before you actually start uploading content. --- resumable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resumable.js b/resumable.js index a58f37d9..9c9b8dc9 100644 --- a/resumable.js +++ b/resumable.js @@ -949,6 +949,9 @@ appendFilesFromFileList(e.target.files, e); e.target.value = ''; }; + $.updateQuery = function(query){ + $.opts.query = query; + }; return(this); }; From 4f6b3d1ea4184d5963ba2f66b02b8edbc5f730bd Mon Sep 17 00:00:00 2001 From: Alessio Gaeta Date: Thu, 29 Jan 2015 16:19:51 +0100 Subject: [PATCH 115/211] Added the list of skipped files as filesAdded callback parameter. That way it is possible to handle the files skipped because already uploaded (i.e. notifying the user instead of skipping them silently). --- README.md | 2 +- resumable.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 14c8a95a..c170c1d6 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Available configuration options are: * `.fileSuccess(file, message)` A specific file was completed. `message` is the response body from the server. * `.fileProgress(file)` Uploading progressed for a specific file. * `.fileAdded(file, event)` A new file was added. Optionally, you can use the browser `event` object from when the file was added. -* `.filesAdded(array)` New files were added. +* `.filesAdded(arrayAdded, arraySkipped)` New files were added (and maybe some have been skipped). * `.fileRetry(file)` Something went wrong during upload of a specific file, uploading is being retried. * `.fileError(file, message)` An error occurred during upload of a specific file. * `.uploadStart()` Upload has been started on the Resumable object. diff --git a/resumable.js b/resumable.js index a58f37d9..c79e452b 100644 --- a/resumable.js +++ b/resumable.js @@ -113,8 +113,8 @@ // EVENTS // catchAll(event, ...) - // fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file), fileError(file, message), - // complete(), progress(), error(message, file), pause() + // 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); @@ -303,7 +303,7 @@ return false; } } - var files = []; + var files = [], filesSkipped = []; $h.each(fileList, function(file){ var fileName = file.name; if(o.fileType.length > 0){ @@ -341,7 +341,9 @@ window.setTimeout(function(){ $.fire('fileAdded', f, event) },0); - })()}; + })()} else { + filesSkipped.push(f); + }; } // directories have size == 0 var uniqueIdentifier = $h.generateUniqueIdentifier(file) @@ -359,7 +361,7 @@ }); window.setTimeout(function(){ - $.fire('filesAdded', files) + $.fire('filesAdded', files, filesSkipped); },0); }; From 11b87e3622d05eed89febe04cd92ee990bd32fd3 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Wed, 2 Nov 2016 21:46:28 +0100 Subject: [PATCH 116/211] Promise support for generateUniqueIdentifier added - renamed done() and fail() parameter of optional async generateUniqueIdentifier result to then() in order to support ES6 Promises - fix: addFile() isn't called when async generateUniqueIdentifier call fails - 'filesAdded' event is now fired after all (sync and async) identifiers are generated - 'filesAdded' event is only fired if at least one file was generated synchronously or async generation succeeded - updated Readme to reflect changes - node.js example extended to include async demonstration --- README.md | 35 ++++--- resumable.js | 38 +++++--- samples/Node.js/app.js | 14 +++ samples/Node.js/public/index-async.html | 119 ++++++++++++++++++++++++ 4 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 samples/Node.js/public/index-async.html diff --git a/README.md b/README.md index c170c1d6..ed1f870e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## What is Resumable.js -Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the [`HTML5 File API`](http://www.w3.org/TR/FileAPI/). +Resumable.js is a JavaScript library providing multiple simultaneous, stable and resumable uploads via the [`HTML5 File API`](http://www.w3.org/TR/FileAPI/). The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each file into small chunks. Then, 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, resume and even recover uploads without losing state because only the currently uploading chunks will be aborted, not the entire upload. @@ -14,12 +14,12 @@ Samples and examples are available in the `samples/` folder. Please push your ow A new `Resumable` object is created with information of what and where to post: var r = new Resumable({ - target:'/api/photo/redeem-upload-token', + target:'/api/photo/redeem-upload-token', query:{upload_token:'my_token'} }); // Resumable.js isn't supported, fall back on a different method if(!r.support) location.href = '/some-old-crappy-uploader'; - + To allow files to be selected and drag-dropped, you need to assign a drop target and a DOM item to be clicked for browsing: r.assignBrowse(document.getElementById('browseButton')); @@ -56,7 +56,7 @@ You should allow for the same chunk to be uploaded more than once; this isn't st For every request, you can confirm reception in HTTP status codes (can be change through the `permanentErrors` option): * `200`: The chunk was accepted and correct. No need to re-upload. -* `404`, `415`. `500`, `501`: The file for which the chunk was uploaded is not supported, cancel the entire upload. +* `404`, `415`. `500`, `501`: The file for which the chunk was uploaded is not supported, cancel the entire upload. * _Anything else_: Something went wrong, but try reuploading the file. ## Handling GET (or `test()` requests) @@ -76,7 +76,7 @@ After this is done and `testChunks` enabled, an upload can quickly catch up even The object is loaded with a configuration hash: var r = new Resumable({opt1:'val', ...}); - + Available configuration options are: * `target` The target URL for the multipart POST request. This can be a `string` or a `function` that allows you you to construct and return a value, based on supplied `params`. (Default: `/`) @@ -84,15 +84,15 @@ Available configuration options are: * `forceChunkSize` Force all chunks to be less or equal than chunkSize. Otherwise, the last chunk will be greater than or equal to `chunkSize`. (Default: `false`) * `simultaneousUploads` Number of simultaneous uploads (Default: `3`) * `fileParameterName` The name of the multipart POST parameter to use for the file chunk (Default: `file`) -* `chunkNumberParameterName` The name of the chunk index (base-1) in the current upload POST parameter to use for the file chunk (Default: `resumableChunkNumber`) -* `totalChunksParameterName` The name of the total number of chunks POST parameter to use for the file chunk (Default: `resumableTotalChunks`) -* `chunkSizeParameterName` The name of the general chunk size POST parameter to use for the file chunk (Default: `resumableChunkSize`) -* `totalSizeParameterName` The name of the total file size number POST parameter to use for the file chunk (Default: `resumableTotalSize`) -* `identifierParameterName` The name of the unique identifier POST parameter to use for the file chunk (Default: `resumableIdentifier`) -* `fileNameParameterName` The name of the original file name POST parameter to use for the file chunk (Default: `resumableFilename`) -* `relativePathParameterName` The name of the file's relative path POST parameter to use for the file chunk (Default: `resumableRelativePath`) -* `currentChunkSizeParameterName` The name of the current chunk size POST parameter to use for the file chunk (Default: `resumableCurrentChunkSize`) -* `typeParameterName` The name of the file type POST parameter to use for the file chunk (Default: `resumableType`) +* `chunkNumberParameterName` The name of the chunk index (base-1) in the current upload POST parameter to use for the file chunk (Default: `resumableChunkNumber`) +* `totalChunksParameterName` The name of the total number of chunks POST parameter to use for the file chunk (Default: `resumableTotalChunks`) +* `chunkSizeParameterName` The name of the general chunk size POST parameter to use for the file chunk (Default: `resumableChunkSize`) +* `totalSizeParameterName` The name of the total file size number POST parameter to use for the file chunk (Default: `resumableTotalSize`) +* `identifierParameterName` The name of the unique identifier POST parameter to use for the file chunk (Default: `resumableIdentifier`) +* `fileNameParameterName` The name of the original file name POST parameter to use for the file chunk (Default: `resumableFilename`) +* `relativePathParameterName` The name of the file's relative path POST parameter to use for the file chunk (Default: `resumableRelativePath`) +* `currentChunkSizeParameterName` The name of the current chunk size POST parameter to use for the file chunk (Default: `resumableCurrentChunkSize`) +* `typeParameterName` The name of the file type POST parameter to use for the file chunk (Default: `resumableType`) * `query` Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: `{}`) * `testMethod` Method for chunk test request. (Default: `'GET'`) * `uploadMethod` Method for chunk upload request. (Default: `'POST'`) @@ -102,7 +102,7 @@ Available configuration options are: * `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`) * `testChunks` Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: `true`) * `preprocess` Optional function to process each chunk before testing & sending. Function is passed the chunk as parameter, and should call the `preprocessFinished` method on the chunk when finished. (Default: `null`) -* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. (Default: `null`) +* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. May return [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)-like object with `then()` method for asynchronous id generation (Default: `null`) * `maxFiles` Indicates how many files can be uploaded in a single session. Valid values are any positive integer and `undefined` for no limit. (Default: `undefined`) * `maxFilesErrorCallback(files, errorCount)` A function which displays the *please upload n file(s) at a time* message. (Default: displays an alert box with the message *Please n one file(s) at a time.*) * `minFileSize` The minimum allowed file size. (Default: `undefined`) @@ -175,11 +175,10 @@ Available configuration options are: * `.retry()` Retry uploading the file. * `.bootstrap()` Rebuild the state of a `ResumableFile` object, including reassigning chunks and XMLHttpRequest instances. * `.isUploading()` Returns a boolean indicating whether file chunks is uploading. -* `.isComplete()` Returns a boolean indicating whether the file has completed uploading and received a server response. +* `.isComplete()` Returns a boolean indicating whether the file has completed uploading and received a server response. ## Alternatives -This library is explicitly designed for modern browsers supporting advanced HTML5 file features, and the motivation has been to provide stable and resumable support for large files (allowing uploads of several GB files through HTTP in a predictable fashion). +This library is explicitly designed for modern browsers supporting advanced HTML5 file features, and the motivation has been to provide stable and resumable support for large files (allowing uploads of several GB files through HTTP in a predictable fashion). If your aim is just to support progress indications during upload/uploading multiple files at once, Resumable.js isn't for you. In those cases, [SWFUpload](http://swfupload.org/) and [Plupload](http://plupload.com/) provides the same features with wider browser support. - diff --git a/resumable.js b/resumable.js index c79e452b..4930030f 100644 --- a/resumable.js +++ b/resumable.js @@ -303,7 +303,19 @@ return false; } } - var files = [], filesSkipped = []; + 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; if(o.fileType.length > 0){ @@ -344,25 +356,29 @@ })()} else { filesSkipped.push(f); }; + decreaseReamining(); } // directories have size == 0 var uniqueIdentifier = $h.generateUniqueIdentifier(file) - if(uniqueIdentifier && typeof uniqueIdentifier.done === 'function' && typeof uniqueIdentifier.fail === 'function'){ + if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ + // Promise or Promise-like object provided as unique identifier uniqueIdentifier - .done(function(uniqueIdentifier){ + .then( + function(uniqueIdentifier){ + // unique identifier generation succeeded addFile(uniqueIdentifier); - }) - .fail(function(){ - addFile(); - }); + }, + function(){ + // unique identifier generation failed + // skip further processing, only decrease file count + decreaseReamining(); + } + ); }else{ + // non-Promise provided as unique identifier, process synchronously addFile(uniqueIdentifier); } - }); - window.setTimeout(function(){ - $.fire('filesAdded', files, filesSkipped); - },0); }; // INTERNAL OBJECT TYPES diff --git a/samples/Node.js/app.js b/samples/Node.js/app.js index 268fb4f0..6ad4b264 100644 --- a/samples/Node.js/app.js +++ b/samples/Node.js/app.js @@ -2,6 +2,7 @@ var express = require('express'); var resumable = require('./resumable-node.js')('/tmp/resumable.js/'); var app = express(); var multipart = require('connect-multiparty'); +var crypto = require('crypto'); // Host most stuff in the public folder app.use(express.static(__dirname + '/public')); @@ -14,6 +15,19 @@ app.use(multipart()); // next(); // }); +// retrieve file id. invoke with /fileid?filename=my-file.jpg +app.get('/fileid', function(req, res){ + if(!req.query.filename){ + return res.status(500).end('query parameter missing'); + } + // create md5 hash from filename + res.end( + crypto.createHash('md5') + .update(req.query.filename) + .digest('hex') + ); +}); + // Handle uploads through Resumable.js app.post('/upload', function(req, res){ resumable.post(req, function(status, filename, original_filename, identifier){ diff --git a/samples/Node.js/public/index-async.html b/samples/Node.js/public/index-async.html new file mode 100644 index 00000000..55f6645e --- /dev/null +++ b/samples/Node.js/public/index-async.html @@ -0,0 +1,119 @@ + + + + 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 with async id generation

+ + + +
+ 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 6856014557ce8a9a56002c9ef6b8f0392d6a91e5 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Thu, 3 Nov 2016 10:25:43 +0100 Subject: [PATCH 117/211] jQuery: check and switch '$' -> 'jQuery' - existence of jQuery is checked before it is used - if not found a descriptive Error is thrown - 'jQuery' global is used instead of '$' shorthand form - allows to use resumable.js in environments where '$' global is reserved (e.g. Microsoft SharePoint) --- resumable.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resumable.js b/resumable.js index c79e452b..42ac0c92 100644 --- a/resumable.js +++ b/resumable.js @@ -5,9 +5,13 @@ * Steffen Tiedemann Christensen, steffen@23company.com */ -(function(){ +(function($){ "use strict"; + if(!$){ + throw new Error("jQuery is not defined but required by resumable.js"); + } + var Resumable = function(opts){ if ( !(this instanceof Resumable) ) { return new Resumable(opts); @@ -969,4 +973,4 @@ window.Resumable = Resumable; } -})(); +})(window.jQuery); From 18ff74ca89415e1ebd7d889a7d6f567942ef4871 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Tue, 8 Nov 2016 13:29:26 +0100 Subject: [PATCH 118/211] parameters can now be disabled if a parameter name is set to a falsy value, it is not included in the requests (test and upload) anymore. --- README.md | 2 ++ resumable.js | 64 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ed1f870e..c5a0258f 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ The object is loaded with a configuration hash: var r = new Resumable({opt1:'val', ...}); +All POST parameters can be omitted by setting them to a falsy value +(e.g. `null`, `false` or empty string). Available configuration options are: * `target` The target URL for the multipart POST request. This can be a `string` or a `function` that allows you you to construct and return a value, based on supplied `params`. (Default: `/`) diff --git a/resumable.js b/resumable.js index 0aa6a7c8..07f3e71c 100644 --- a/resumable.js +++ b/resumable.js @@ -590,15 +590,31 @@ params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); }); // Add extra data to identify chunk - params.push([parameterNamespace + $.getOpt('chunkNumberParameterName'), encodeURIComponent($.offset + 1)].join('=')); - params.push([parameterNamespace + $.getOpt('chunkSizeParameterName'), encodeURIComponent($.getOpt('chunkSize'))].join('=')); - params.push([parameterNamespace + $.getOpt('currentChunkSizeParameterName'), encodeURIComponent($.endByte - $.startByte)].join('=')); - params.push([parameterNamespace + $.getOpt('totalSizeParameterName'), encodeURIComponent($.fileObjSize)].join('=')); - params.push([parameterNamespace + $.getOpt('typeParameterName'), encodeURIComponent($.fileObjType)].join('=')); - params.push([parameterNamespace + $.getOpt('identifierParameterName'), encodeURIComponent($.fileObj.uniqueIdentifier)].join('=')); - params.push([parameterNamespace + $.getOpt('fileNameParameterName'), encodeURIComponent($.fileObj.fileName)].join('=')); - params.push([parameterNamespace + $.getOpt('relativePathParameterName'), encodeURIComponent($.fileObj.relativePath)].join('=')); - params.push([parameterNamespace + $.getOpt('totalChunksParameterName'), encodeURIComponent($.fileObj.chunks.length)].join('=')); + 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(params)); $.xhr.timeout = $.getOpt('xhrTimeout'); @@ -673,16 +689,26 @@ $.xhr.addEventListener('timeout', doneHandler, false); // Set up the basic query data from Resumable - var query = {}; - query[$.getOpt('chunkNumberParameterName')] = $.offset + 1; - query[$.getOpt('chunkSizeParameterName')] = $.getOpt('chunkSize'); - query[$.getOpt('currentChunkSizeParameterName')] = $.endByte - $.startByte; - query[$.getOpt('totalSizeParameterName')] = $.fileObjSize; - query[$.getOpt('typeParameterName')] = $.fileObjType; - query[$.getOpt('identifierParameterName')] = $.fileObj.uniqueIdentifier; - query[$.getOpt('fileNameParameterName')] = $.fileObj.fileName; - query[$.getOpt('relativePathParameterName')] = $.fileObj.relativePath; - query[$.getOpt('totalChunksParameterName')] = $.fileObj.chunks.length; + 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, $); From 6017619e7d993ff38b3d464f8a78d847c9b13eb0 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Wed, 9 Nov 2016 10:50:38 +0100 Subject: [PATCH 119/211] fixed skipping files files which have already been uploaded (i.e. a file with the same id already exists) should be skipped. because a non-existing variable was used, trying to upload the same file again resulted in a 'variable f is not defined' error. this commit fixes the error by using the proper 'file' variable --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 0aa6a7c8..6da8c799 100644 --- a/resumable.js +++ b/resumable.js @@ -358,7 +358,7 @@ $.fire('fileAdded', f, event) },0); })()} else { - filesSkipped.push(f); + filesSkipped.push(file); }; decreaseReamining(); } From 3caace1a9e3ca35e48abb5d3a234231a977efc20 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Wed, 9 Nov 2016 11:00:48 +0100 Subject: [PATCH 120/211] file event is passed to unique id generator the optional function which generates a unique file id now gets passed the file event (i.e. DragEvent or ChangeEvent) as second parameter. This is quite handy if a single resumblejs instance is used for different drop / select zones. --- README.md | 3 ++- resumable.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ed1f870e..6a1fcbdc 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,8 @@ Available configuration options are: * `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`) * `testChunks` Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: `true`) * `preprocess` Optional function to process each chunk before testing & sending. Function is passed the chunk as parameter, and should call the `preprocessFinished` method on the chunk when finished. (Default: `null`) -* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. May return [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)-like object with `then()` method for asynchronous id generation (Default: `null`) +* `generateUniqueIdentifier(file, event)` Override the function that generates unique identifiers for each file. May return [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)-like object with `then()` method for asynchronous id generation. Parameters are the ES `File` object and the event that led to +adding the file. (Default: `null`) * `maxFiles` Indicates how many files can be uploaded in a single session. Valid values are any positive integer and `undefined` for no limit. (Default: `undefined`) * `maxFilesErrorCallback(files, errorCount)` A function which displays the *please upload n file(s) at a time* message. (Default: displays an alert box with the message *Please n one file(s) at a time.*) * `minFileSize` The minimum allowed file size. (Default: `undefined`) diff --git a/resumable.js b/resumable.js index 0aa6a7c8..7c1b0f85 100644 --- a/resumable.js +++ b/resumable.js @@ -157,10 +157,10 @@ } } }, - generateUniqueIdentifier:function(file){ + generateUniqueIdentifier:function(file, event){ var custom = $.getOpt('generateUniqueIdentifier'); if(typeof custom === 'function') { - return custom(file); + return custom(file, event); } var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox var size = file.size; @@ -363,7 +363,7 @@ decreaseReamining(); } // directories have size == 0 - var uniqueIdentifier = $h.generateUniqueIdentifier(file) + var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ // Promise or Promise-like object provided as unique identifier uniqueIdentifier From 5cad80763ecc8a8fdd63bde75102bf28117c1faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Fagerstr=C3=B6m=20Christensen?= Date: Thu, 10 Nov 2016 00:33:14 +0100 Subject: [PATCH 121/211] file-path access doesn't do anything . Close 23/resumable.js#347. --- resumable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/resumable.js b/resumable.js index 0aa6a7c8..3498ee84 100644 --- a/resumable.js +++ b/resumable.js @@ -285,7 +285,6 @@ * @param [path] {String} - the file's relative path from the originally dropped folder if we are parsing folder content (Chrome only for now) */ var enqueueFileAddition = function(file, event){ - if (!file.relativePath) file.fullPath; queueFiles.push(file); // If all the files we expect have shown up, then flush the queue. From 9a09f67253f7ce4d1561a984c2406304e12ea971 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Mon, 7 Nov 2016 15:09:57 +0100 Subject: [PATCH 122/211] reset filequeue after processing file queue was not reseted after all queued files have been processed (even though there was a comment claminig it was done) --- resumable.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resumable.js b/resumable.js index dd513b96..491297ab 100644 --- a/resumable.js +++ b/resumable.js @@ -289,7 +289,11 @@ // If all the files we expect have shown up, then flush the queue. if (queueFiles.length == queueLength) { + // process files appendFilesFromFileList(queueFiles, event); + // flush the queue + queueFiles = []; + queueLength = 0; } }; From 7d166238e49216335c408ae87eec9f4cb8340115 Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Thu, 10 Nov 2016 11:05:59 +0100 Subject: [PATCH 123/211] replaced queue with cps-style async processing queue with separate length and content was error-prone (especially when multiple concurrent operations occured) and introduced globals, which impeded code readability. the workaround introduced callback-based async processing and created a new function for dedicated item processing (instead of doing this inline). --- resumable.js | 163 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 67 deletions(-) diff --git a/resumable.js b/resumable.js index 491297ab..3a545791 100644 --- a/resumable.js +++ b/resumable.js @@ -220,81 +220,110 @@ e.preventDefault(); }; - var queueLength = 0; + /** + * 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; + } + 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(); + item.relativePath = path + item.name; + items.push(item); + } + cb(); // indicate processing is done + } - var loadFiles = function (items, event) { - $.fire('beforeAdd'); - var entry, item, i, ii; - for (i = 0, ii = items.length; i < ii; i++) { - item = items[i]; - if ((item.webkitGetAsEntry != null) && (entry = item.webkitGetAsEntry())) { - if (entry.isFile) { - queueLength++; - enqueueFileAddition(item.getAsFile(), event); - } else if (entry.isDirectory) { - processDirectory(entry, entry.name, event); - } - } else if (item.getAsFile != null) { - if ((item.kind == null) || item.kind === 'file') { - queueLength++; - enqueueFileAddition(item.getAsFile(), event); - } - } + /** + * 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(); } - }; - - var processDirectory = function (directory, path, event) { - var dirReader, readEntries; - - dirReader = directory.createReader(); - - readEntries = (function() { - return function() { - return dirReader.readEntries(function(entries) { - var entry, i; - if (entries.length > 0) { - for (i = 0; i < entries.length; i++) { - entry = entries[i]; - if (entry.isFile) { - queueLength++; - entry.file(function(file) { - file.relativePath = '/' + path + '/' + file.name; - return enqueueFileAddition(file, event); - }, function() { // Error - queueLength--; - }); - } else if (entry.isDirectory) { - processDirectory(entry, path + '/' + entry.name, event); - } - } - readEntries(); - } - return null; - }); - }; - })(this); + // invoke current function, pass the next part as continuation + items[0](function(){ + processCallbacks(items.slice(1), cb); + }); + } - return readEntries(); - }; + /** + * 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(); + dirReader.readEntries(function(entries){ + if(!entries.length){ + // empty directory, skip + return cb(); + } + // process all conversion callbacks, finally invoke own one + processCallbacks( + entries.map(function(entry){ + // bind all properties except for callback + return processItem.bind(null, entry, path, items); + }), + cb + ); + }); + } - var queueFiles = []; /** - * @summary Add a file to the queue of processed files, if it brings the total up to the expected total, flush the queue - * @param file {Object} - File object to be passed along to appendFilesFromFileList eventually - * @param [path] {String} - the file's relative path from the originally dropped folder if we are parsing folder content (Chrome only for now) + * process items to extract files to be uploaded + * @param {File[]} items items to process + * @param {Event} event event that led to upload */ - var enqueueFileAddition = function(file, event){ - queueFiles.push(file); - - // If all the files we expect have shown up, then flush the queue. - if (queueFiles.length == queueLength) { - // process files - appendFilesFromFileList(queueFiles, event); - // flush the queue - queueFiles = []; - queueLength = 0; + 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 + return processItem.bind(null, item, "", files); + }), + function(){ + if(files.length){ + // at least one file found + appendFilesFromFileList(files, event); + } + } + ); }; var appendFilesFromFileList = function(fileList, event){ From eb732b0ea8e3bb608af17040de92ff4562b5072a Mon Sep 17 00:00:00 2001 From: "Mahlon E. Smith" Date: Tue, 15 Nov 2016 00:02:11 -0800 Subject: [PATCH 124/211] Re-add Firefox > 46 support. --- resumable.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resumable.js b/resumable.js index 3a545791..1895edd7 100644 --- a/resumable.js +++ b/resumable.js @@ -239,6 +239,8 @@ }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 From b5592d6ab1ac77b811881a39d84dc38188254d96 Mon Sep 17 00:00:00 2001 From: Steffen Tiedemann Christensen Date: Mon, 5 Dec 2016 22:56:42 +0100 Subject: [PATCH 125/211] Resolve #344 --- resumable.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/resumable.js b/resumable.js index 1895edd7..c7a54f35 100644 --- a/resumable.js +++ b/resumable.js @@ -5,13 +5,9 @@ * Steffen Tiedemann Christensen, steffen@23company.com */ -(function($){ +(function(){ "use strict"; - if(!$){ - throw new Error("jQuery is not defined but required by resumable.js"); - } - var Resumable = function(opts){ if ( !(this instanceof Resumable) ) { return new Resumable(opts); @@ -1049,4 +1045,4 @@ window.Resumable = Resumable; } -})(window.jQuery); +})(); From be53c7837701f52c8fd2c372e0c006bbd5233118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Fagerstr=C3=B6m=20Christensen?= Date: Mon, 16 Jan 2017 01:17:46 +0100 Subject: [PATCH 126/211] Fixed merge typo --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index c7a54f35..2b344e51 100644 --- a/resumable.js +++ b/resumable.js @@ -341,7 +341,7 @@ var decreaseReamining = function(){ if(!--remaining){ // all files processed, trigger event - if(!files.length && !filesSkipped.length){ + if(!files.length && !filesSkipped.length){ // no succeeded files, just skip return; } From 55d27b85d3f361246cee9244d3c54ff681023523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Fagerstr=C3=B6m=20Christensen?= Date: Mon, 16 Jan 2017 01:19:06 +0100 Subject: [PATCH 127/211] Changed the default value for maxChunkRetries from undefined to 100, to avoid some common infinite loop cases (ie on ERR_CONNECTION_REFUSED). --- resumable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resumable.js b/resumable.js index 2b344e51..e2222c18 100644 --- a/resumable.js +++ b/resumable.js @@ -61,7 +61,7 @@ testChunks:true, generateUniqueIdentifier:null, getTarget:null, - maxChunkRetries:undefined, + maxChunkRetries:100, chunkRetryInterval:undefined, permanentErrors:[400, 404, 415, 500, 501], maxFiles:undefined, From acd48d8a9d0231921c239f844031121ca9ec2688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Fagerstr=C3=B6m=20Christensen?= Date: Mon, 16 Jan 2017 01:41:39 +0100 Subject: [PATCH 128/211] Updated test file to list 'fileRetry' string in its console.log message --- test.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.html b/test.html index 7720836c..fbe4fe85 100644 --- a/test.html +++ b/test.html @@ -23,7 +23,7 @@ console.debug('filesAdded', array); }); r.on('fileRetry', function(file){ - console.debug(file); + console.debug('fileRetry', file); }); r.on('fileError', function(file, message){ console.debug('fileError', file, message); From ba1d29df691607a600348777438f06a3c1f98690 Mon Sep 17 00:00:00 2001 From: Aleksandr Davydov Date: Sun, 4 Dec 2016 00:39:26 +0200 Subject: [PATCH 129/211] Remove unusable variable --- resumable.js | 1 - 1 file changed, 1 deletion(-) diff --git a/resumable.js b/resumable.js index c7a54f35..e1308586 100644 --- a/resumable.js +++ b/resumable.js @@ -377,7 +377,6 @@ } function addFile(uniqueIdentifier){ - var pathIndex = $.files.length ? $.files.length : 0; if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ file.uniqueIdentifier = uniqueIdentifier; var f = new ResumableFile($, file, uniqueIdentifier); From a94184644ffc60a90b1ee63b76a97cbab7051fac Mon Sep 17 00:00:00 2001 From: Aleksandr Davydov Date: Fri, 6 Jan 2017 18:34:31 +0200 Subject: [PATCH 130/211] Add editor config --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..dc6cb8bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] + +# Change these settings to your own preference +indent_style = space +indent_size = 2 + +# We recommend you to keep these unchanged +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true From 720a8bd4f6208ea2c5b2d851179d5b9f0297ae11 Mon Sep 17 00:00:00 2001 From: Aleksandr Davydov Date: Fri, 6 Jan 2017 18:41:20 +0200 Subject: [PATCH 131/211] Add testTarget - the target url for the GET request to see if it already exists --- README.md | 11 ++++++----- resumable.js | 48 ++++++++++++++++++++++++++++-------------------- test.html | 5 +++-- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 7cd51572..e3922e20 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Most of the magic for Resumable.js happens in the user's browser, but files stil To handle the state of upload chunks, a number of extra parameters are sent along with all requests: * `resumableChunkNumber`: The index of the chunk in the current upload. First chunk is `1` (no base-0 counting here). -* `resumableTotalChunks`: The total number of chunks. +* `resumableTotalChunks`: The total number of chunks. * `resumableChunkSize`: The general chunk size. Using this value and `resumableTotalSize` you can calculate the total number of chunks. Please note that the size of the data received in the HTTP might be lower than `resumableChunkSize` of this for the last chunk for a file. * `resumableTotalSize`: The total file size. * `resumableIdentifier`: A unique identifier for the file contained in the request. @@ -82,10 +82,11 @@ All POST parameters can be omitted by setting them to a falsy value Available configuration options are: * `target` The target URL for the multipart POST request. This can be a `string` or a `function` that allows you you to construct and return a value, based on supplied `params`. (Default: `/`) +* `testTarget` The target URL for the GET request to the server for each chunk to see if it already exists. This can be a `string` or a `function` that allows you you to construct and return a value, based on supplied `params`. (Default: `target`) * `chunkSize` The size in bytes of each uploaded chunk of data. The last uploaded chunk will be at least this size and up to two the size, see [Issue #51](https://github.com/23/resumable.js/issues/51) for details and reasons. (Default: `1*1024*1024`) * `forceChunkSize` Force all chunks to be less or equal than chunkSize. Otherwise, the last chunk will be greater than or equal to `chunkSize`. (Default: `false`) * `simultaneousUploads` Number of simultaneous uploads (Default: `3`) -* `fileParameterName` The name of the multipart POST parameter to use for the file chunk (Default: `file`) +* `fileParameterName` The name of the multipart request parameter to use for the file chunk (Default: `file`) * `chunkNumberParameterName` The name of the chunk index (base-1) in the current upload POST parameter to use for the file chunk (Default: `resumableChunkNumber`) * `totalChunksParameterName` The name of the total number of chunks POST parameter to use for the file chunk (Default: `resumableTotalChunks`) * `chunkSizeParameterName` The name of the general chunk size POST parameter to use for the file chunk (Default: `resumableChunkSize`) @@ -95,12 +96,12 @@ Available configuration options are: * `relativePathParameterName` The name of the file's relative path POST parameter to use for the file chunk (Default: `resumableRelativePath`) * `currentChunkSizeParameterName` The name of the current chunk size POST parameter to use for the file chunk (Default: `resumableCurrentChunkSize`) * `typeParameterName` The name of the file type POST parameter to use for the file chunk (Default: `resumableType`) -* `query` Extra parameters to include in the multipart POST with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: `{}`) +* `query` Extra parameters to include in the multipart request with data. This can be an object or a function. If a function, it will be passed a ResumableFile and a ResumableChunk object (Default: `{}`) * `testMethod` Method for chunk test request. (Default: `'GET'`) -* `uploadMethod` Method for chunk upload request. (Default: `'POST'`) +* `uploadMethod` HTTP method to use when sending chunks to the server (`POST`, `PUT`, `PATCH`) (Default: `POST`) * `parameterNamespace` Extra prefix added before the name of each parameter included in the multipart POST or in the test GET. (Default: `''`) * `headers` Extra headers to include in the multipart POST with data. This can be an `object` or a `function` that allows you to construct and return a value, based on supplied `file` (Default: `{}`) -* `method` Method to use when POSTing chunks to the server (`multipart` or `octet`) (Default: `multipart`) +* `method` Method to use when sending chunks to the server (`multipart` or `octet`) (Default: `multipart`) * `prioritizeFirstAndLastChunk` Prioritize first and last chunks of all files. This can be handy if you can determine if a file is valid for your service from only the first or last chunk. For example, photo or video meta data is usually located in the first part of a file, making it easy to test support from only the first chunk. (Default: `false`) * `testChunks` Make a GET request to the server for each chunks to see if it already exists. If implemented on the server-side, this will allow for upload resumes even after a browser crash or even a computer restart. (Default: `true`) * `preprocess` Optional function to process each chunk before testing & sending. Function is passed the chunk as parameter, and should call the `preprocessFinished` method on the chunk when finished. (Default: `null`) diff --git a/resumable.js b/resumable.js index e1308586..743d1c84 100644 --- a/resumable.js +++ b/resumable.js @@ -57,6 +57,7 @@ testMethod: 'GET', prioritizeFirstAndLastChunk:false, target:'/', + testTarget:'/', parameterNamespace:'', testChunks:true, generateUniqueIdentifier:null, @@ -186,17 +187,21 @@ return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB'; } }, - getTarget:function(params){ + getTarget:function(request, params){ var target = $.getOpt('target'); - if(typeof target === 'function') { - return target(params); + + if (request === 'test') { + target = $.getOpt('testTarget') === '/' ? $.getOpt('target') : $.getOpt('testTarget'); } - if(target.indexOf('?') < 0) { - target += '?'; - } else { - target += '&'; + + if (typeof target === 'function') { + return target(params); } - return target + params.join('&'); + + var separator = target.indexOf('?') < 0 ? '?' : '&'; + var joinedParams = params.join('&'); + + return target + separator + joinedParams; } }; @@ -645,7 +650,7 @@ }) ); // Append the relevant chunk and send it - $.xhr.open($.getOpt('testMethod'), $h.getTarget(params)); + $.xhr.open($.getOpt('testMethod'), $h.getTarget('test', params)); $.xhr.timeout = $.getOpt('xhrTimeout'); $.xhr.withCredentials = $.getOpt('withCredentials'); // Add data from header options @@ -745,30 +750,31 @@ query[k] = v; }); - var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))), - bytes = $.fileObj.file[func]($.startByte,$.endByte), - data = null, - target = $.getOpt('target'); + var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))); + var bytes = $.fileObj.file[func]($.startByte, $.endByte); + var data = null; + var params = []; var parameterNamespace = $.getOpt('parameterNamespace'); if ($.getOpt('method') === 'octet') { // Add data from the query options data = bytes; - var params = []; - $h.each(query, function(k,v){ - params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); + $h.each(query, function (k, v) { + params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); }); - target = $h.getTarget(params); } else { // Add data from the query options data = new FormData(); - $h.each(query, function(k,v){ - data.append(parameterNamespace+k,v); + $h.each(query, function (k, v) { + data.append(parameterNamespace + k, v); + params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); }); - data.append(parameterNamespace+$.getOpt('fileParameterName'), bytes); + data.append(parameterNamespace + $.getOpt('fileParameterName'), 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'); @@ -780,9 +786,11 @@ if(typeof customHeaders === 'function') { customHeaders = customHeaders($.fileObj, $); } + $h.each(customHeaders, function(k,v) { $.xhr.setRequestHeader(k, v); }); + $.xhr.send(data); }; $.abort = function(){ diff --git a/test.html b/test.html index 7720836c..f630f280 100644 --- a/test.html +++ b/test.html @@ -3,9 +3,10 @@