@@ -62,6 +62,7 @@
Demo
r.assignDrop($('.flow-drop')[0]);
r.assignBrowse($('.flow-browse')[0]);
r.assignBrowse($('.flow-browse-folder')[0], true);
+ r.assignBrowse($('.flow-browse-image')[0], false, false, {accept: 'image/*'});
// Handle file add event
r.on('fileAdded', function(file){
@@ -72,7 +73,10 @@
Demo
'
' +
'Uploading ' +
' ' +
- '' +
+ ' ' +
+ '' +
+ 'Download' +
+ ' ' +
'' +
'
' +
'' +
@@ -86,6 +90,7 @@ Demo
var $self = $('.flow-file-'+file.uniqueIdentifier);
$self.find('.flow-file-name').text(file.name);
$self.find('.flow-file-size').text(readablizeBytes(file.size));
+ $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).hide();
$self.find('.flow-file-pause').on('click', function () {
file.pause();
$self.find('.flow-file-pause').hide();
@@ -109,12 +114,11 @@ Demo
$('.flow-progress .progress-resume-link, .flow-progress .progress-pause-link').hide();
});
r.on('fileSuccess', function(file,message){
+ var $self = $('.flow-file-'+file.uniqueIdentifier);
// Reflect that the file upload has completed
- $('.flow-file-'+file.uniqueIdentifier+' .flow-file-progress')
- .text('(completed)');
- $('.flow-file-'+file.uniqueIdentifier+'')
- .find('.flow-file-pause, .flow-file-resume')
- .remove();
+ $self.find('.flow-file-progress').text('(completed)');
+ $self.find('.flow-file-pause, .flow-file-resume').remove();
+ $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).show();
});
r.on('fileError', function(file, message){
// Reflect that the file upload has resulted in error
diff --git a/samples/Ruby backend in Sinatra.md b/samples/Ruby backend in Sinatra.md
new file mode 100644
index 00000000..9b102a5a
--- /dev/null
+++ b/samples/Ruby backend in Sinatra.md
@@ -0,0 +1,141 @@
+# Ruby backend in Sinatra
+
+@rmontgomery429 has provided this sample implementation in ruby.
+
+1. This is constructed here as a modular sinatra app but you app does not necessarily need to be modular.
+2. I've included the use of the sinatra-cross_origin gem which we required for our use case. Your use case may be different and this may not be required.
+3. I have not tested this specific gist of the app, but we do have a version of this tested and working in production.
+4. This solution does not take into account any kind of file.io race conditions or any other permissions issues.
+5. I provided this as a reference example not as copy/paste production ready code. Your mileage may vary. :)
+
+The basic idea is that you capture chunks of files, save them as part1, part2, partN, and when you've recieved all the files you combine them into the final single file.
+
+```ruby
+##
+# Gemfile
+gem 'sinatra', '~> 1.4.5'
+gem 'sinatra-cross_origin', '~> 0.3.1'
+
+##
+# config.ru
+require 'sinatra'
+set :root, File.dirname(__FILE__)
+
+require './flow_app'
+require './flow_controller'
+
+get '/' do
+ 404
+end
+
+run Rack::URLMap.new(
+ "/" => Sinatra::Application,
+ "/flow" => FlowApp.new,
+)
+
+##
+# flow_app.rb
+class FlowApp < Sinatra::Base
+ register Sinatra::CrossOrigin
+
+ get "/" do
+ cross_origin
+ FlowController.new(params).get
+ end
+
+ post "/" do
+ cross_origin
+ FlowController.new(params).post!
+ end
+
+ options "/" do
+ cross_origin
+ 200
+ end
+end
+
+##
+# flow_controller.rb
+class FlowController
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ def get
+ File.exists?(chunk_file_path) ? 200 : 204
+ end
+
+ def post!
+ save_file!
+ combine_file! if last_chunk?
+ 200
+ rescue
+ 500
+ end
+
+private
+
+ ##
+ # Move the temporary Sinatra upload to the chunk file location
+ def save_file!
+ # Ensure required paths exist
+ FileUtils.mkpath chunk_file_directory
+ # Move the temporary file upload to the temporary chunk file path
+ FileUtils.mv params['file'][:tempfile], chunk_file_path, force: true
+ end
+
+ ##
+ # Determine if this is the last chunk based on the chunk number.
+ def last_chunk?
+ params[:flowChunkNumber].to_i == params[:flowTotalChunks].to_i
+ end
+
+ ##
+ # ./tmp/flow/abc-123/upload.txt.part1
+ def chunk_file_path
+ File.join(chunk_file_directory, "#{params[:flowFilename]}.part#{params[:flowChunkNumber]}")
+ end
+
+ ##
+ # ./tmp/flow/abc-123
+ def chunk_file_directory
+ File.join "tmp", "flow", params[:flowIdentifier]
+ end
+
+ ##
+ # Build final file
+ def combine_file!
+ # Ensure required paths exist
+ FileUtils.mkpath final_file_directory
+ # Open final file in append mode
+ File.open(final_file_path, "a") do |f|
+ file_chunks.each do |file_chunk_path|
+ # Write each chunk to the permanent file
+ f.write File.read(file_chunk_path)
+ end
+ end
+ # Cleanup chunk file directory and all chunk files
+ FileUtils.rm_rf chunk_file_directory
+ end
+
+ ##
+ # /final/resting/place/upload.txt
+ def final_file_path
+ File.join final_file_directory, params[:flowFilename]
+ end
+
+ ##
+ # /final/resting/place
+ def final_file_directory
+ File.join "", "final", "resting", "place"
+ end
+
+ ##
+ # Get all file chunks sorted by cardinality of their part number
+ def file_chunks
+ Dir["#{chunk_file_directory}/*.part*"].sort_by {|f| f.split(".part")[1].to_i }
+ end
+end
+```
diff --git a/samples/java/README.md b/samples/java/README.md
index f4cf53a3..5c4f2431 100644
--- a/samples/java/README.md
+++ b/samples/java/README.md
@@ -1,3 +1,6 @@
+# Other JAVA demos
+https://github.com/jdc18/ng-flow-with-java
+
## Java Demo for Resumable.js
This sample might be outdated, note that resumable.js was renamed to flow.js.
diff --git a/src/flow.js b/src/flow.js
index d84ef03f..aceab424 100644
--- a/src/flow.js
+++ b/src/flow.js
@@ -2,12 +2,17 @@
* @license MIT
*/
(function(window, document, undefined) {'use strict';
-
+ if (!window || !document) {
+ console.warn('Flowjs needs window and document objects to work');
+ return;
+ }
+ // ie10+
+ var ie10plus = window.navigator.msPointerEnabled;
/**
* Flow.js is a library providing multiple simultaneous, stable and
* resumable uploads via the HTML5 File API.
* @param [opts]
- * @param {number} [opts.chunkSize]
+ * @param {number|Function} [opts.chunkSize]
* @param {bool} [opts.forceChunkSize]
* @param {number} [opts.simultaneousUploads]
* @param {bool} [opts.singleFile]
@@ -15,15 +20,21 @@
* @param {number} [opts.progressCallbacksInterval]
* @param {number} [opts.speedSmoothingFactor]
* @param {Object|Function} [opts.query]
- * @param {Object} [opts.headers]
+ * @param {Object|Function} [opts.headers]
* @param {bool} [opts.withCredentials]
* @param {Function} [opts.preprocess]
* @param {string} [opts.method]
+ * @param {string|Function} [opts.testMethod]
+ * @param {string|Function} [opts.uploadMethod]
* @param {bool} [opts.prioritizeFirstAndLastChunk]
- * @param {string} [opts.target]
+ * @param {bool} [opts.allowDuplicateUploads]
+ * @param {string|Function} [opts.target]
* @param {number} [opts.maxChunkRetries]
* @param {number} [opts.chunkRetryInterval]
* @param {Array.} [opts.permanentErrors]
+ * @param {Array.} [opts.successStatuses]
+ * @param {Function} [opts.initFileFn]
+ * @param {Function} [opts.readFileFn]
* @param {Function} [opts.generateUniqueIdentifier]
* @constructor
*/
@@ -50,7 +61,16 @@
* Check if directory upload is supported
* @type {boolean}
*/
- this.supportDirectory = /WebKit/.test(window.navigator.userAgent);
+ var tmpDirTestInput = document.createElement('input');
+ if ('webkitdirectory' in tmpDirTestInput
+ || 'mozdirectory' in tmpDirTestInput
+ || 'odirectory' in tmpDirTestInput
+ || 'msdirectory' in tmpDirTestInput
+ || 'directory' in tmpDirTestInput) {
+ this.supportDirectory = true;
+ } else {
+ this.supportDirectory = false;
+ }
/**
* List of FlowFile objects
@@ -74,14 +94,22 @@
headers: {},
withCredentials: false,
preprocess: null,
+ changeRawDataBeforeSend: null,
method: 'multipart',
+ testMethod: 'GET',
+ uploadMethod: 'POST',
prioritizeFirstAndLastChunk: false,
+ allowDuplicateUploads: false,
target: '/',
testChunks: true,
generateUniqueIdentifier: null,
maxChunkRetries: 0,
chunkRetryInterval: null,
- permanentErrors: [404, 415, 500, 501]
+ permanentErrors: [404, 413, 415, 500, 501],
+ successStatuses: [200, 201, 202],
+ onDropStopPropagation: false,
+ initFileFn: null,
+ readFileFn: webAPIFileRead
};
/**
@@ -106,7 +134,9 @@
* @param {MouseEvent} event
*/
this.onDrop = function (event) {
- event.stopPropagation();
+ if ($.opts.onDropStopPropagation) {
+ event.stopPropagation();
+ }
event.preventDefault();
var dataTransfer = event.dataTransfer;
if (dataTransfer.items && dataTransfer.items[0] &&
@@ -132,14 +162,15 @@
* @type {Object}
*/
this.opts = Flow.extend({}, this.defaults, opts || {});
+
}
Flow.prototype = {
/**
* Set a callback for an event, possible events:
* fileSuccess(file), fileProgress(file), fileAdded(file, event),
- * fileRetry(file), fileError(file, message), complete(),
- * progress(), error(message, file), pause()
+ * fileRemoved(file), fileRetry(file), fileError(file, message),
+ * complete(), progress(), error(message, file), pause()
* @function
* @param {string} event
* @param {Function} callback
@@ -189,7 +220,7 @@
if (this.events.hasOwnProperty(event)) {
each(this.events[event], function (callback) {
preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault;
- });
+ }, this);
}
if (event != 'catchall') {
args.unshift('catchAll');
@@ -216,22 +247,28 @@
// due to a bug in Chrome's File System API impl - #149735
fileReadSuccess(item.getAsFile(), entry.fullPath);
} else {
- entry.createReader().readEntries(readSuccess, readError);
+ readDirectory(entry.createReader());
}
});
- function readSuccess(entries) {
- queue += entries.length;
- each(entries, function(entry) {
- if (entry.isFile) {
- var fullPath = entry.fullPath;
- entry.file(function (file) {
- fileReadSuccess(file, fullPath);
- }, readError);
- } else if (entry.isDirectory) {
- entry.createReader().readEntries(readSuccess, readError);
+ function readDirectory(reader) {
+ reader.readEntries(function (entries) {
+ if (entries.length) {
+ queue += entries.length;
+ each(entries, function(entry) {
+ if (entry.isFile) {
+ var fullPath = entry.fullPath;
+ entry.file(function (file) {
+ fileReadSuccess(file, fullPath);
+ }, readError);
+ } else if (entry.isDirectory) {
+ readDirectory(entry.createReader());
+ }
+ });
+ readDirectory(reader);
+ } else {
+ decrement();
}
- });
- decrement();
+ }, readError);
}
function fileReadSuccess(file, fullPath) {
// relative path should not start with "/"
@@ -240,6 +277,7 @@
decrement();
}
function readError(fileError) {
+ decrement();
throw fileError;
}
function decrement() {
@@ -279,15 +317,13 @@
if (this.opts.prioritizeFirstAndLastChunk) {
each(this.files, function (file) {
if (!file.paused && file.chunks.length &&
- file.chunks[0].status() === 'pending' &&
- file.chunks[0].preprocessState === 0) {
+ file.chunks[0].status() === 'pending') {
file.chunks[0].send();
found = true;
return false;
}
if (!file.paused && file.chunks.length > 1 &&
- file.chunks[file.chunks.length - 1].status() === 'pending' &&
- file.chunks[0].preprocessState === 0) {
+ file.chunks[file.chunks.length - 1].status() === 'pending') {
file.chunks[file.chunks.length - 1].send();
found = true;
return false;
@@ -302,7 +338,7 @@
each(this.files, function (file) {
if (!file.paused) {
each(file.chunks, function (chunk) {
- if (chunk.status() === 'pending' && chunk.preprocessState === 0) {
+ if (chunk.status() === 'pending') {
chunk.send();
found = true;
return false;
@@ -327,7 +363,9 @@
});
if (!outstanding && !preventEvents) {
// All chunks have been uploaded, complete
- this.fire('complete');
+ async(function () {
+ this.fire('complete');
+ }, this);
}
return false;
},
@@ -339,17 +377,16 @@
* @param {Element|Array.} domNodes
* @param {boolean} isDirectory Pass in true to allow directories to
* @param {boolean} singleFile prevent multi file upload
+ * @param {Object} attributes set custom attributes:
+ * http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
+ * eg: accept: 'image/*'
* be selected (Chrome only).
*/
- assignBrowse: function (domNodes, isDirectory, singleFile) {
- if (typeof domNodes.length === 'undefined') {
+ assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
+ if (domNodes instanceof Element) {
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)
each(domNodes, function (domNode) {
var input;
if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
@@ -357,29 +394,22 @@
} else {
input = document.createElement('input');
input.setAttribute('type', 'file');
- // input fill entire dom node
- extend(domNode.style, {
- display: 'inline-block',
- position: 'relative',
- overflow: 'hidden',
- verticalAlign: 'top'
- });
- // in Opera only 'browse' button
- // is clickable and it is located at
- // the right side of the input
+ // display:none - not working in opera 12
extend(input.style, {
+ visibility: 'hidden',
position: 'absolute',
- top: 0,
- right: 0,
- fontFamily: 'Arial',
- // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
- fontSize: '118px',
- margin: 0,
- padding: 0,
- opacity: 0,
- cursor: 'pointer'
+ width: '1px',
+ height: '1px'
});
+ // for opera 12 browser, input must be assigned to a document
domNode.appendChild(input);
+ // https://developer.mozilla.org/en/using_files_from_web_applications)
+ // event listener is executed two times
+ // first one - original mouse click event
+ // second - input.click(), input is inside domNode
+ domNode.addEventListener('click', function() {
+ input.click();
+ }, false);
}
if (!this.opts.singleFile && !singleFile) {
input.setAttribute('multiple', 'multiple');
@@ -387,11 +417,16 @@
if (isDirectory) {
input.setAttribute('webkitdirectory', 'webkitdirectory');
}
+ each(attributes, function (value, key) {
+ input.setAttribute(key, value);
+ });
// When new files are added, simply append them to the overall list
var $ = this;
input.addEventListener('change', function (e) {
- $.addFiles(e.target.files, e);
- e.target.value = '';
+ if (e.target.value) {
+ $.addFiles(e.target.files, e);
+ e.target.value = '';
+ }
}, false);
}, this);
},
@@ -445,23 +480,50 @@
return uploading;
},
+ /**
+ * should upload next chunk
+ * @function
+ * @returns {boolean|number}
+ */
+ _shouldUploadNext: function () {
+ var num = 0;
+ var should = true;
+ var simultaneousUploads = this.opts.simultaneousUploads;
+ each(this.files, function (file) {
+ each(file.chunks, function(chunk) {
+ if (chunk.status() === 'uploading') {
+ num++;
+ if (num >= simultaneousUploads) {
+ should = false;
+ return false;
+ }
+ }
+ });
+ });
+ // if should is true then return uploading chunks's length
+ return should && num;
+ },
+
/**
* Start or resume uploading.
* @function
*/
upload: function () {
// Make sure we don't start too many uploads at once
- if (this.isUploading()) {
+ var ret = this._shouldUploadNext();
+ if (ret === false) {
return;
}
// Kick off the queue
this.fire('uploadStart');
var started = false;
- for (var num = 1; num <= this.opts.simultaneousUploads; num++) {
+ for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
started = this.uploadNextChunk(true) || started;
}
if (!started) {
- this.fire('complete');
+ async(function () {
+ this.fire('complete');
+ }, this);
}
},
@@ -471,7 +533,9 @@
*/
resume: function () {
each(this.files, function (file) {
- file.resume();
+ if (!file.isComplete()) {
+ file.resume();
+ }
});
},
@@ -531,13 +595,14 @@
addFiles: function (fileList, event) {
var files = [];
each(fileList, function (file) {
- // Directories have size `0` and name `.`
- // Ignore already added files
- if (!(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.')) &&
- !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file))) {
- var f = new FlowFile(this, file);
- if (this.fire('fileAdded', f, event)) {
- files.push(f);
+ // https://github.com/flowjs/flow.js/issues/55
+ if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
+ var uniqueIdentifier = this.generateUniqueIdentifier(file);
+ if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
+ var f = new FlowFile(this, file, uniqueIdentifier);
+ if (this.fire('fileAdded', f, event)) {
+ files.push(f);
+ }
}
}
}, this);
@@ -548,8 +613,8 @@
}
this.files.push(file);
}, this);
+ this.fire('filesSubmitted', files, event);
}
- this.fire('filesSubmitted', files, event);
},
@@ -563,6 +628,7 @@
if (this.files[i] === file) {
this.files.splice(i, 1);
file.abort();
+ this.fire('fileRemoved', file);
}
}
},
@@ -644,9 +710,10 @@
* @name FlowFile
* @param {Flow} flowObj
* @param {File} file
+ * @param {string} uniqueIdentifier
* @constructor
*/
- function FlowFile(flowObj, file) {
+ function FlowFile(flowObj, file, uniqueIdentifier) {
/**
* Reference to parent Flow instance
@@ -682,7 +749,13 @@
* File unique identifier
* @type {string}
*/
- this.uniqueIdentifier = flowObj.generateUniqueIdentifier(file);
+ this.uniqueIdentifier = (uniqueIdentifier === undefined ? flowObj.generateUniqueIdentifier(file) : uniqueIdentifier);
+
+ /**
+ * Size of Each Chunk
+ * @type {number}
+ */
+ this.chunkSize = 0;
/**
* List of chunks
@@ -761,10 +834,11 @@
* For internal usage only.
* Callback when something happens within the chunk.
* @function
+ * @param {FlowChunk} chunk
* @param {string} event can be 'progress', 'success', 'error' or 'retry'
* @param {string} [message]
*/
- chunkEvent: function (event, message) {
+ chunkEvent: function (chunk, event, message) {
switch (event) {
case 'progress':
if (Date.now() - this._lastProgressCallback <
@@ -772,32 +846,32 @@
break;
}
this.measureSpeed();
- this.flowObj.fire('fileProgress', this);
+ this.flowObj.fire('fileProgress', this, chunk);
this.flowObj.fire('progress');
this._lastProgressCallback = Date.now();
break;
case 'error':
this.error = true;
this.abort(true);
- this.flowObj.fire('fileError', this, message);
- this.flowObj.fire('error', message, this);
+ this.flowObj.fire('fileError', this, message, chunk);
+ this.flowObj.fire('error', message, this, chunk);
break;
case 'success':
if (this.error) {
return;
}
this.measureSpeed();
- this.flowObj.fire('fileProgress', this);
+ this.flowObj.fire('fileProgress', this, chunk);
this.flowObj.fire('progress');
this._lastProgressCallback = Date.now();
if (this.isComplete()) {
this.currentSpeed = 0;
this.averageSpeed = 0;
- this.flowObj.fire('fileSuccess', this, message);
+ this.flowObj.fire('fileSuccess', this, message, chunk);
}
break;
case 'retry':
- this.flowObj.fire('fileRetry', this);
+ this.flowObj.fire('fileRetry', this, chunk);
break;
}
},
@@ -861,13 +935,25 @@
* @function
*/
bootstrap: function () {
+ if (typeof this.flowObj.opts.initFileFn === "function") {
+ var ret = this.flowObj.opts.initFileFn(this);
+ if (ret && 'then' in ret) {
+ ret.then(this._bootstrap.bind(this));
+ return;
+ }
+ }
+ this._bootstrap();
+ },
+
+ _bootstrap: function () {
this.abort(true);
this.error = false;
// Rebuild stack of chunks from file
this._prevProgress = 0;
var round = this.flowObj.opts.forceChunkSize ? Math.ceil : Math.floor;
+ this.chunkSize = evalOpts(this.flowObj.opts.chunkSize, this);
var chunks = Math.max(
- round(this.file.size / this.flowObj.opts.chunkSize), 1
+ round(this.size / this.chunkSize), 1
);
for (var offset = 0; offset < chunks; offset++) {
this.chunks.push(
@@ -897,7 +983,7 @@
});
var percent = bytesLoaded / this.size;
// We don't want to lose percentages when an upload is paused
- this._prevProgress = Math.max(this._prevProgress, percent > 0.999 ? 1 : percent);
+ this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent);
return this._prevProgress;
},
@@ -926,7 +1012,7 @@
var outstanding = false;
each(this.chunks, function (chunk) {
var status = chunk.status();
- if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) {
+ if (status === 'pending' || status === 'uploading' || status === 'reading' || chunk.preprocessState === 1 || chunk.readState === 1) {
outstanding = true;
return false;
}
@@ -986,11 +1072,24 @@
}
};
+ /**
+ * Default read function using the webAPI
+ *
+ * @function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk)
+ *
+ */
+ function webAPIFileRead(fileObj, startByte, endByte, fileType, chunk) {
+ var function_name = 'slice';
+ if (fileObj.file.slice)
+ function_name = 'slice';
+ else if (fileObj.file.mozSlice)
+ function_name = 'mozSlice';
+ else if (fileObj.file.webkitSlice)
+ function_name = 'webkitSlice';
-
-
-
+ chunk.readFinished(fileObj.file[function_name](startByte, endByte, fileType));
+ }
/**
@@ -1015,12 +1114,6 @@
*/
this.fileObj = fileObj;
- /**
- * File size
- * @type {number}
- */
- this.fileObjSize = fileObj.size;
-
/**
* File offset
* @type {number}
@@ -1051,6 +1144,18 @@
*/
this.preprocessState = 0;
+ /**
+ * Read state
+ * @type {number} 0 = not read, 1 = reading, 2 = finished
+ */
+ this.readState = 0;
+
+ /**
+ * Used to store the bytes read
+ * @type {Blob|string}
+ */
+ this.bytes = undefined;
+
/**
* Bytes transferred from total request size
* @type {number}
@@ -1067,19 +1172,39 @@
* Size of a chunk
* @type {number}
*/
- var chunkSize = this.flowObj.opts.chunkSize;
+ this.chunkSize = this.fileObj.chunkSize;
/**
* Chunk start byte in a file
* @type {number}
*/
- this.startByte = this.offset * chunkSize;
+ this.startByte = this.offset * this.chunkSize;
+
+ /**
+ * A specific filename for this chunk which otherwise default to the main name
+ * @type {string}
+ */
+ this.filename = null;
+
+ /**
+ * Compute the endbyte in a file
+ *
+ */
+ this.computeEndByte = function() {
+ var endByte = Math.min(this.fileObj.size, (this.offset + 1) * this.chunkSize);
+ if (this.fileObj.size - endByte < this.chunkSize && !this.flowObj.opts.forceChunkSize) {
+ // The last chunk will be bigger than the chunk size,
+ // but less than 2 * this.chunkSize
+ endByte = this.fileObj.size;
+ }
+ return endByte;
+ }
/**
* Chunk end byte in a file
* @type {number}
*/
- this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * chunkSize);
+ this.endByte = this.computeEndByte();
/**
* XMLHttpRequest
@@ -1087,15 +1212,18 @@
*/
this.xhr = null;
- if (this.fileObjSize - this.endByte < chunkSize &&
- !this.flowObj.opts.forceChunkSize) {
- // The last chunk will be bigger than the chunk size,
- // but less than 2*chunkSize
- this.endByte = this.fileObjSize;
- }
-
var $ = this;
+ /**
+ * Send chunk event
+ * @param event
+ * @param {...} args arguments of a callback
+ */
+ this.event = function (event, args) {
+ args = Array.prototype.slice.call(arguments);
+ args.unshift($);
+ $.fileObj.chunkEvent.apply($.fileObj, args);
+ };
/**
* Catch progress event
* @param {ProgressEvent} event
@@ -1105,7 +1233,7 @@
$.loaded = event.loaded ;
$.total = event.total;
}
- $.fileObj.chunkEvent('progress');
+ $.event('progress', event);
};
/**
@@ -1113,12 +1241,17 @@
* @param {Event} event
*/
this.testHandler = function(event) {
- var status = $.status();
- if (status === 'success') {
+ var status = $.status(true);
+ if (status === 'error') {
+ $.event(status, $.message());
+ $.flowObj.uploadNextChunk();
+ } else if (status === 'success') {
$.tested = true;
- $.fileObj.chunkEvent(status, $.message());
+ $.event(status, $.message());
$.flowObj.uploadNextChunk();
- } else if (!$.fileObj.paused) {// Error might be caused by file pause method
+ } else if (!$.fileObj.paused) {
+ // Error might be caused by file pause method
+ // Chunks does not exist on the server side
$.tested = true;
$.send();
}
@@ -1131,10 +1264,11 @@
this.doneHandler = function(event) {
var status = $.status();
if (status === 'success' || status === 'error') {
- $.fileObj.chunkEvent(status, $.message());
+ delete this.data;
+ $.event(status, $.message());
$.flowObj.uploadNextChunk();
- } else {
- $.fileObj.chunkEvent('retry', $.message());
+ } else if (!$.fileObj.paused) {
+ $.event('retry', $.message());
$.pendingRetry = true;
$.abort();
$.retries++;
@@ -1158,9 +1292,9 @@
getParams: function () {
return {
flowChunkNumber: this.offset + 1,
- flowChunkSize: this.flowObj.opts.chunkSize,
+ flowChunkSize: this.chunkSize,
flowCurrentChunkSize: this.endByte - this.startByte,
- flowTotalSize: this.fileObjSize,
+ flowTotalSize: this.fileObj.size,
flowIdentifier: this.fileObj.uniqueIdentifier,
flowFilename: this.fileObj.name,
flowRelativePath: this.fileObj.relativePath,
@@ -1174,8 +1308,11 @@
* @param params
* @returns {string}
*/
- getTarget: function(params){
- var target = this.flowObj.opts.target;
+ getTarget: function(target, params){
+ if (params.length == 0) {
+ return target;
+ }
+
if(target.indexOf('?') < 0) {
target += '?';
} else {
@@ -1194,7 +1331,8 @@
this.xhr = new XMLHttpRequest();
this.xhr.addEventListener("load", this.testHandler, false);
this.xhr.addEventListener("error", this.testHandler, false);
- var data = this.prepareXhrRequest('GET');
+ var testMethod = evalOpts(this.flowObj.opts.testMethod, this.fileObj, this);
+ var data = this.prepareXhrRequest(testMethod, true);
this.xhr.send(data);
},
@@ -1203,28 +1341,50 @@
* @function
*/
preprocessFinished: function () {
+ // Re-compute the endByte after the preprocess function to allow an
+ // implementer of preprocess to set the fileObj size
+ this.endByte = this.computeEndByte();
+
this.preprocessState = 2;
this.send();
},
+ /**
+ * Finish read state
+ * @function
+ */
+ readFinished: function (bytes) {
+ this.readState = 2;
+ this.bytes = bytes;
+ this.send();
+ },
+
+
/**
* Uploads the actual data in a POST call
* @function
*/
send: function () {
var preprocess = this.flowObj.opts.preprocess;
+ var read = this.flowObj.opts.readFileFn;
if (typeof preprocess === 'function') {
switch (this.preprocessState) {
case 0:
- preprocess(this);
this.preprocessState = 1;
+ preprocess(this);
return;
case 1:
return;
- case 2:
- break;
}
}
+ switch (this.readState) {
+ case 0:
+ this.readState = 1;
+ read(this.fileObj, this.startByte, this.endByte, this.fileObj.file.type, this);
+ return;
+ case 1:
+ return;
+ }
if (this.flowObj.opts.testChunks && !this.tested) {
this.test();
return;
@@ -1234,20 +1394,18 @@
this.total = 0;
this.pendingRetry = false;
- var func = (this.fileObj.file.slice ? 'slice' :
- (this.fileObj.file.mozSlice ? 'mozSlice' :
- (this.fileObj.file.webkitSlice ? 'webkitSlice' :
- 'slice')));
- var bytes = this.fileObj.file[func](this.startByte, this.endByte);
-
// Set up request and listen for event
this.xhr = new XMLHttpRequest();
this.xhr.upload.addEventListener('progress', this.progressHandler, false);
this.xhr.addEventListener("load", this.doneHandler, false);
this.xhr.addEventListener("error", this.doneHandler, false);
- var data = this.prepareXhrRequest('POST', this.flowObj.opts.method, bytes);
-
+ var uploadMethod = evalOpts(this.flowObj.opts.uploadMethod, this.fileObj, this);
+ var data = this.prepareXhrRequest(uploadMethod, false, this.flowObj.opts.method, this.bytes);
+ var changeRawDataBeforeSend = this.flowObj.opts.changeRawDataBeforeSend;
+ if (typeof changeRawDataBeforeSend === 'function') {
+ data = changeRawDataBeforeSend(this, data);
+ }
this.xhr.send(data);
},
@@ -1269,8 +1427,10 @@
* @function
* @returns {string} 'pending', 'uploading', 'success', 'error'
*/
- status: function () {
- if (this.pendingRetry) {
+ status: function (isTest) {
+ if (this.readState === 1) {
+ return 'reading';
+ } else if (this.pendingRetry || this.preprocessState === 1) {
// 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';
@@ -1281,12 +1441,13 @@
// or 'LOADING' - meaning that stuff is happening
return 'uploading';
} else {
- if (this.xhr.status == 200) {
+ if (this.flowObj.opts.successStatuses.indexOf(this.xhr.status) > -1) {
// HTTP 200, perfect
+ // HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
return 'success';
} else if (this.flowObj.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
- this.retries >= this.flowObj.opts.maxChunkRetries) {
- // HTTP 415/500/501, permanent error
+ !isTest && this.retries >= this.flowObj.opts.maxChunkRetries) {
+ // HTTP 413/415/500/501, permanent error
return 'error';
} else {
// this should never happen, but we'll reset and queue a retry
@@ -1342,19 +1503,17 @@
/**
* Prepare Xhr request. Set query, headers and data
* @param {string} method GET or POST
+ * @param {bool} isTest is this a test request
* @param {string} [paramsMethod] octet or form
* @param {Blob} [blob] to send
* @returns {FormData|Blob|Null} data to send
*/
- prepareXhrRequest: function(method, paramsMethod, blob) {
+ prepareXhrRequest: function(method, isTest, paramsMethod, blob) {
// Add data from the query options
- var query = this.flowObj.opts.query;
- if (typeof query === "function") {
- query = query(this.fileObj, this);
- }
- query = extend(this.getParams(), query);
+ var query = evalOpts(this.flowObj.opts.query, this.fileObj, this, isTest);
+ query = extend(query || {}, this.getParams());
- var target = this.flowObj.opts.target;
+ var target = evalOpts(this.flowObj.opts.target, this.fileObj, this, isTest);
var data = null;
if (method === 'GET' || paramsMethod === 'octet') {
// Add data from the query options
@@ -1362,7 +1521,7 @@
each(query, function (v, k) {
params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
});
- target = this.getTarget(params);
+ target = this.getTarget(target, params);
data = blob || null;
} else {
// Add data from the query options
@@ -1370,14 +1529,16 @@
each(query, function (v, k) {
data.append(k, v);
});
- data.append(this.flowObj.opts.fileParameterName, blob);
+ if (typeof blob !== "undefined") {
+ data.append(this.flowObj.opts.fileParameterName, blob, this.filename || this.fileObj.file.name);
+ }
}
- this.xhr.open(method, target);
+ this.xhr.open(method, target, true);
this.xhr.withCredentials = this.flowObj.opts.withCredentials;
// Add data from header options
- each(this.flowObj.opts.headers, function (v, k) {
+ each(evalOpts(this.flowObj.opts.headers, this.fileObj, this, isTest), function (v, k) {
this.xhr.setRequestHeader(k, v);
}, this);
@@ -1397,6 +1558,31 @@
}
}
+ /**
+ * If option is a function, evaluate it with given params
+ * @param {*} data
+ * @param {...} args arguments of a callback
+ * @returns {*}
+ */
+ function evalOpts(data, args) {
+ if (typeof data === "function") {
+ // `arguments` is an object, not array, in FF, so:
+ args = Array.prototype.slice.call(arguments);
+ data = data.apply(null, args.slice(1));
+ }
+ return data;
+ }
+ Flow.evalOpts = evalOpts;
+
+ /**
+ * Execute function asynchronously
+ * @param fn
+ * @param context
+ */
+ function async(fn, context) {
+ setTimeout(fn.bind(context), 0);
+ }
+
/**
* Extends the destination object `dst` by copying all of the properties from
* the `src` object(s) to `dst`. You can specify multiple `src` objects.
@@ -1430,6 +1616,7 @@
}
var key;
// Is Array?
+ // Array.isArray won't work, not only arrays can be iterated by index https://github.com/flowjs/ng-flow/issues/236#
if (typeof(obj.length) !== 'undefined') {
for (key = 0; key < obj.length; key++) {
if (callback.call(context, obj[key], key) === false) {
@@ -1462,7 +1649,7 @@
* Library version
* @type {string}
*/
- Flow.version = '2.1.0';
+ Flow.version = '<%= version %>';
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
// Expose Flow as module.exports in loaders that implement the Node
@@ -1485,4 +1672,4 @@
define( "flow", [], function () { return Flow; } );
}
}
-})(window, document);
+})(typeof window !== 'undefined' && window, typeof document !== 'undefined' && document);
diff --git a/test/evalOptsSpec.js b/test/evalOptsSpec.js
new file mode 100644
index 00000000..7593ddfc
--- /dev/null
+++ b/test/evalOptsSpec.js
@@ -0,0 +1,17 @@
+describe('evalOpts', function () {
+
+ it('should return same object for non functions', function() {
+ var obj = {};
+ expect(Flow.evalOpts(obj)).toBe(obj);
+ });
+ it('should return same type for non functions', function() {
+ expect(Flow.evalOpts(5)).toBe(5);
+ });
+ it('should evaluate function', function() {
+ expect(Flow.evalOpts(function () {return 5;})).toBe(5);
+ });
+ it('should evaluate function with given arguments', function() {
+ var obj = {};
+ expect(Flow.evalOpts(function (a) {return a;}, obj)).toBe(obj);
+ });
+});
\ No newline at end of file
diff --git a/test/eventsSpec.js b/test/eventsSpec.js
index 24a982f8..813cb695 100644
--- a/test/eventsSpec.js
+++ b/test/eventsSpec.js
@@ -17,6 +17,15 @@ describe('events', function() {
expect(valid).toBeTruthy();
});
+ it('should have a context of flow instance', function() {
+ var context = null;
+ flow.on('test', function () {
+ context = this;
+ });
+ flow.fire('test');
+ expect(context).toEqual(flow);
+ });
+
it('should pass some arguments', function() {
var valid = false;
var argumentOne = 123;
@@ -101,4 +110,4 @@ describe('events', function() {
expect(event).not.toHaveBeenCalled();
});
});
-});
\ No newline at end of file
+});
diff --git a/test/fileRemoveSpec.js b/test/fileRemoveSpec.js
new file mode 100644
index 00000000..ff1ddc97
--- /dev/null
+++ b/test/fileRemoveSpec.js
@@ -0,0 +1,45 @@
+describe('fileRemoved event', function() {
+ /**
+ * @type {Flow}
+ */
+ var flow;
+
+ beforeEach(function () {
+ flow = new Flow({
+ generateUniqueIdentifier: function (file) {
+ return file.size;
+ }
+ });
+ });
+
+ it('should call fileRemoved event on Flow.removeFile', function() {
+ var valid = false;
+ var removedFile = null;
+ flow.on('fileRemoved', function (file) {
+ expect(file.file instanceof Blob).toBeTruthy();
+ removedFile = file;
+ valid = true;
+ });
+ flow.addFile(new Blob(['file part']));
+ var addedFile = flow.files[0];
+ flow.removeFile(addedFile);
+ expect(removedFile).toBe(addedFile);
+ expect(valid).toBeTruthy();
+ });
+
+ it('should call fileRemoved event FlowFile.cancel', function() {
+ var valid = false;
+ var removedFile = null;
+ flow.on('fileRemoved', function (file) {
+ expect(file.file instanceof Blob).toBeTruthy();
+ removedFile = file;
+ valid = true;
+ });
+ flow.addFile(new Blob(['file part']));
+ var addedFile = flow.files[0];
+ addedFile.cancel();
+ expect(removedFile).toBe(addedFile);
+ expect(valid).toBeTruthy();
+ });
+
+});
\ No newline at end of file
diff --git a/test/fileSpec.js b/test/fileSpec.js
index d7607be1..190c031a 100644
--- a/test/fileSpec.js
+++ b/test/fileSpec.js
@@ -34,4 +34,5 @@ describe('FlowFile functions', function() {
file.name = '.dwq.dq.wd.qdw.E';
expect(file.getExtension()).toBe('e');
});
-});
\ No newline at end of file
+
+});
diff --git a/test/setupSpec.js b/test/setupSpec.js
index 91413e30..bf4040d6 100644
--- a/test/setupSpec.js
+++ b/test/setupSpec.js
@@ -69,7 +69,7 @@ describe('setup', function() {
var event = document.createEvent('MouseEvents');
event.initEvent('change', true, true);
input.dispatchEvent(event);
- expect(addFiles).toHaveBeenCalled();
+ expect(addFiles).not.toHaveBeenCalled();
});
it('assign to div', function() {
@@ -83,7 +83,7 @@ describe('setup', function() {
var event = document.createEvent('MouseEvents');
event.initEvent('change', true, true);
input.dispatchEvent(event);
- expect(addFiles).toHaveBeenCalled();
+ expect(addFiles).not.toHaveBeenCalled();
});
it('single file', function() {
@@ -112,12 +112,12 @@ describe('setup', function() {
event.dataTransfer = {files: []};
div.dispatchEvent(event);
expect(onDrop).toHaveBeenCalled();
- expect(onDrop.callCount).toBe(1);
+ expect(onDrop.calls.count()).toBe(1);
flow.unAssignDrop(div);
div.dispatchEvent(event);
- expect(onDrop.callCount).toBe(1);
+ expect(onDrop.calls.count()).toBe(1);
});
});
-});
\ No newline at end of file
+});
diff --git a/test/singleFileSpec.js b/test/singleFileSpec.js
index 25dbdf37..3ba49343 100644
--- a/test/singleFileSpec.js
+++ b/test/singleFileSpec.js
@@ -23,4 +23,29 @@ describe('add single file', function() {
expect(flow.files.length).toBe(1);
expect(file.isUploading()).toBeFalsy();
});
+
+ it('should fire remove event after adding another file', function(){
+ var events = [];
+ flow.on('catchAll', function (event) {
+ events.push(event);
+ });
+ flow.addFile(new Blob(['file part']));
+ expect(flow.files.length).toBe(1);
+ expect(events.length).toBe(3);
+ expect(events[0]).toBe('fileAdded');
+ expect(events[1]).toBe('filesAdded');
+ expect(events[2]).toBe('filesSubmitted');
+
+ var removedFile = flow.files[0];
+ flow.on('fileRemoved', function(file){
+ expect(file).toBe(removedFile);
+ });
+ flow.addFile(new Blob(['file part 2']));
+ expect(flow.files.length).toBe(1);
+ expect(events.length).toBe(7);
+ expect(events[3]).toBe('fileAdded');
+ expect(events[4]).toBe('filesAdded');
+ expect(events[5]).toBe('fileRemoved');
+ expect(events[6]).toBe('filesSubmitted');
+ });
});
\ No newline at end of file
diff --git a/test/uploadSpec.js b/test/uploadSpec.js
index 6328a36f..81ef44c9 100644
--- a/test/uploadSpec.js
+++ b/test/uploadSpec.js
@@ -1,461 +1,597 @@
-describe('upload file', function() {
- /**
- * @type {Flow}
- */
- var flow;
- /**
- * @type {FakeXMLHttpRequest}
- */
- var xhr;
- /**
- * @type {FakeXMLHttpRequest[]}
- */
- var requests = [];
-
- beforeEach(function () {
- flow = new Flow({
- progressCallbacksInterval: 0,
- generateUniqueIdentifier: function (file) {
- return file.size;
- }
- });
- requests = [];
-
- xhr = sinon.useFakeXMLHttpRequest();
- xhr.onCreate = function (xhr) {
- requests.push(xhr);
- };
- });
-
- afterEach(function () {
- xhr.restore();
- });
-
- it('should pass query params', function() {
- flow.opts.query = {};
- flow.opts.target = 'file';
- flow.addFile(new Blob(['123']));
- flow.upload();
- expect(requests.length).toBe(1);
- expect(requests[0].url).toContain('file');
-
- flow.opts.query = {a:1};
- flow.files[0].retry();
- expect(requests.length).toBe(2);
- expect(requests[1].url).toContain('file');
- expect(requests[1].url).toContain('a=1');
-
- flow.opts.query = function (file, chunk) {
- expect(file).toBe(flow.files[0]);
- expect(chunk).toBe(flow.files[0].chunks[0]);
- return {b:2};
- };
- flow.files[0].retry();
- expect(requests.length).toBe(3);
- expect(requests[2].url).toContain('file');
- expect(requests[2].url).toContain('b=2');
- expect(requests[2].url).not.toContain('a=1');
-
- flow.opts.target = 'file?w=w';
- flow.opts.query = undefined;
- flow.files[0].retry();
- expect(requests.length).toBe(4);
- expect(requests[3].url).toContain('file?w=w&');
- expect(requests[3].url).not.toContain('a=1');
- expect(requests[3].url).not.toContain('b=2');
- });
-
- it('should track file upload status with lots of chunks', function() {
- flow.opts.chunkSize = 1;
- flow.addFile(new Blob(['IIIIIIIIII']));
- var file = flow.files[0];
- expect(file.chunks.length).toBe(10);
- flow.upload();
- expect(file.progress()).toBe(0);
- for (var i = 0; i < 9; i++) {
- expect(requests[i]).toBeDefined();
- expect(file.isComplete()).toBeFalsy();
- expect(file.isUploading()).toBeTruthy();
- requests[i].respond(200);
- expect(file.progress()).toBe((i+1) / 10);
- expect(file.isComplete()).toBeFalsy();
- expect(file.isUploading()).toBeTruthy();
- }
- expect(requests[9]).toBeDefined();
- expect(file.isComplete()).toBeFalsy();
- expect(file.isUploading()).toBeTruthy();
- expect(file.progress()).toBe(0.9);
- requests[i].respond(200);
- expect(file.isComplete()).toBeTruthy();
- expect(file.isUploading()).toBeFalsy();
- expect(file.progress()).toBe(1);
- expect(flow.progress()).toBe(1);
- });
-
- it('should throw expected events', function () {
- var events = [];
- flow.on('catchAll', function (event) {
- events.push(event);
- });
- flow.opts.chunkSize = 1;
- flow.addFile(new Blob(['12']));
- var file = flow.files[0];
- expect(file.chunks.length).toBe(2);
- flow.upload();
- // Sync events
- expect(events.length).toBe(4);
- expect(events[0]).toBe('fileAdded');
- expect(events[1]).toBe('filesAdded');
- expect(events[2]).toBe('filesSubmitted');
- expect(events[3]).toBe('uploadStart');
- // Async
- requests[0].respond(200);
- expect(events.length).toBe(6);
- expect(events[4]).toBe('fileProgress');
- expect(events[5]).toBe('progress');
- requests[1].respond(400);
- expect(events.length).toBe(6);
- requests[2].progress(5, 10, true);
- expect(events.length).toBe(8);
- expect(events[6]).toBe('fileProgress');
- expect(events[7]).toBe('progress');
- requests[2].respond(200);
- expect(events.length).toBe(12);
- expect(events[8]).toBe('fileProgress');
- expect(events[9]).toBe('progress');
- expect(events[10]).toBe('fileSuccess');
- // Can be sync and async
- expect(events[11]).toBe('complete');
-
- flow.upload();
- expect(events.length).toBe(14);
- expect(events[12]).toBe('uploadStart');
- expect(events[13]).toBe('complete');
- });
-
- it('should pause and resume file', function () {
- flow.opts.chunkSize = 1;
- flow.opts.simultaneousUploads = 2;
- flow.addFile(new Blob(['1234']));
- flow.addFile(new Blob(['56']));
- var files = flow.files;
- expect(files[0].chunks.length).toBe(4);
- expect(files[1].chunks.length).toBe(2);
- flow.upload();
- expect(files[0].isUploading()).toBeTruthy();
- expect(requests.length).toBe(2);
- expect(requests[0].aborted).toBeUndefined();
- expect(requests[1].aborted).toBeUndefined();
- // should start upload second file
- files[0].pause();
- expect(files[0].isUploading()).toBeFalsy();
- expect(files[1].isUploading()).toBeTruthy();
- expect(requests.length).toBe(4);
- expect(requests[0].aborted).toBeTruthy();
- expect(requests[1].aborted).toBeTruthy();
- expect(requests[2].aborted).toBeUndefined();
- expect(requests[3].aborted).toBeUndefined();
- // Should resume file after second file chunks is uploaded
- files[0].resume();
- expect(files[0].isUploading()).toBeFalsy();
- expect(requests.length).toBe(4);
- requests[2].respond(200);// second file chunk
- expect(files[0].isUploading()).toBeTruthy();
- expect(files[1].isUploading()).toBeTruthy();
- expect(requests.length).toBe(5);
- requests[3].respond(200); // second file chunk
- expect(requests.length).toBe(6);
- expect(files[0].isUploading()).toBeTruthy();
- expect(files[1].isUploading()).toBeFalsy();
- expect(files[1].isComplete()).toBeTruthy();
- requests[4].respond(200);
- expect(requests.length).toBe(7);
- requests[5].respond(200);
- expect(requests.length).toBe(8);
- requests[6].respond(200);
- expect(requests.length).toBe(8);
- requests[7].respond(200);
- expect(requests.length).toBe(8);
- // Upload finished
- expect(files[0].isUploading()).toBeFalsy();
- expect(files[0].isComplete()).toBeTruthy();
- expect(files[0].progress()).toBe(1);
- expect(files[1].isUploading()).toBeFalsy();
- expect(files[1].isComplete()).toBeTruthy();
- expect(files[1].progress()).toBe(1);
- expect(flow.progress()).toBe(1);
- });
-
- it('should retry file', function () {
- flow.opts.testChunks = false;
- flow.opts.chunkSize = 1;
- flow.opts.simultaneousUploads = 1;
- flow.opts.maxChunkRetries = 1;
- flow.opts.permanentErrors = [500];
- var error = jasmine.createSpy('error');
- var progress = jasmine.createSpy('progress');
- var success = jasmine.createSpy('success');
- var retry = jasmine.createSpy('retry');
- flow.on('fileError', error);
- flow.on('fileProgress', progress);
- flow.on('fileSuccess', success);
- flow.on('fileRetry', retry);
-
- flow.addFile(new Blob(['12']));
- var file = flow.files[0];
- expect(file.chunks.length).toBe(2);
- expect(file.chunks[0].status()).toBe('pending');
- expect(file.chunks[1].status()).toBe('pending');
-
- flow.upload();
- expect(requests.length).toBe(1);
- expect(file.chunks[0].status()).toBe('uploading');
- expect(file.chunks[1].status()).toBe('pending');
-
- expect(error).not.toHaveBeenCalled();
- expect(progress).not.toHaveBeenCalled();
- expect(success).not.toHaveBeenCalled();
- expect(retry).not.toHaveBeenCalled();
-
- requests[0].respond(400);
- expect(requests.length).toBe(2);
- expect(file.chunks[0].status()).toBe('uploading');
- expect(file.chunks[1].status()).toBe('pending');
-
- expect(error).not.toHaveBeenCalled();
- expect(progress).not.toHaveBeenCalled();
- expect(success).not.toHaveBeenCalled();
- expect(retry).toHaveBeenCalled();
-
- requests[1].respond(200);
- expect(requests.length).toBe(3);
- expect(file.chunks[0].status()).toBe('success');
- expect(file.chunks[1].status()).toBe('uploading');
-
- expect(error).not.toHaveBeenCalled();
- expect(progress.callCount).toBe(1);
- expect(success).not.toHaveBeenCalled();
- expect(retry.callCount).toBe(1);
-
- requests[2].respond(400);
- expect(requests.length).toBe(4);
- expect(file.chunks[0].status()).toBe('success');
- expect(file.chunks[1].status()).toBe('uploading');
-
- expect(error).not.toHaveBeenCalled();
- expect(progress.callCount).toBe(1);
- expect(success).not.toHaveBeenCalled();
- expect(retry.callCount).toBe(2);
-
- requests[3].respond(400, {}, 'Err');
- expect(requests.length).toBe(4);
- expect(file.chunks.length).toBe(0);
-
- expect(error.callCount).toBe(1);
- expect(error).toHaveBeenCalledWith(file, 'Err');
- expect(progress.callCount).toBe(1);
- expect(success).not.toHaveBeenCalled();
- expect(retry.callCount).toBe(2);
-
- expect(file.error).toBeTruthy();
- expect(file.isComplete()).toBeTruthy();
- expect(file.isUploading()).toBeFalsy();
- expect(file.progress()).toBe(1);
- });
-
- it('should retry file with timeout', function () {
- jasmine.Clock.useMock();
- flow.opts.testChunks = false;
- flow.opts.maxChunkRetries = 1;
- flow.opts.chunkRetryInterval = 100;
-
- var error = jasmine.createSpy('error');
- var success = jasmine.createSpy('success');
- var retry = jasmine.createSpy('retry');
- flow.on('fileError', error);
- flow.on('fileSuccess', success);
- flow.on('fileRetry', retry);
-
- flow.addFile(new Blob(['12']));
- var file = flow.files[0];
- flow.upload();
- expect(requests.length).toBe(1);
-
- requests[0].respond(400);
- expect(requests.length).toBe(1);
- expect(error).not.toHaveBeenCalled();
- expect(success).not.toHaveBeenCalled();
- expect(retry).toHaveBeenCalled();
- expect(file.chunks[0].status()).toBe('uploading');
-
- jasmine.Clock.tick(100);
- expect(requests.length).toBe(2);
- requests[1].respond(200);
- expect(error).not.toHaveBeenCalled();
- expect(success).toHaveBeenCalled();
- expect(retry).toHaveBeenCalled();
- });
-
- it('should fail on permanent error', function () {
- flow.opts.testChunks = false;
- flow.opts.chunkSize = 1;
- flow.opts.simultaneousUploads = 2;
- flow.opts.maxChunkRetries = 1;
- flow.opts.permanentErrors = [500];
-
- var error = jasmine.createSpy('error');
- var success = jasmine.createSpy('success');
- var retry = jasmine.createSpy('retry');
- flow.on('fileError', error);
- flow.on('fileSuccess', success);
- flow.on('fileRetry', retry);
-
- flow.addFile(new Blob(['abc']));
- var file = flow.files[0];
- expect(file.chunks.length).toBe(3);
- flow.upload();
- expect(requests.length).toBe(2);
- requests[0].respond(500);
- expect(requests.length).toBe(2);
- expect(error).toHaveBeenCalled();
- expect(retry).not.toHaveBeenCalled();
- expect(success).not.toHaveBeenCalled();
- });
-
- it('should upload empty file', function () {
- var error = jasmine.createSpy('error');
- var success = jasmine.createSpy('success');
- flow.on('fileError', error);
- flow.on('fileSuccess', success);
-
- flow.addFile(new Blob([]));
- var file = flow.files[0];
- flow.upload();
- expect(requests.length).toBe(1);
- expect(file.progress()).toBe(0);
- requests[0].respond(200);
- expect(requests.length).toBe(1);
- expect(error).not.toHaveBeenCalled();
- expect(success).toHaveBeenCalled();
- expect(file.progress()).toBe(1);
- expect(file.isUploading()).toBe(false);
- expect(file.isComplete()).toBe(true);
- });
-
- it('should not upload folder', function () {
- // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
- flow.addFile({
- name: '.',
- size: 0
- });
- expect(flow.files.length).toBe(0);
- flow.addFile({
- name: '.',
- size: 4096
- });
- expect(flow.files.length).toBe(0);
- flow.addFile({
- name: '.',
- size: 4096 * 2
- });
- expect(flow.files.length).toBe(0);
- });
-
- it('should preprocess chunks', function () {
- var preprocess = jasmine.createSpy('preprocess');
- var error = jasmine.createSpy('error');
- var success = jasmine.createSpy('success');
- flow.on('fileError', error);
- flow.on('fileSuccess', success);
- flow.opts.preprocess = preprocess;
- flow.addFile(new Blob(['abc']));
- var file = flow.files[0];
- flow.upload();
- expect(requests.length).toBe(0);
- expect(preprocess).wasCalledWith(file.chunks[0]);
- expect(file.chunks[0].preprocessState).toBe(1);
- file.chunks[0].preprocessFinished();
- expect(requests.length).toBe(1);
- requests[0].respond(200, [], "response");
- expect(success).wasCalledWith(file, "response");
- expect(error).not.toHaveBeenCalled();
- });
-
- it('should have upload speed', function() {
- var clock = sinon.useFakeTimers();
- flow.opts.testChunks = false;
- flow.opts.speedSmoothingFactor = 0.5;
- flow.opts.simultaneousUploads = 1;
- var fileProgress = jasmine.createSpy('fileProgress');
- flow.on('fileProgress', fileProgress);
- flow.addFile(new Blob(['0123456789']));
- flow.addFile(new Blob(['12345']));
- var fileFirst = flow.files[0];
- var fileSecond = flow.files[1];
- expect(fileFirst.currentSpeed).toBe(0);
- expect(fileFirst.averageSpeed).toBe(0);
- expect(fileFirst.sizeUploaded()).toBe(0);
- expect(fileFirst.timeRemaining()).toBe(Number.POSITIVE_INFINITY);
- expect(flow.sizeUploaded()).toBe(0);
- expect(flow.timeRemaining()).toBe(Number.POSITIVE_INFINITY);
- flow.upload();
-
- clock.tick(1000);
- requests[0].progress(50, 100, true);
- expect(fileProgress).toHaveBeenCalled();
- expect(fileFirst.currentSpeed).toBe(5);
- expect(fileFirst.averageSpeed).toBe(2.5);
- expect(fileFirst.sizeUploaded()).toBe(5);
- expect(fileFirst.timeRemaining()).toBe(2);
-
- expect(flow.sizeUploaded()).toBe(5);
- expect(flow.timeRemaining()).toBe(4);
-
- clock.tick(1000);
- requests[0].progress(10, 10, true);
- expect(fileFirst.currentSpeed).toBe(5);
- expect(fileFirst.averageSpeed).toBe(3.75);
-
- requests[0].respond(200, [], "response");
- expect(fileFirst.currentSpeed).toBe(0);
- expect(fileFirst.averageSpeed).toBe(0);
-
- requests[1].respond(200, [], "response");
- expect(fileFirst.sizeUploaded()).toBe(10);
- expect(fileFirst.timeRemaining()).toBe(0);
- expect(fileSecond.sizeUploaded()).toBe(5);
- expect(fileSecond.timeRemaining()).toBe(0);
- expect(flow.sizeUploaded()).toBe(15);
- expect(flow.timeRemaining()).toBe(0);
-
- // paused and resumed
- flow.addFile(new Blob(['012345678901234']));
- var fileThird = flow.files[2];
- expect(fileThird.timeRemaining()).toBe(Number.POSITIVE_INFINITY);
- flow.upload();
- clock.tick(1000);
- requests[2].progress(10, 15, true);
- expect(fileThird.timeRemaining()).toBe(1);
- expect(flow.timeRemaining()).toBe(1);
- fileThird.pause();
- expect(fileThird.timeRemaining()).toBe(0);
- expect(flow.timeRemaining()).toBe(0);
- fileThird.resume();
- expect(fileThird.timeRemaining()).toBe(Number.POSITIVE_INFINITY);
- expect(flow.timeRemaining()).toBe(Number.POSITIVE_INFINITY);
- clock.tick(1000);
- requests[3].progress(11, 15, true);
- expect(fileThird.timeRemaining()).toBe(8);
- expect(flow.timeRemaining()).toBe(8);
- clock.tick(1000);
- requests[3].progress(12, 15, true);
- expect(fileThird.timeRemaining()).toBe(4);
- expect(flow.timeRemaining()).toBe(4);
-
- requests[3].respond(500);
- expect(fileThird.currentSpeed).toBe(0);
- expect(fileThird.averageSpeed).toBe(0);
- expect(fileThird.timeRemaining()).toBe(0);
- expect(flow.timeRemaining()).toBe(0);
- });
-});
\ No newline at end of file
+describe('upload file', function() {
+ /**
+ * @type {Flow}
+ */
+ var flow;
+ /**
+ * @type {FakeXMLHttpRequest}
+ */
+ var xhr;
+ /**
+ * @type {FakeXMLHttpRequest[]}
+ */
+ var requests = [];
+
+ beforeEach(function () {
+ jasmine.clock().install();
+
+ flow = new Flow({
+ progressCallbacksInterval: 0,
+ generateUniqueIdentifier: function (file) {
+ return file.size;
+ }
+ });
+
+ requests = [];
+ xhr = sinon.useFakeXMLHttpRequest();
+ xhr.onCreate = function (xhr) {
+ requests.push(xhr);
+ };
+ });
+
+ afterEach(function () {
+ jasmine.clock().uninstall();
+
+ xhr.restore();
+ });
+
+ it('should pass query params', function() {
+ flow.opts.query = {};
+ flow.opts.target = 'file';
+ flow.addFile(new Blob(['123']));
+ flow.upload();
+ expect(requests.length).toBe(1);
+ expect(requests[0].url).toContain('file');
+
+ flow.opts.query = {a:1};
+ flow.files[0].retry();
+ expect(requests.length).toBe(2);
+ expect(requests[1].url).toContain('file');
+ expect(requests[1].url).toContain('a=1');
+
+ flow.opts.query = function (file, chunk) {
+ expect(file).toBe(flow.files[0]);
+ expect(chunk).toBe(flow.files[0].chunks[0]);
+ return {b:2};
+ };
+ flow.files[0].retry();
+ expect(requests.length).toBe(3);
+ expect(requests[2].url).toContain('file');
+ expect(requests[2].url).toContain('b=2');
+ expect(requests[2].url).not.toContain('a=1');
+
+ flow.opts.target = 'file?w=w';
+ flow.opts.query = {};
+ flow.files[0].retry();
+ expect(requests.length).toBe(4);
+ expect(requests[3].url).toContain('file?w=w&');
+ expect(requests[3].url).not.toContain('a=1');
+ expect(requests[3].url).not.toContain('b=2');
+ });
+
+ it('should track file upload status with lots of chunks', function() {
+ flow.opts.chunkSize = 1;
+ flow.addFile(new Blob(['IIIIIIIIII']));
+ var file = flow.files[0];
+ expect(file.chunks.length).toBe(10);
+ flow.upload();
+ expect(file.progress()).toBe(0);
+ for (var i = 0; i < 9; i++) {
+ expect(requests[i]).toBeDefined();
+ expect(file.isComplete()).toBeFalsy();
+ expect(file.isUploading()).toBeTruthy();
+ requests[i].respond(200);
+ expect(file.progress()).toBe((i+1) / 10);
+ expect(file.isComplete()).toBeFalsy();
+ expect(file.isUploading()).toBeTruthy();
+ }
+ expect(requests[9]).toBeDefined();
+ expect(file.isComplete()).toBeFalsy();
+ expect(file.isUploading()).toBeTruthy();
+ expect(file.progress()).toBe(0.9);
+ requests[i].respond(200);
+ expect(file.isComplete()).toBeTruthy();
+ expect(file.isUploading()).toBeFalsy();
+ expect(file.progress()).toBe(1);
+ expect(flow.progress()).toBe(1);
+ });
+
+ it('should throw expected events', function () {
+ var events = [];
+ flow.on('catchAll', function (event) {
+ events.push(event);
+ });
+ flow.opts.chunkSize = 1;
+ flow.addFile(new Blob(['12']));
+ var file = flow.files[0];
+ expect(file.chunks.length).toBe(2);
+ flow.upload();
+ // Sync events
+ expect(events.length).toBe(4);
+ expect(events[0]).toBe('fileAdded');
+ expect(events[1]).toBe('filesAdded');
+ expect(events[2]).toBe('filesSubmitted');
+ expect(events[3]).toBe('uploadStart');
+ // Async
+ requests[0].respond(200);
+ expect(events.length).toBe(6);
+ expect(events[4]).toBe('fileProgress');
+ expect(events[5]).toBe('progress');
+ requests[1].respond(400);
+ expect(events.length).toBe(6);
+ requests[2].progress(5, 10, true);
+ expect(events.length).toBe(8);
+ expect(events[6]).toBe('fileProgress');
+ expect(events[7]).toBe('progress');
+ requests[2].respond(200);
+ expect(events.length).toBe(11);
+ expect(events[8]).toBe('fileProgress');
+ expect(events[9]).toBe('progress');
+ expect(events[10]).toBe('fileSuccess');
+
+ jasmine.clock().tick(1);
+ expect(events.length).toBe(12);
+ expect(events[11]).toBe('complete');
+
+ flow.upload();
+ expect(events.length).toBe(13);
+ expect(events[12]).toBe('uploadStart');
+
+ // complete event is always asynchronous
+ jasmine.clock().tick(1);
+ expect(events.length).toBe(14);
+ expect(events[13]).toBe('complete');
+ });
+
+ it('should pause and resume file', function () {
+ flow.opts.chunkSize = 1;
+ flow.opts.simultaneousUploads = 2;
+ flow.addFile(new Blob(['1234']));
+ flow.addFile(new Blob(['56']));
+ var files = flow.files;
+ expect(files[0].chunks.length).toBe(4);
+ expect(files[1].chunks.length).toBe(2);
+ flow.upload();
+ expect(files[0].isUploading()).toBeTruthy();
+ expect(requests.length).toBe(2);
+ expect(requests[0].aborted).toBeUndefined();
+ expect(requests[1].aborted).toBeUndefined();
+ // should start upload second file
+ files[0].pause();
+ expect(files[0].isUploading()).toBeFalsy();
+ expect(files[1].isUploading()).toBeTruthy();
+ expect(requests.length).toBe(4);
+ expect(requests[0].aborted).toBeTruthy();
+ expect(requests[1].aborted).toBeTruthy();
+ expect(requests[2].aborted).toBeUndefined();
+ expect(requests[3].aborted).toBeUndefined();
+ // Should resume file after second file chunks is uploaded
+ files[0].resume();
+ expect(files[0].isUploading()).toBeFalsy();
+ expect(requests.length).toBe(4);
+ requests[2].respond(200);// second file chunk
+ expect(files[0].isUploading()).toBeTruthy();
+ expect(files[1].isUploading()).toBeTruthy();
+ expect(requests.length).toBe(5);
+ requests[3].respond(200); // second file chunk
+ expect(requests.length).toBe(6);
+ expect(files[0].isUploading()).toBeTruthy();
+ expect(files[1].isUploading()).toBeFalsy();
+ expect(files[1].isComplete()).toBeTruthy();
+ requests[4].respond(200);
+ expect(requests.length).toBe(7);
+ requests[5].respond(200);
+ expect(requests.length).toBe(8);
+ requests[6].respond(200);
+ expect(requests.length).toBe(8);
+ requests[7].respond(200);
+ expect(requests.length).toBe(8);
+ // Upload finished
+ expect(files[0].isUploading()).toBeFalsy();
+ expect(files[0].isComplete()).toBeTruthy();
+ expect(files[0].progress()).toBe(1);
+ expect(files[1].isUploading()).toBeFalsy();
+ expect(files[1].isComplete()).toBeTruthy();
+ expect(files[1].progress()).toBe(1);
+ expect(flow.progress()).toBe(1);
+ });
+
+ it('should retry file', function () {
+ flow.opts.testChunks = false;
+ flow.opts.chunkSize = 1;
+ flow.opts.simultaneousUploads = 1;
+ flow.opts.maxChunkRetries = 1;
+ flow.opts.permanentErrors = [500];
+ var error = jasmine.createSpy('error');
+ var progress = jasmine.createSpy('progress');
+ var success = jasmine.createSpy('success');
+ var retry = jasmine.createSpy('retry');
+ flow.on('fileError', error);
+ flow.on('fileProgress', progress);
+ flow.on('fileSuccess', success);
+ flow.on('fileRetry', retry);
+
+ flow.addFile(new Blob(['12']));
+ var file = flow.files[0];
+ expect(file.chunks.length).toBe(2);
+ var firstChunk = file.chunks[0];
+ var secondChunk = file.chunks[1];
+ expect(firstChunk.status()).toBe('pending');
+ expect(secondChunk.status()).toBe('pending');
+
+ flow.upload();
+ expect(requests.length).toBe(1);
+ expect(firstChunk.status()).toBe('uploading');
+ expect(secondChunk.status()).toBe('pending');
+
+ expect(error).not.toHaveBeenCalled();
+ expect(progress).not.toHaveBeenCalled();
+ expect(success).not.toHaveBeenCalled();
+ expect(retry).not.toHaveBeenCalled();
+
+ requests[0].respond(400);
+ expect(requests.length).toBe(2);
+ expect(firstChunk.status()).toBe('uploading');
+ expect(secondChunk.status()).toBe('pending');
+
+ expect(error).not.toHaveBeenCalled();
+ expect(progress).not.toHaveBeenCalled();
+ expect(success).not.toHaveBeenCalled();
+ expect(retry).toHaveBeenCalled();
+
+ requests[1].respond(200);
+ expect(requests.length).toBe(3);
+ expect(firstChunk.status()).toBe('success');
+ expect(secondChunk.status()).toBe('uploading');
+
+ expect(error).not.toHaveBeenCalled();
+ expect(progress.calls.count()).toBe(1);
+ expect(success).not.toHaveBeenCalled();
+ expect(retry.calls.count()).toBe(1);
+
+ requests[2].respond(400);
+ expect(requests.length).toBe(4);
+ expect(firstChunk.status()).toBe('success');
+ expect(secondChunk.status()).toBe('uploading');
+
+ expect(error).not.toHaveBeenCalled();
+ expect(progress.calls.count()).toBe(1);
+ expect(success).not.toHaveBeenCalled();
+ expect(retry.calls.count()).toBe(2);
+
+ requests[3].respond(400, {}, 'Err');
+ expect(requests.length).toBe(4);
+ expect(file.chunks.length).toBe(0);
+
+ expect(error.calls.count()).toBe(1);
+ expect(error).toHaveBeenCalledWith(file, 'Err', secondChunk);
+ expect(progress.calls.count()).toBe(1);
+ expect(success).not.toHaveBeenCalled();
+ expect(retry.calls.count()).toBe(2);
+
+ expect(file.error).toBeTruthy();
+ expect(file.isComplete()).toBeTruthy();
+ expect(file.isUploading()).toBeFalsy();
+ expect(file.progress()).toBe(1);
+ });
+
+ it('should retry file with timeout', function () {
+ flow.opts.testChunks = false;
+ flow.opts.maxChunkRetries = 1;
+ flow.opts.chunkRetryInterval = 100;
+
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ var retry = jasmine.createSpy('retry');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+ flow.on('fileRetry', retry);
+
+ flow.addFile(new Blob(['12']));
+ var file = flow.files[0];
+ flow.upload();
+ expect(requests.length).toBe(1);
+
+ requests[0].respond(400);
+ expect(requests.length).toBe(1);
+ expect(error).not.toHaveBeenCalled();
+ expect(success).not.toHaveBeenCalled();
+ expect(retry).toHaveBeenCalled();
+ expect(file.chunks[0].status()).toBe('uploading');
+
+ jasmine.clock().tick(100);
+ expect(requests.length).toBe(2);
+ requests[1].respond(200);
+ expect(error).not.toHaveBeenCalled();
+ expect(success).toHaveBeenCalled();
+ expect(retry).toHaveBeenCalled();
+ });
+
+ it('should fail on permanent error', function () {
+ flow.opts.testChunks = false;
+ flow.opts.chunkSize = 1;
+ flow.opts.simultaneousUploads = 2;
+ flow.opts.maxChunkRetries = 1;
+ flow.opts.permanentErrors = [500];
+
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ var retry = jasmine.createSpy('retry');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+ flow.on('fileRetry', retry);
+
+ flow.addFile(new Blob(['abc']));
+ var file = flow.files[0];
+ expect(file.chunks.length).toBe(3);
+ flow.upload();
+ expect(requests.length).toBe(2);
+ requests[0].respond(500);
+ expect(requests.length).toBe(2);
+ expect(error).toHaveBeenCalled();
+ expect(retry).not.toHaveBeenCalled();
+ expect(success).not.toHaveBeenCalled();
+ });
+
+ it('should fail on permanent test error', function () {
+ flow.opts.testChunks = true;
+ flow.opts.chunkSize = 1;
+ flow.opts.simultaneousUploads = 2;
+ flow.opts.maxChunkRetries = 1;
+ flow.opts.permanentErrors = [500];
+
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ var retry = jasmine.createSpy('retry');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+ flow.on('fileRetry', retry);
+
+ flow.addFile(new Blob(['abc']));
+ flow.upload();
+ expect(requests.length).toBe(2);
+ requests[0].respond(500);
+ expect(requests.length).toBe(2);
+ expect(error).toHaveBeenCalled();
+ expect(retry).not.toHaveBeenCalled();
+ expect(success).not.toHaveBeenCalled();
+ });
+
+ it('should upload empty file', function () {
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+
+ flow.addFile(new Blob([]));
+
+ // https://github.com/flowjs/flow.js/issues/55
+ if (window.navigator.msPointerEnabled) {
+ expect(flow.files.length, 0);
+ } else {
+ expect(flow.files.length, 1);
+ var file = flow.files[0];
+ flow.upload();
+ expect(requests.length).toBe(1);
+ expect(file.progress()).toBe(0);
+ requests[0].respond(200);
+ expect(requests.length).toBe(1);
+ expect(error).not.toHaveBeenCalled();
+ expect(success).toHaveBeenCalled();
+ expect(file.progress()).toBe(1);
+ expect(file.isUploading()).toBe(false);
+ expect(file.isComplete()).toBe(true);
+ }
+ });
+
+ it('should not upload folder', function () {
+ // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
+ flow.addFile({
+ name: '.',
+ size: 0
+ });
+ expect(flow.files.length).toBe(0);
+ flow.addFile({
+ name: '.',
+ size: 4096
+ });
+ expect(flow.files.length).toBe(0);
+ flow.addFile({
+ name: '.',
+ size: 4096 * 2
+ });
+ expect(flow.files.length).toBe(0);
+ });
+
+ it('should preprocess chunks', function () {
+ var preprocess = jasmine.createSpy('preprocess');
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+ flow.opts.preprocess = preprocess;
+ flow.addFile(new Blob(['abc']));
+ var file = flow.files[0];
+ flow.upload();
+ expect(requests.length).toBe(0);
+ expect(preprocess).toHaveBeenCalledWith(file.chunks[0]);
+ expect(file.chunks[0].preprocessState).toBe(1);
+ file.chunks[0].preprocessFinished();
+ expect(requests.length).toBe(1);
+ requests[0].respond(200, [], "response");
+ expect(success).toHaveBeenCalledWith(file, "response", file.chunks[0]);
+ expect(error).not.toHaveBeenCalled();
+ });
+
+ it('should preprocess chunks and wait for preprocess to finish', function () {
+ flow.opts.simultaneousUploads = 1;
+ var preprocess = jasmine.createSpy('preprocess');
+ flow.opts.preprocess = preprocess;
+ flow.addFile(new Blob(['abc']));
+ flow.addFile(new Blob(['abca']));
+ var file = flow.files[0];
+ var secondFile = flow.files[1];
+ flow.upload();
+ expect(requests.length).toBe(0);
+ expect(preprocess).toHaveBeenCalledWith(file.chunks[0]);
+ expect(preprocess).not.toHaveBeenCalledWith(secondFile.chunks[0]);
+
+ flow.upload();
+ expect(preprocess).not.toHaveBeenCalledWith(secondFile.chunks[0]);
+ });
+
+ it('should resume preprocess chunks after pause', function () {
+ flow.opts.chunkSize = 1;
+ flow.opts.simultaneousUploads = 1;
+ flow.opts.testChunks = false;
+ var preprocess = jasmine.createSpy('preprocess');
+ var error = jasmine.createSpy('error');
+ var success = jasmine.createSpy('success');
+ flow.on('fileError', error);
+ flow.on('fileSuccess', success);
+ flow.opts.preprocess = preprocess;
+ flow.addFile(new Blob(['abc']));
+ var file = flow.files[0];
+ flow.upload();
+ for(var i=0; i