resumablejs
Advanced tools
Comparing version 1.0.2 to 1.1.0
{ | ||
"name": "resumable.js", | ||
"repo": "23/resumable.js", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"main": "resumable.js", | ||
"scripts": ["resumable.js"] | ||
} |
{ | ||
"name": "resumablejs", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "A JavaScript library for providing multiple simultaneous, stable, fault-tolerant and resumable/restartable uploads via the HTML5 File API.", | ||
@@ -5,0 +5,0 @@ "main": "resumable.js", |
## 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/). | ||
@@ -17,3 +17,3 @@ 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. | ||
var r = new Resumable({ | ||
target:'/api/photo/redeem-upload-token', | ||
target:'/api/photo/redeem-upload-token', | ||
query:{upload_token:'my_token'} | ||
@@ -23,3 +23,3 @@ }); | ||
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: | ||
@@ -30,2 +30,4 @@ | ||
It is recommended to use an HTML span for the browse button. Using an actual button does not work reliably across all browsers, because Resumable.js creates the file input as a child of this control, and this may be invalid in the case of an HTML button. | ||
After this, interaction with Resumable.js is done by listening to events: | ||
@@ -50,3 +52,3 @@ | ||
* `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. | ||
@@ -63,3 +65,3 @@ * `resumableTotalSize`: The total file size. | ||
* `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. | ||
@@ -84,20 +86,33 @@ | ||
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: `/`) | ||
* `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: `null`) | ||
* `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`) | ||
* `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: `{}`) | ||
* `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`) | ||
* `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 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`) | ||
* `generateUniqueIdentifier` Override the function that generates unique identifiers for each file. (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`) | ||
@@ -114,3 +129,4 @@ * `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.*) | ||
* `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`) | ||
* `xhrTimeout` The timeout in milliseconds for each request (Default: `0`) | ||
* `setChunkTypeFromFile` Set chunk content-type from original file.type. (Default: `false`, if `false` default Content-Type: `application/octet-stream`) | ||
#### Properties | ||
@@ -124,3 +140,3 @@ | ||
* `.assignBrowse(domNodes, isDirectory)` Assign a browse action to one or more DOM nodes. Pass in `true` to allow directories to be selected (Chrome only). | ||
* `.assignBrowse(domNodes, isDirectory)` Assign a browse action to one or more DOM nodes. Pass in `true` to allow directories to be selected (Chrome only). See the note above about using an HTML span instead of an actual button. | ||
* `.assignDrop(domNodes)` Assign one or more DOM nodes as a drop target. | ||
@@ -134,2 +150,3 @@ * `.on(event, callback)` Listen for event from Resumable.js (see below) | ||
* `.addFile(file)` Add a HTML5 File object to the list of files. | ||
* `.addFiles(files)` Add an Array of HTML5 File objects to the list of files. | ||
* `.removeFile(file)` Cancel upload of a specific `ResumableFile` object on the list from the list. | ||
@@ -141,6 +158,6 @@ * `.getFromUniqueIdentifier(uniqueIdentifier)` Look up a `ResumableFile` object by its unique identifier. | ||
* `.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. | ||
* `.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. | ||
@@ -153,2 +170,3 @@ * `.fileError(file, message)` An error occurred during upload of a specific file. | ||
* `.pause()` Uploading was paused. | ||
* `.beforeCancel()` Triggers before the items are cancelled allowing to do any processing on uploading files. | ||
* `.cancel()` Uploading was canceled. | ||
@@ -179,9 +197,8 @@ * `.chunkingStart(file)` Started preparing file for upload | ||
* `.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. | ||
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, something like [Plupload](http://plupload.com/) provides the same features with wider browser support. |
493
resumable.js
@@ -42,3 +42,12 @@ /* | ||
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:{}, | ||
@@ -52,2 +61,3 @@ headers:{}, | ||
target:'/', | ||
testTarget: null, | ||
parameterNamespace:'', | ||
@@ -57,3 +67,3 @@ testChunks:true, | ||
getTarget:null, | ||
maxChunkRetries:undefined, | ||
maxChunkRetries:100, | ||
chunkRetryInterval:undefined, | ||
@@ -64,2 +74,5 @@ permanentErrors:[400, 404, 415, 500, 501], | ||
xhrTimeout:0, | ||
clearInput:true, | ||
chunkFormat:'blob', | ||
setChunkTypeFromFile:false, | ||
maxFilesErrorCallback:function (files, errorCount) { | ||
@@ -110,4 +123,4 @@ var maxFiles = $.getOpt('maxFiles'); | ||
// 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 = []; | ||
@@ -151,6 +164,6 @@ $.on = function(event,callback){ | ||
}, | ||
generateUniqueIdentifier:function(file){ | ||
generateUniqueIdentifier:function(file, event){ | ||
var custom = $.getOpt('generateUniqueIdentifier'); | ||
if(typeof custom === 'function') { | ||
return custom(file); | ||
return custom(file, event); | ||
} | ||
@@ -185,13 +198,17 @@ var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox | ||
}, | ||
getTarget:function(params){ | ||
getTarget:function(request, params){ | ||
var target = $.getOpt('target'); | ||
if(typeof target === 'function') { | ||
if (request === 'test' && $.getOpt('testTarget')) { | ||
target = $.getOpt('testTarget') === '/' ? $.getOpt('target') : $.getOpt('testTarget'); | ||
} | ||
if (typeof target === 'function') { | ||
return target(params); | ||
} | ||
if(target.indexOf('?') < 0) { | ||
target += '?'; | ||
} else { | ||
target += '&'; | ||
} | ||
return target + params.join('&'); | ||
var separator = target.indexOf('?') < 0 ? '?' : '&'; | ||
var joinedParams = params.join('&'); | ||
return target + separator + joinedParams; | ||
} | ||
@@ -216,141 +233,110 @@ }; | ||
// 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 | ||
* 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 | ||
*/ | ||
var loadFiles = function (files, event, queue, path){ | ||
//initialize the queue object if it doesn't exist | ||
if (!queue) { | ||
queue = { | ||
total: 0, | ||
files: [], | ||
event: event | ||
}; | ||
function processItem(item, path, items, cb) { | ||
var entry; | ||
if(item.isFile){ | ||
// file provided | ||
return item.file(function(file){ | ||
file.relativePath = path + file.name; | ||
items.push(file); | ||
cb(); | ||
}); | ||
}else if(item.isDirectory){ | ||
// item is already a directory entry, just assign | ||
entry = item; | ||
}else if(item instanceof File) { | ||
items.push(item); | ||
} | ||
//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; | ||
if('function' === typeof item.webkitGetAsEntry){ | ||
// get entry from file object | ||
entry = item.webkitGetAsEntry(); | ||
} | ||
if(entry && entry.isDirectory){ | ||
// directory provided, process it | ||
return processDirectory(entry, path + entry.name + '/', items, cb); | ||
} | ||
if('function' === typeof item.getAsFile){ | ||
// item represents a File object, convert it | ||
item = item.getAsFile(); | ||
if(item instanceof File) { | ||
item.relativePath = path + item.name; | ||
items.push(item); | ||
} | ||
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; | ||
} | ||
} | ||
cb(); // indicate processing is done | ||
} | ||
if (!entry) { | ||
//there isn't anything we can do with this so decrement the total expected | ||
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 | ||
//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 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<entries.length; i++) { newEntries.push(entries[i]); } | ||
//call this function again as all the results may not have been sent yet | ||
readDir(entry.fullPath); | ||
} | ||
else { | ||
//we have now gotten all the results in newEntries so let's process them recursively | ||
loadFiles(newEntries, event, queue, path); | ||
//this was a directory rather than a file so decrement the expected file count | ||
updateQueueTotal(-1, queue); | ||
} | ||
}, | ||
//error callback, most often hit if there is a directory with nothing inside it | ||
function(err) { | ||
//this was a directory rather than a file so decrement the expected file count | ||
updateQueueTotal(-1, queue); | ||
console.warn(err); | ||
} | ||
); | ||
}; | ||
readDir(entry.fullPath); | ||
} | ||
/** | ||
* cps-style list iteration. | ||
* invokes all functions in list and waits for their callback to be | ||
* triggered. | ||
* @param {Function[]} items list of functions expecting callback parameter | ||
* @param {Function} cb callback to trigger after the last callback has been invoked | ||
*/ | ||
function processCallbacks(items, cb){ | ||
if(!items || items.length === 0){ | ||
// empty or no list, invoke callback | ||
return cb(); | ||
} | ||
}; | ||
// invoke current function, pass the next part as continuation | ||
items[0](function(){ | ||
processCallbacks(items.slice(1), cb); | ||
}); | ||
} | ||
/** | ||
* @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 | ||
* 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 | ||
*/ | ||
var updateQueueTotal = function(addition, queue){ | ||
queue.total += addition; | ||
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 | ||
); | ||
}); | ||
} | ||
// 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) | ||
* 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, 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); | ||
// If all the files we expect have shown up, then flush the queue. | ||
if (queue.files.length === queue.total) { | ||
appendFilesFromFileList(queue.files, queue.event); | ||
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); | ||
} | ||
} | ||
); | ||
}; | ||
@@ -371,19 +357,31 @@ | ||
} | ||
var files = []; | ||
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){ | ||
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.toLowerCase().indexOf(extension.toLowerCase(), fileName.length - extension.length) !== -1){ | ||
fileTypeFound = true; | ||
break; | ||
} | ||
} | ||
if (!fileTypeFound) { | ||
o.fileTypeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
} | ||
@@ -409,22 +407,28 @@ if (typeof(o.minFileSize)!=='undefined' && file.size<o.minFileSize) { | ||
},0); | ||
})()}; | ||
})()} else { | ||
filesSkipped.push(file); | ||
}; | ||
decreaseReamining(); | ||
} | ||
// directories have size == 0 | ||
var uniqueIdentifier = $h.generateUniqueIdentifier(file) | ||
if(uniqueIdentifier && typeof uniqueIdentifier.done === 'function' && typeof uniqueIdentifier.fail === 'function'){ | ||
var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); | ||
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) | ||
},0); | ||
}; | ||
@@ -442,3 +446,3 @@ | ||
$.size = file.size; | ||
$.relativePath = file.webkitRelativePath || file.relativePath || $.fileName; | ||
$.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; | ||
$.uniqueIdentifier = uniqueIdentifier; | ||
@@ -454,3 +458,3 @@ $._pause = false; | ||
case 'progress': | ||
$.resumableObj.fire('fileProgress', $); | ||
$.resumableObj.fire('fileProgress', $, message); | ||
break; | ||
@@ -639,13 +643,29 @@ case 'error': | ||
// 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 = 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.open($.getOpt('testMethod'), $h.getTarget('test', params)); | ||
$.xhr.timeout = $.getOpt('xhrTimeout'); | ||
@@ -723,13 +743,22 @@ $.xhr.withCredentials = $.getOpt('withCredentials'); | ||
// 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 = [ | ||
['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 | ||
@@ -742,29 +771,40 @@ var customQuery = $.getOpt('query'); | ||
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, $.getOpt('setChunkTypeFromFile') ? $.fileObj.file.type : ""); | ||
var data = null; | ||
var params = []; | ||
var parameterNamespace = $.getOpt('parameterNamespace'); | ||
if ($.getOpt('method') === 'octet') { | ||
// Add data from the query options | ||
data = bytes; | ||
var params = []; | ||
$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); | ||
}); | ||
data.append(parameterNamespace+$.getOpt('fileParameterName'), bytes); | ||
} | ||
if ($.getOpt('method') === 'octet') { | ||
// Add data from the query options | ||
data = bytes; | ||
$h.each(query, function (k, v) { | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
} else { | ||
// Add data from the query options | ||
data = new FormData(); | ||
$h.each(query, function (k, v) { | ||
data.append(parameterNamespace + k, v); | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), bytes, $.fileObj.fileName); | ||
} | ||
else if ($.getOpt('chunkFormat') == 'base64') { | ||
var fr = new FileReader(); | ||
fr.onload = function (e) { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), fr.result); | ||
$.xhr.send(data); | ||
} | ||
fr.readAsDataURL(bytes); | ||
} | ||
} | ||
var target = $h.getTarget('upload', params); | ||
var method = $.getOpt('uploadMethod'); | ||
$.xhr.open(method, target); | ||
if ($.getOpt('method') === 'octet') { | ||
$.xhr.setRequestHeader('Content-Type', 'binary/octet-stream'); | ||
$.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); | ||
} | ||
@@ -778,6 +818,10 @@ $.xhr.timeout = $.getOpt('xhrTimeout'); | ||
} | ||
$h.each(customHeaders, function(k,v) { | ||
$.xhr.setRequestHeader(k, v); | ||
}); | ||
$.xhr.send(data); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
$.xhr.send(data); | ||
} | ||
}; | ||
@@ -802,3 +846,3 @@ $.abort = function(){ | ||
if($.xhr.status == 200 || $.xhr.status == 201) { | ||
// HTTP 200 or 201 (created) perfect | ||
// HTTP 200, 201 (created) | ||
return('success'); | ||
@@ -823,2 +867,3 @@ } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) { | ||
if($.pendingRetry) return(0); | ||
if(!$.xhr || !$.xhr.status) factor*=.95; | ||
var s = $.status(); | ||
@@ -924,6 +969,16 @@ switch(s){ | ||
} | ||
var fileTypes = $.getOpt('fileType'); | ||
if (typeof (fileTypes) !== 'undefined' && fileTypes.length >= 1) { | ||
input.setAttribute('accept', fileTypes.map(function (e) { return '.' + e }).join(',')); | ||
} | ||
else { | ||
input.removeAttribute('accept'); | ||
} | ||
// When new files are added, simply append them to the overall list | ||
input.addEventListener('change', function(e){ | ||
appendFilesFromFileList(e.target.files,e); | ||
e.target.value = ''; | ||
var clearInput = $.getOpt('clearInput'); | ||
if (clearInput) { | ||
e.target.value = ''; | ||
} | ||
}, false); | ||
@@ -977,2 +1032,3 @@ }); | ||
$.cancel = function(){ | ||
$.fire('beforeCancel'); | ||
for(var i = $.files.length - 1; i >= 0; i--) { | ||
@@ -996,2 +1052,5 @@ $.files[i].cancel(); | ||
}; | ||
$.addFiles = function(files, event){ | ||
appendFilesFromFileList(files, event); | ||
}; | ||
$.removeFile = function(file){ | ||
@@ -1018,2 +1077,12 @@ for(var i = $.files.length - 1; i >= 0; i--) { | ||
}; | ||
$.handleDropEvent = function (e) { | ||
onDrop(e); | ||
}; | ||
$.handleChangeEvent = function (e) { | ||
appendFilesFromFileList(e.target.files, e); | ||
e.target.value = ''; | ||
}; | ||
$.updateQuery = function(query){ | ||
$.opts.query = query; | ||
}; | ||
@@ -1020,0 +1089,0 @@ return(this); |
@@ -24,2 +24,5 @@ # Sample server implementation in PHP | ||
* @email www.online.php@gmail.com | ||
* | ||
* @editor Bivek Joshi (http://www.bivekjoshi.com.np) | ||
* @email meetbivek@gmail.com | ||
*/ | ||
@@ -82,3 +85,3 @@ | ||
*/ | ||
function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize) { | ||
function createFileFromChunks($temp_dir, $fileName, $chunkSize, $totalSize,$total_files) { | ||
@@ -127,14 +130,20 @@ // count all the parts of this file | ||
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 | ||
@@ -151,3 +160,5 @@ if (!empty($_FILES)) foreach ($_FILES as $file) { | ||
// 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']; | ||
@@ -164,6 +175,4 @@ | ||
} 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']); | ||
} | ||
@@ -170,0 +179,0 @@ } |
@@ -51,2 +51,4 @@ # Sample server implementation in Ruby on Rails | ||
FileUtils.mkdir(dir, :mode => 0700) | ||
elsif params[:resumableChunkNumber].to_i == 1 | ||
FileUtils.rm_rf Dir.glob("#{dir}/*") | ||
end | ||
@@ -80,4 +82,5 @@ | ||
end | ||
puts "File saved to #{dir}/#{params[:resumableFilename]}" | ||
end | ||
#You can use the file now | ||
puts "File saved to #{dir}/#{params[:resumableFilename]}" | ||
end | ||
@@ -84,0 +87,0 @@ |
@@ -1,800 +0,1071 @@ | ||
//@ sourceMappingURL=resumable.map | ||
// Generated by CoffeeScript 1.6.1 | ||
(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; }; | ||
/* | ||
* MIT Licensed | ||
* http://www.23developer.com/opensource | ||
* http://github.com/23/resumable.js | ||
* Steffen Tiedemann Christensen, steffen@23company.com | ||
*/ | ||
window.Resumable = Resumable = (function() { | ||
(function(){ | ||
"use strict"; | ||
function Resumable(opt) { | ||
this.opt = opt; | ||
console.log('constructor'); | ||
this.support = (typeof File !== "undefined" && File !== null) && (typeof Blob !== "undefined" && Blob !== null) && (typeof FileList !== "undefined" && FileList !== null) && ((Blob.prototype.webkitSlice != null) || (Blob.prototype.mozSlice != null) || (Blob.prototype.slice != null)); | ||
this.files = []; | ||
this.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: void 0, | ||
chunkRetryInterval: void 0, | ||
permanentErrors: [415, 500, 501], | ||
maxFiles: void 0, | ||
maxFilesErrorCallback: function(files, errorCount) { | ||
var maxFiles, _ref; | ||
maxFiles = this.getOpt('maxFiles'); | ||
return alert('Please upload ' + maxFiles + ' file' + ((_ref = maxFiles === 1) != null ? _ref : { | ||
'': 's' | ||
}) + ' at a time.'); | ||
}, | ||
minFileSize: void 0, | ||
minFileSizeErrorCallback: function(file, errorCount) { | ||
return alert(file.fileName(+' is too small, please upload files larger than ' + this.formatSize(this.getOpt('minFileSize')) + '.')); | ||
}, | ||
maxFileSize: void 0, | ||
maxFileSizeErrorCallback: function(file, errorCount) { | ||
return alert(file.fileName(+' is too large, please upload files less than ' + this.formatSize(this.getOpt('maxFileSize')) + '.')); | ||
} | ||
}; | ||
if (this.opt == null) { | ||
this.opt = {}; | ||
} | ||
this.events = []; | ||
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); | ||
Resumable.prototype.getOpt = function(o) { | ||
var item, opts, _i, _len; | ||
if (o instanceof Array) { | ||
opts = {}; | ||
for (_i = 0, _len = o.length; _i < _len; _i++) { | ||
item = o[_i]; | ||
opts[item] = this.getOpt(item); | ||
} | ||
return opts; | ||
} else { | ||
if (this.opt[o] != null) { | ||
return this.opt[o]; | ||
} else { | ||
return this.defaults[o]; | ||
} | ||
} | ||
}; | ||
Resumable.prototype.formatSize = function(size) { | ||
if (size < 1024) { | ||
return size + ' bytes'; | ||
} else if (size < 1024 * 1024) { | ||
return (size / 1024.0).toFixed(0) + ' KB'; | ||
} else if (size < 1024 * 1024 * 1024) { | ||
return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'; | ||
} else { | ||
return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'; | ||
// PROPERTIES | ||
var $ = this; | ||
$.files = []; | ||
$.defaults = { | ||
chunkSize:1*1024*1024, | ||
forceChunkSize:false, | ||
simultaneousUploads:3, | ||
fileParameterName:'file', | ||
chunkNumberParameterName: 'resumableChunkNumber', | ||
chunkSizeParameterName: 'resumableChunkSize', | ||
currentChunkSizeParameterName: 'resumableCurrentChunkSize', | ||
totalSizeParameterName: 'resumableTotalSize', | ||
typeParameterName: 'resumableType', | ||
identifierParameterName: 'resumableIdentifier', | ||
fileNameParameterName: 'resumableFilename', | ||
relativePathParameterName: 'resumableRelativePath', | ||
totalChunksParameterName: 'resumableTotalChunks', | ||
throttleProgressCallbacks: 0.5, | ||
query:{}, | ||
headers:{}, | ||
preprocess:null, | ||
method:'multipart', | ||
uploadMethod: 'POST', | ||
testMethod: 'GET', | ||
prioritizeFirstAndLastChunk:false, | ||
target:'/', | ||
testTarget: null, | ||
parameterNamespace:'', | ||
testChunks:true, | ||
generateUniqueIdentifier:null, | ||
getTarget:null, | ||
maxChunkRetries:100, | ||
chunkRetryInterval:undefined, | ||
permanentErrors:[400, 404, 415, 500, 501], | ||
maxFiles:undefined, | ||
withCredentials:false, | ||
xhrTimeout:0, | ||
clearInput:true, | ||
chunkFormat:'blob', | ||
maxFilesErrorCallback:function (files, errorCount) { | ||
var maxFiles = $.getOpt('maxFiles'); | ||
alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); | ||
}, | ||
minFileSize:1, | ||
minFileSizeErrorCallback:function(file, errorCount) { | ||
alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); | ||
}, | ||
maxFileSize:undefined, | ||
maxFileSizeErrorCallback:function(file, errorCount) { | ||
alert(file.fileName||file.name +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); | ||
}, | ||
fileType: [], | ||
fileTypeErrorCallback: function(file, errorCount) { | ||
alert(file.fileName||file.name +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); | ||
} | ||
}; | ||
Resumable.prototype.stopEvent = function(e) { | ||
console.log('stopEvent'); | ||
e.stopPropagation(); | ||
return e.preventDefault(); | ||
}; | ||
Resumable.prototype.generateUniqueIdentifier = function(file) { | ||
var custom, relativePath, size; | ||
console.log('generateUniqueIdentifier'); | ||
custom = this.getOpt('generateUniqueIdentifier'); | ||
if (typeof custom === 'function') { | ||
return custom(file); | ||
} else { | ||
relativePath = file.webkitRelativePath || file.fileName || file.name; | ||
size = file.size; | ||
return size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, ''); | ||
$.opts = opts||{}; | ||
$.getOpt = function(o) { | ||
var $opt = this; | ||
// Get multiple option if passed an array | ||
if(o instanceof Array) { | ||
var options = {}; | ||
$h.each(o, function(option){ | ||
options[option] = $opt.getOpt(option); | ||
}); | ||
return options; | ||
} | ||
}; | ||
Resumable.prototype.on = function(event, callback) { | ||
console.log("on: " + event); | ||
return this.events.push({ | ||
event: event, | ||
callback: callback | ||
}); | ||
}; | ||
Resumable.prototype.fire = function() { | ||
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]; | ||
if (e.event.toLowerCase() === event) { | ||
e.callback.apply(this, args.slice(1)); | ||
} | ||
if (e.event.toLowerCase() === 'catchall') { | ||
e.callback.apply(null, args); | ||
} | ||
// Otherwise, just return a simple option | ||
if ($opt instanceof ResumableChunk) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { $opt = $opt.fileObj; } | ||
} | ||
if (event === 'fireerror') { | ||
this.fire('error', args[2], args[1]); | ||
if ($opt instanceof ResumableFile) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { $opt = $opt.resumableObj; } | ||
} | ||
if (event === 'fileprogress') { | ||
return this.fire('progress'); | ||
if ($opt instanceof Resumable) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { return $opt.defaults[o]; } | ||
} | ||
}; | ||
Resumable.prototype.onDrop = function(event) { | ||
console.log("onDrop"); | ||
this.stopEvent(event); | ||
return this.appendFilesFromFileList(event.dataTransfer.files, event); | ||
// EVENTS | ||
// catchAll(event, ...) | ||
// fileSuccess(file), fileProgress(file), fileAdded(file, event), filesAdded(files, filesSkipped), fileRetry(file), | ||
// fileError(file, message), complete(), progress(), error(message, file), pause() | ||
$.events = []; | ||
$.on = function(event,callback){ | ||
$.events.push(event.toLowerCase(), callback); | ||
}; | ||
Resumable.prototype.onDragOver = function(event) { | ||
console.log("onDragOver"); | ||
return event.preventDefault(); | ||
}; | ||
Resumable.prototype.appendFilesFromFileList = function(fileList, event) { | ||
var errorCount, file, files, maxFileSize, maxFileSizeErrorCallback, maxFiles, maxFilesErrorCallback, minFileSize, minFileSizeErrorCallback, resumableFile, _i, _len, _ref; | ||
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]; | ||
if ((maxFiles != null) && maxFiles < (fileList.length + this.files.length)) { | ||
maxFilesErrorCallback(fileList, errorCount++); | ||
return false; | ||
$.fire = function(){ | ||
// `arguments` is an object, not array, in FF, so: | ||
var args = []; | ||
for (var i=0; i<arguments.length; i++) args.push(arguments[i]); | ||
// Find event listeners, and support pseudo-event `catchAll` | ||
var event = args[0].toLowerCase(); | ||
for (var i=0; i<=$.events.length; i+=2) { | ||
if($.events[i]==event) $.events[i+1].apply($,args.slice(1)); | ||
if($.events[i]=='catchall') $.events[i+1].apply(null,args); | ||
} | ||
files = []; | ||
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++); | ||
return false; | ||
} | ||
if ((maxFileSize != null) && file.size > maxFileSize) { | ||
maxFilesErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
if (file.size > 0 && !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file))) { | ||
resumableFile = new ResumableFile(this, file); | ||
this.files.push(resumableFile); | ||
files.push(resumableFile); | ||
this.fire('fileAdded', resumableFile, event); | ||
} | ||
} | ||
return this.fire('fileAdded', files); | ||
if(event=='fileerror') $.fire('error', args[2], args[1]); | ||
if(event=='fileprogress') $.fire('progress'); | ||
}; | ||
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; | ||
console.log("uploadNextChunk"); | ||
found = false; | ||
if (this.getOpt('prioritizeFirstAndLastChunk')) { | ||
_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; | ||
break; | ||
// INTERNAL HELPER METHODS (handy, but ultimately not part of uploading) | ||
var $h = { | ||
stopEvent: function(e){ | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
}, | ||
each: function(o,callback){ | ||
if(typeof(o.length)!=='undefined') { | ||
for (var i=0; i<o.length; i++) { | ||
// Array or FileList | ||
if(callback(o[i])===false) return; | ||
} | ||
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; | ||
break; | ||
} else { | ||
for (i in o) { | ||
// Object | ||
if(callback(i,o[i])===false) return; | ||
} | ||
} | ||
if (found) { | ||
return true; | ||
}, | ||
generateUniqueIdentifier:function(file, event){ | ||
var custom = $.getOpt('generateUniqueIdentifier'); | ||
if(typeof custom === 'function') { | ||
return custom(file, event); | ||
} | ||
} | ||
_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; | ||
break; | ||
var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox | ||
var size = file.size; | ||
return(size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')); | ||
}, | ||
contains:function(array,test) { | ||
var result = false; | ||
$h.each(array, function(value) { | ||
if (value == test) { | ||
result = true; | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return result; | ||
}, | ||
formatSize:function(size){ | ||
if(size<1024) { | ||
return size + ' bytes'; | ||
} else if(size<1024*1024) { | ||
return (size/1024.0).toFixed(0) + ' KB'; | ||
} else if(size<1024*1024*1024) { | ||
return (size/1024.0/1024.0).toFixed(1) + ' MB'; | ||
} else { | ||
return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB'; | ||
} | ||
if (found) { | ||
break; | ||
}, | ||
getTarget:function(request, params){ | ||
var target = $.getOpt('target'); | ||
if (request === 'test' && $.getOpt('testTarget')) { | ||
target = $.getOpt('testTarget') === '/' ? $.getOpt('target') : $.getOpt('testTarget'); | ||
} | ||
} | ||
if (found) { | ||
return true; | ||
} | ||
_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]; | ||
status = chunk.status(); | ||
if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) { | ||
outstanding = true; | ||
break; | ||
} | ||
if (typeof target === 'function') { | ||
return target(params); | ||
} | ||
if (outstanding) { | ||
break; | ||
} | ||
var separator = target.indexOf('?') < 0 ? '?' : '&'; | ||
var joinedParams = params.join('&'); | ||
return target + separator + joinedParams; | ||
} | ||
if (!outstanding) { | ||
this.fire('complete'); | ||
} | ||
return false; | ||
}; | ||
Resumable.prototype.assignBrowse = function(domNodes, isDirectory) { | ||
var changeHandler, dn, input, maxFiles, _i, _len, | ||
_this = this; | ||
console.log("assignBrowse"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
var onDrop = function(event){ | ||
$h.stopEvent(event); | ||
//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); | ||
} | ||
for (_i = 0, _len = domNodes.length; _i < _len; _i++) { | ||
dn = domNodes[_i]; | ||
if (dn.tagName === 'INPUT' && dn.type === 'file') { | ||
input = dn; | ||
} else { | ||
input = document.createElement('input'); | ||
input.setAttribute('type', 'file'); | ||
dn.style.display = 'inline-block'; | ||
dn.style.position = 'relative'; | ||
input.style.position = 'absolute'; | ||
input.style.top = input.style.left = input.style.bottom = input.style.right = 0; | ||
input.style.opacity = 0; | ||
input.style.cursor = 'pointer'; | ||
dn.appendChild(input); | ||
} | ||
//else handle them as files | ||
else if (event.dataTransfer && event.dataTransfer.files) { | ||
loadFiles(event.dataTransfer.files, event); | ||
} | ||
maxFiles = this.getOpt('maxFiles'); | ||
if ((maxFiles != null) || maxFiles !== 1) { | ||
input.setAttribute('multiple', 'multiple'); | ||
} else { | ||
input.removeAttribute('multiple'); | ||
} | ||
if (isDirectory) { | ||
input.setAttribute('webkitdirectory', 'webkitdirectory'); | ||
} else { | ||
input.removeAttribute('webkitdirectory'); | ||
} | ||
changeHandler = function(e) { | ||
_this.appendFilesFromFileList(e.target.files); | ||
return e.target.value = ''; | ||
}; | ||
return input.addEventListener('change', changeHandler, false); | ||
}; | ||
var preventDefault = function(e) { | ||
e.preventDefault(); | ||
}; | ||
Resumable.prototype.assignDrop = function(domNodes) { | ||
var dn, _i, _len, _results; | ||
console.log("assignDrop"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
/** | ||
* processes a single upload item (file or directory) | ||
* @param {Object} item item to upload, may be file or directory entry | ||
* @param {string} path current file path | ||
* @param {File[]} items list of files to append new items to | ||
* @param {Function} cb callback invoked when item is processed | ||
*/ | ||
function processItem(item, path, items, cb) { | ||
var entry; | ||
if(item.isFile){ | ||
// file provided | ||
return item.file(function(file){ | ||
file.relativePath = path + file.name; | ||
items.push(file); | ||
cb(); | ||
}); | ||
}else if(item.isDirectory){ | ||
// item is already a directory entry, just assign | ||
entry = item; | ||
}else if(item instanceof File) { | ||
items.push(item); | ||
} | ||
_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)); | ||
if('function' === typeof item.webkitGetAsEntry){ | ||
// get entry from file object | ||
entry = item.webkitGetAsEntry(); | ||
} | ||
return _results; | ||
}; | ||
Resumable.prototype.unAssignDrop = function(domNodes) { | ||
var dn, _i, _len, _results; | ||
console.log("unAssignDrop"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
if(entry && entry.isDirectory){ | ||
// directory provided, process it | ||
return processDirectory(entry, path + entry.name + '/', items, cb); | ||
} | ||
_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)); | ||
if('function' === typeof item.getAsFile){ | ||
// item represents a File object, convert it | ||
item = item.getAsFile(); | ||
item.relativePath = path + item.name; | ||
items.push(item); | ||
} | ||
return _results; | ||
}; | ||
cb(); // indicate processing is done | ||
} | ||
Resumable.prototype.isUploading = function() { | ||
var chunk, file, uploading, _i, _j, _len, _len1, _ref, _ref1; | ||
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]; | ||
if (chunk.status() === 'uploading') { | ||
uploading = true; | ||
break; | ||
} | ||
} | ||
if (uploading) { | ||
break; | ||
} | ||
} | ||
return uploading; | ||
}; | ||
Resumable.prototype.upload = function() { | ||
var num, _i, _ref, _results; | ||
console.log("upload"); | ||
if (this.isUploading()) { | ||
return; | ||
/** | ||
* 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(); | ||
} | ||
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()); | ||
} | ||
return _results; | ||
}; | ||
// invoke current function, pass the next part as continuation | ||
items[0](function(){ | ||
processCallbacks(items.slice(1), cb); | ||
}); | ||
} | ||
Resumable.prototype.pause = function() { | ||
var file, _i, _len, _ref; | ||
console.log("pause"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
file.abort(); | ||
} | ||
return this.fire('pause'); | ||
}; | ||
/** | ||
* 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 | ||
); | ||
}); | ||
} | ||
Resumable.prototype.cancel = function() { | ||
var file, _i, _len, _ref; | ||
console.log("cancel"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
file.cancel(); | ||
/** | ||
* process items to extract files to be uploaded | ||
* @param {File[]} items items to process | ||
* @param {Event} event event that led to upload | ||
*/ | ||
function loadFiles(items, event) { | ||
if(!items.length){ | ||
return; // nothing to do | ||
} | ||
return this.fire('cancel'); | ||
$.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); | ||
} | ||
} | ||
); | ||
}; | ||
Resumable.prototype.progress = function() { | ||
var file, totalDone, totalSize, _i, _len, _ref; | ||
console.log("progress"); | ||
totalDone = 0; | ||
totalSize = 0; | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
totalDone += file.progress() * file.size; | ||
totalSize += file.size; | ||
var appendFilesFromFileList = function(fileList, event){ | ||
// check for uploading too many files | ||
var errorCount = 0; | ||
var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']); | ||
if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) { | ||
// if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file | ||
if (o.maxFiles===1 && $.files.length===1 && fileList.length===1) { | ||
$.removeFile($.files[0]); | ||
} else { | ||
o.maxFilesErrorCallback(fileList, errorCount++); | ||
return false; | ||
} | ||
} | ||
return (totalSize > 0 ? totalDone / totalSize : 0); | ||
}; | ||
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){ | ||
var fileTypeFound = false; | ||
for(var index in o.fileType){ | ||
var extension = '.' + o.fileType[index]; | ||
if(fileName.toLowerCase().indexOf(extension.toLowerCase(), fileName.length - extension.length) !== -1){ | ||
fileTypeFound = true; | ||
break; | ||
} | ||
} | ||
if (!fileTypeFound) { | ||
o.fileTypeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
} | ||
Resumable.prototype.addFile = function(file) { | ||
console.log("addFile"); | ||
return this.appendFilesFromFileList([file]); | ||
}; | ||
if (typeof(o.minFileSize)!=='undefined' && file.size<o.minFileSize) { | ||
o.minFileSizeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
if (typeof(o.maxFileSize)!=='undefined' && file.size>o.maxFileSize) { | ||
o.maxFileSizeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
Resumable.prototype.removeFile = function(file) { | ||
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]; | ||
if (f !== file) { | ||
files.push(f); | ||
function addFile(uniqueIdentifier){ | ||
if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ | ||
file.uniqueIdentifier = uniqueIdentifier; | ||
var f = new ResumableFile($, file, uniqueIdentifier); | ||
$.files.push(f); | ||
files.push(f); | ||
f.container = (typeof event != 'undefined' ? event.srcElement : null); | ||
window.setTimeout(function(){ | ||
$.fire('fileAdded', f, event) | ||
},0); | ||
})()} else { | ||
filesSkipped.push(file); | ||
}; | ||
decreaseReamining(); | ||
} | ||
} | ||
return this.files = files; | ||
// directories have size == 0 | ||
var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); | ||
if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ | ||
// Promise or Promise-like object provided as unique identifier | ||
uniqueIdentifier | ||
.then( | ||
function(uniqueIdentifier){ | ||
// unique identifier generation succeeded | ||
addFile(uniqueIdentifier); | ||
}, | ||
function(){ | ||
// unique identifier generation failed | ||
// skip further processing, only decrease file count | ||
decreaseReamining(); | ||
} | ||
); | ||
}else{ | ||
// non-Promise provided as unique identifier, process synchronously | ||
addFile(uniqueIdentifier); | ||
} | ||
}); | ||
}; | ||
Resumable.prototype.getFromUniqueIdentifier = function(uniqueIdentifier) { | ||
var f, _i, _len, _ref; | ||
console.log("getFromUniqueIdentifier"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
f = _ref[_i]; | ||
if (f.uniqueIdentifier === uniqueIdentifier) { | ||
return f; | ||
// INTERNAL OBJECT TYPES | ||
function ResumableFile(resumableObj, file, uniqueIdentifier){ | ||
var $ = this; | ||
$.opts = {}; | ||
$.getOpt = resumableObj.getOpt; | ||
$._prevProgress = 0; | ||
$.resumableObj = resumableObj; | ||
$.file = file; | ||
$.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox | ||
$.size = file.size; | ||
$.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; | ||
$.uniqueIdentifier = uniqueIdentifier; | ||
$._pause = false; | ||
$.container = ''; | ||
var _error = uniqueIdentifier !== undefined; | ||
// Callback when something happens within the chunk | ||
var chunkEvent = function(event, message){ | ||
// event can be 'progress', 'success', 'error' or 'retry' | ||
switch(event){ | ||
case 'progress': | ||
$.resumableObj.fire('fileProgress', $, message); | ||
break; | ||
case 'error': | ||
$.abort(); | ||
_error = true; | ||
$.chunks = []; | ||
$.resumableObj.fire('fileError', $, message); | ||
break; | ||
case 'success': | ||
if(_error) return; | ||
$.resumableObj.fire('fileProgress', $); // it's at least progress | ||
if($.isComplete()) { | ||
$.resumableObj.fire('fileSuccess', $, message); | ||
} | ||
break; | ||
case 'retry': | ||
$.resumableObj.fire('fileRetry', $); | ||
break; | ||
} | ||
} | ||
return false; | ||
}; | ||
}; | ||
Resumable.prototype.getSize = function() { | ||
var file, totalSize, _i, _len, _ref; | ||
console.log("getSize"); | ||
totalSize = 0; | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
totalSize += file.size; | ||
} | ||
return totalSize; | ||
}; | ||
// Main code to set up a file object with chunks, | ||
// packaged to be able to handle retries if needed. | ||
$.chunks = []; | ||
$.abort = function(){ | ||
// Stop current uploads | ||
var abortCount = 0; | ||
$h.each($.chunks, function(c){ | ||
if(c.status()=='uploading') { | ||
c.abort(); | ||
abortCount++; | ||
} | ||
}); | ||
if(abortCount>0) $.resumableObj.fire('fileProgress', $); | ||
}; | ||
$.cancel = function(){ | ||
// Reset this file to be void | ||
var _chunks = $.chunks; | ||
$.chunks = []; | ||
// Stop current uploads | ||
$h.each(_chunks, function(c){ | ||
if(c.status()=='uploading') { | ||
c.abort(); | ||
$.resumableObj.uploadNextChunk(); | ||
} | ||
}); | ||
$.resumableObj.removeFile($); | ||
$.resumableObj.fire('fileProgress', $); | ||
}; | ||
$.retry = function(){ | ||
$.bootstrap(); | ||
var firedRetry = false; | ||
$.resumableObj.on('chunkingComplete', function(){ | ||
if(!firedRetry) $.resumableObj.upload(); | ||
firedRetry = true; | ||
}); | ||
}; | ||
$.bootstrap = function(){ | ||
$.abort(); | ||
_error = false; | ||
// Rebuild stack of chunks from file | ||
$.chunks = []; | ||
$._prevProgress = 0; | ||
var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; | ||
var maxOffset = Math.max(round($.file.size/$.getOpt('chunkSize')),1); | ||
for (var offset=0; offset<maxOffset; offset++) {(function(offset){ | ||
window.setTimeout(function(){ | ||
$.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent)); | ||
$.resumableObj.fire('chunkingProgress',$,offset/maxOffset); | ||
},0); | ||
})(offset)} | ||
window.setTimeout(function(){ | ||
$.resumableObj.fire('chunkingComplete',$); | ||
},0); | ||
}; | ||
$.progress = function(){ | ||
if(_error) return(1); | ||
// Sum up progress across everything | ||
var ret = 0; | ||
var error = false; | ||
$h.each($.chunks, function(c){ | ||
if(c.status()=='error') error = true; | ||
ret += c.progress(true); // get chunk progress relative to entire file | ||
}); | ||
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); | ||
}; | ||
$.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); | ||
}; | ||
$.pause = function(pause){ | ||
if(typeof(pause)==='undefined'){ | ||
$._pause = ($._pause ? false : true); | ||
}else{ | ||
$._pause = pause; | ||
} | ||
}; | ||
$.isPaused = function() { | ||
return $._pause; | ||
}; | ||
return Resumable; | ||
})(); | ||
// Bootstrap and return | ||
$.resumableObj.fire('chunkingStart', $); | ||
$.bootstrap(); | ||
return(this); | ||
} | ||
window.ResumableChunk = ResumableChunk = (function() { | ||
function ResumableChunk(resumableObj, fileObj, offset, callback) { | ||
this.resumableObj = resumableObj; | ||
this.fileObj = fileObj; | ||
this.offset = offset; | ||
this.callback = callback; | ||
this.opt = {}; | ||
this.fileObjSize = this.fileObj.size; | ||
this.lastProgressCallback = new Date; | ||
this.tested = false; | ||
this.retries = 0; | ||
this.preprocessState = 0; | ||
this.chunkSize = this.getOpt('chunkSize'); | ||
this.loaded = 0; | ||
this.startByte = this.offset * this.chunkSize; | ||
this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * this.chunkSize); | ||
if ((this.fileObjSize - this.endByte < this.chunkSize) && (!this.getOpt('forceChunkSize'))) { | ||
this.endByte = this.fileObjSize; | ||
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; | ||
} | ||
this.xhr = null; | ||
} | ||
$.xhr = null; | ||
ResumableChunk.prototype.getOpt = function(o) { | ||
return this.resumableObj.getOpt(o); | ||
}; | ||
// 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(); | ||
ResumableChunk.prototype.pushParams = function(params, key, value) { | ||
return params.push([encodeURIComponent(key), encodeURIComponent(value)].join('=')); | ||
}; | ||
var testHandler = function(e){ | ||
$.tested = true; | ||
var status = $.status(); | ||
if(status=='success') { | ||
$.callback(status, $.message()); | ||
$.resumableObj.uploadNextChunk(); | ||
} else { | ||
$.send(); | ||
} | ||
}; | ||
$.xhr.addEventListener('load', testHandler, false); | ||
$.xhr.addEventListener('error', testHandler, false); | ||
$.xhr.addEventListener('timeout', testHandler, false); | ||
ResumableChunk.prototype.test = function() { | ||
var customQuery, headers, key, params, testHandler, value, | ||
_this = this; | ||
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(); | ||
// Add data from the query options | ||
var params = []; | ||
var parameterNamespace = $.getOpt('parameterNamespace'); | ||
var customQuery = $.getOpt('query'); | ||
if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); | ||
$h.each(customQuery, function(k,v){ | ||
params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); | ||
}); | ||
// Add extra data to identify chunk | ||
params = params.concat( | ||
[ | ||
// define key/value pairs for additional parameters | ||
['chunkNumberParameterName', $.offset + 1], | ||
['chunkSizeParameterName', $.getOpt('chunkSize')], | ||
['currentChunkSizeParameterName', $.endByte - $.startByte], | ||
['totalSizeParameterName', $.fileObjSize], | ||
['typeParameterName', $.fileObjType], | ||
['identifierParameterName', $.fileObj.uniqueIdentifier], | ||
['fileNameParameterName', $.fileObj.fileName], | ||
['relativePathParameterName', $.fileObj.relativePath], | ||
['totalChunksParameterName', $.fileObj.chunks.length] | ||
].filter(function(pair){ | ||
// include items that resolve to truthy values | ||
// i.e. exclude false, null, undefined and empty strings | ||
return $.getOpt(pair[0]); | ||
}) | ||
.map(function(pair){ | ||
// map each key/value pair to its final form | ||
return [ | ||
parameterNamespace + $.getOpt(pair[0]), | ||
encodeURIComponent(pair[1]) | ||
].join('='); | ||
}) | ||
); | ||
// Append the relevant chunk and send it | ||
$.xhr.open($.getOpt('testMethod'), $h.getTarget('test', params)); | ||
$.xhr.timeout = $.getOpt('xhrTimeout'); | ||
$.xhr.withCredentials = $.getOpt('withCredentials'); | ||
// Add data from header options | ||
var customHeaders = $.getOpt('headers'); | ||
if(typeof customHeaders === 'function') { | ||
customHeaders = customHeaders($.fileObj, $); | ||
} | ||
$h.each(customHeaders, function(k,v) { | ||
$.xhr.setRequestHeader(k, v); | ||
}); | ||
$.xhr.send(null); | ||
}; | ||
this.xhr.addEventListener('load', testHandler, false); | ||
this.xhr.addEventListener('error', testHandler, false); | ||
params = []; | ||
customQuery = this.getOpt('query'); | ||
if (typeof customQuery === 'function') { | ||
customQuery = customQuery(this.fileObj, this); | ||
} | ||
if (customQuery != null) { | ||
for (key in customQuery) { | ||
value = customQuery[key]; | ||
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.xhr.open('GET', this.getOpt('target') + '?' + params.join('&')); | ||
headers = this.getOpt('headers'); | ||
if (headers == null) { | ||
headers = {}; | ||
} | ||
for (key in headers) { | ||
value = headers[key]; | ||
this.xhr.setRequestHeader(key, value); | ||
} | ||
return this.xhr.send(null); | ||
}; | ||
ResumableChunk.prototype.preprocessFinished = function() { | ||
this.preprocessState = 2; | ||
return this.send(); | ||
}; | ||
$.preprocessFinished = function(){ | ||
$.preprocessState = 2; | ||
$.send(); | ||
}; | ||
ResumableChunk.prototype.send = function() { | ||
var bytes, customQuery, data, doneHandler, func, headers, key, params, preprocess, progressHandler, query, ret, target, value, | ||
_this = this; | ||
preprocess = this.getOpt('preprocess'); | ||
if (typeof preprocess === 'function') { | ||
ret = false; | ||
switch (this.preprocessState) { | ||
case 0: | ||
preprocess(this); | ||
this.preprocessState = 1; | ||
ret = true; | ||
break; | ||
case 1: | ||
ret = true; | ||
break; | ||
case 2: | ||
ret = false; | ||
// send() uploads the actual data in a POST call | ||
$.send = function(){ | ||
var preprocess = $.getOpt('preprocess'); | ||
if(typeof preprocess === 'function') { | ||
switch($.preprocessState) { | ||
case 0: $.preprocessState = 1; preprocess($); return; | ||
case 1: return; | ||
case 2: break; | ||
} | ||
} | ||
if (ret) { | ||
if($.getOpt('testChunks') && !$.tested) { | ||
$.test(); | ||
return; | ||
} | ||
} | ||
if (this.getOpt('testChunks') && !this.tested) { | ||
this.test(); | ||
return; | ||
} | ||
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; | ||
// Set up request and listen for event | ||
$.xhr = new XMLHttpRequest(); | ||
// Progress | ||
$.xhr.upload.addEventListener('progress', function(e){ | ||
if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) { | ||
$.callback('progress'); | ||
$.lastProgressCallback = (new Date); | ||
} | ||
$.loaded=e.loaded||0; | ||
}, false); | ||
$.loaded = 0; | ||
$.pendingRetry = false; | ||
$.callback('progress'); | ||
// Done (either done, failed or retry) | ||
var doneHandler = function(e){ | ||
var status = $.status(); | ||
if(status=='success'||status=='error') { | ||
$.callback(status, $.message()); | ||
$.resumableObj.uploadNextChunk(); | ||
} else { | ||
$.callback('retry', $.message()); | ||
$.abort(); | ||
$.retries++; | ||
var retryInterval = $.getOpt('chunkRetryInterval'); | ||
if(retryInterval !== undefined) { | ||
$.pendingRetry = true; | ||
setTimeout($.send, retryInterval); | ||
} else { | ||
$.send(); | ||
} | ||
} | ||
}; | ||
$.xhr.addEventListener('load', doneHandler, false); | ||
$.xhr.addEventListener('error', doneHandler, false); | ||
$.xhr.addEventListener('timeout', doneHandler, false); | ||
// Set up the basic query data from Resumable | ||
var query = [ | ||
['chunkNumberParameterName', $.offset + 1], | ||
['chunkSizeParameterName', $.getOpt('chunkSize')], | ||
['currentChunkSizeParameterName', $.endByte - $.startByte], | ||
['totalSizeParameterName', $.fileObjSize], | ||
['typeParameterName', $.fileObjType], | ||
['identifierParameterName', $.fileObj.uniqueIdentifier], | ||
['fileNameParameterName', $.fileObj.fileName], | ||
['relativePathParameterName', $.fileObj.relativePath], | ||
['totalChunksParameterName', $.fileObj.chunks.length], | ||
].filter(function(pair){ | ||
// include items that resolve to truthy values | ||
// i.e. exclude false, null, undefined and empty strings | ||
return $.getOpt(pair[0]); | ||
}) | ||
.reduce(function(query, pair){ | ||
// assign query key/value | ||
query[$.getOpt(pair[0])] = pair[1]; | ||
return query; | ||
}, {}); | ||
// Mix in custom data | ||
var customQuery = $.getOpt('query'); | ||
if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); | ||
$h.each(customQuery, function(k,v){ | ||
query[k] = v; | ||
}); | ||
var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))); | ||
var bytes = $.fileObj.file[func]($.startByte, $.endByte); | ||
var data = null; | ||
var params = []; | ||
var parameterNamespace = $.getOpt('parameterNamespace'); | ||
if ($.getOpt('method') === 'octet') { | ||
// Add data from the query options | ||
data = bytes; | ||
$h.each(query, function (k, v) { | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
} else { | ||
// Add data from the query options | ||
data = new FormData(); | ||
$h.each(query, function (k, v) { | ||
data.append(parameterNamespace + k, v); | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), bytes, $.fileObj.fileName); | ||
} | ||
else if ($.getOpt('chunkFormat') == 'base64') { | ||
var fr = new FileReader(); | ||
fr.onload = function (e) { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), fr.result); | ||
$.xhr.send(data); | ||
} | ||
fr.readAsDataURL(bytes); | ||
} | ||
} | ||
var target = $h.getTarget('upload', params); | ||
var method = $.getOpt('uploadMethod'); | ||
$.xhr.open(method, target); | ||
if ($.getOpt('method') === 'octet') { | ||
$.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); | ||
} | ||
return _this.loaded = e.loaded || 0; | ||
$.xhr.timeout = $.getOpt('xhrTimeout'); | ||
$.xhr.withCredentials = $.getOpt('withCredentials'); | ||
// Add data from header options | ||
var customHeaders = $.getOpt('headers'); | ||
if(typeof customHeaders === 'function') { | ||
customHeaders = customHeaders($.fileObj, $); | ||
} | ||
$h.each(customHeaders, function(k,v) { | ||
$.xhr.setRequestHeader(k, v); | ||
}); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
$.xhr.send(data); | ||
} | ||
}; | ||
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(); | ||
$.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 { | ||
_this.callback('retry', _this.message()); | ||
_this.abort(); | ||
_this.retries++; | ||
retryInterval = getOpt('chunkRetryInterval'); | ||
if (retryInterval != null) { | ||
return setTimeout(_this.send, retryInterval); | ||
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 | ||
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'); | ||
} | ||
} | ||
}; | ||
this.xhr.addEventListener('load', doneHandler, false); | ||
this.xhr.addEventListener('error', doneHandler, false); | ||
headers = this.getOpt('headers'); | ||
if (headers == null) { | ||
headers = {}; | ||
} | ||
for (key in headers) { | ||
value = headers[key]; | ||
this.xhr.setRequestHeader(key, value); | ||
} | ||
if (this.fileObj.file.slice != null) { | ||
func = 'slice'; | ||
} else if (this.fileObj.file.mozSlice != null) { | ||
func = 'mozSlice'; | ||
} else if (this.fileObj.file.webkitSlice != null) { | ||
func = 'webkitSlice'; | ||
} else { | ||
func = 'slice'; | ||
} | ||
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 | ||
$.message = function(){ | ||
return($.xhr ? $.xhr.responseText : ''); | ||
}; | ||
customQuery = this.getOpt('query'); | ||
if (typeof customQuery === 'function') { | ||
customQuery = customQuery(this.fileObj, this); | ||
} | ||
if (customQuery == null) { | ||
customQuery = {}; | ||
} | ||
for (key in customQuery) { | ||
value = customQuery[key]; | ||
pushParams(query, key, value); | ||
} | ||
if (this.getOpt('method') === 'octet') { | ||
data = bytes; | ||
params = []; | ||
for (key in query) { | ||
value = query[key]; | ||
this.pushParams(params, key, value); | ||
$.progress = function(relative){ | ||
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': | ||
case 'error': | ||
return(1*factor); | ||
case 'pending': | ||
return(0*factor); | ||
default: | ||
return($.loaded/($.endByte-$.startByte)*factor); | ||
} | ||
target += '?' + params.join('&'); | ||
} else { | ||
data = new FormData(); | ||
for (key in query) { | ||
value = query[key]; | ||
data.append(key, value); | ||
} | ||
data.append(this.getOpt('fileParameterName'), bytes); | ||
} | ||
this.xhr.open('POST', target); | ||
return this.xhr.send(data); | ||
}; | ||
}; | ||
return(this); | ||
} | ||
ResumableChunk.prototype.abort = function() { | ||
if (this.xhr != null) { | ||
this.xhr.abort(); | ||
} | ||
return this.xhr = null; | ||
}; | ||
// QUEUE | ||
$.uploadNextChunk = function(){ | ||
var found = false; | ||
ResumableChunk.prototype.status = function() { | ||
var maxChunkRetries, permanentErrors, _ref; | ||
permanentErrors = this.getOpt('permanentErrors'); | ||
maxChunkRetries = this.getOpt('maxChunkRetries'); | ||
if (permanentErrors == null) { | ||
permanentErrors = {}; | ||
// In some cases (such as videos) it's really handy to upload the first | ||
// and last chunk of a file quickly; this let's the server check the file's | ||
// metadata and determine if there's even a point in continuing. | ||
if ($.getOpt('prioritizeFirstAndLastChunk')) { | ||
$h.each($.files, function(file){ | ||
if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) { | ||
file.chunks[0].send(); | ||
found = true; | ||
return(false); | ||
} | ||
if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[file.chunks.length-1].preprocessState === 0) { | ||
file.chunks[file.chunks.length-1].send(); | ||
found = true; | ||
return(false); | ||
} | ||
}); | ||
if(found) return(true); | ||
} | ||
if (maxChunkRetries == null) { | ||
maxChunkRetries = 0; | ||
} | ||
if (this.xhr == null) { | ||
return 'pending'; | ||
} else if (this.xhr.readyState < 4) { | ||
return 'uploading'; | ||
} else if (this.xhr.status === 200) { | ||
return 'success'; | ||
} else if ((_ref = this.xhr.status, __indexOf.call(permanentErrors, _ref) >= 0) || (this.retries >= maxChunkRetries)) { | ||
return 'error'; | ||
} else { | ||
this.abort(); | ||
return 'pending'; | ||
} | ||
}; | ||
ResumableChunk.prototype.message = function() { | ||
return (this.xhr != null ? this.xhr.responseText : ''); | ||
}; | ||
// Now, simply look for the next, best thing to upload | ||
$h.each($.files, function(file){ | ||
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); | ||
ResumableChunk.prototype.progress = function(relative) { | ||
var factor; | ||
factor = (relative != null ? (this.endByte - this.startByte) / this.fileObjSize : 1); | ||
switch (this.status()) { | ||
case 'success': | ||
case 'error': | ||
return 1 * factor; | ||
case 'pending': | ||
return 0 * factor; | ||
default: | ||
return this.loaded / (this.endByte - this.startByte) * factor; | ||
// The are no more outstanding chunks to upload, check is everything is done | ||
var outstanding = false; | ||
$h.each($.files, function(file){ | ||
if(!file.isComplete()) { | ||
outstanding = true; | ||
return(false); | ||
} | ||
}); | ||
if(!outstanding) { | ||
// All chunks have been uploaded, complete | ||
$.fire('complete'); | ||
} | ||
return(false); | ||
}; | ||
return ResumableChunk; | ||
})(); | ||
// PUBLIC METHODS FOR RESUMABLE.JS | ||
$.assignBrowse = function(domNodes, isDirectory){ | ||
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; | ||
window.ResumableFile = ResumableFile = (function() { | ||
$h.each(domNodes, function(domNode) { | ||
var input; | ||
if(domNode.tagName==='INPUT' && domNode.type==='file'){ | ||
input = domNode; | ||
} else { | ||
input = document.createElement('input'); | ||
input.setAttribute('type', 'file'); | ||
input.style.display = 'none'; | ||
domNode.addEventListener('click', function(){ | ||
input.style.opacity = 0; | ||
input.style.display='block'; | ||
input.focus(); | ||
input.click(); | ||
input.style.display='none'; | ||
}, false); | ||
domNode.appendChild(input); | ||
} | ||
var maxFiles = $.getOpt('maxFiles'); | ||
if (typeof(maxFiles)==='undefined'||maxFiles!=1){ | ||
input.setAttribute('multiple', 'multiple'); | ||
} else { | ||
input.removeAttribute('multiple'); | ||
} | ||
if(isDirectory){ | ||
input.setAttribute('webkitdirectory', 'webkitdirectory'); | ||
} else { | ||
input.removeAttribute('webkitdirectory'); | ||
} | ||
// When new files are added, simply append them to the overall list | ||
input.addEventListener('change', function(e){ | ||
appendFilesFromFileList(e.target.files,e); | ||
var clearInput = $.getOpt('clearInput'); | ||
if (clearInput) { | ||
e.target.value = ''; | ||
} | ||
}, false); | ||
}); | ||
}; | ||
$.assignDrop = function(domNodes){ | ||
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; | ||
function ResumableFile(resumableObj, file) { | ||
this.resumableObj = resumableObj; | ||
this.file = file; | ||
this.opt = {}; | ||
this._prevProgress = 0; | ||
this.fileName = this.file.fileName || this.file.name; | ||
this.size = this.file.size; | ||
this.relativePath = this.file.webkitRelativePath || this.fileName; | ||
this.uniqueIdentifier = this.resumableObj.generateUniqueIdentifier(this.file); | ||
this._error = false; | ||
this.chunks = []; | ||
this.bootstrap(); | ||
} | ||
ResumableFile.prototype.getOpt = function(o) { | ||
return this.resumableObj.getOpt(o); | ||
$h.each(domNodes, function(domNode) { | ||
domNode.addEventListener('dragover', preventDefault, false); | ||
domNode.addEventListener('dragenter', preventDefault, false); | ||
domNode.addEventListener('drop', onDrop, false); | ||
}); | ||
}; | ||
$.unAssignDrop = function(domNodes) { | ||
if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; | ||
ResumableFile.prototype.chunkEvent = function(event, message) { | ||
switch (event) { | ||
case "progress": | ||
return this.resumableObj.fire('fileProgress', this); | ||
case "error": | ||
this.abort(); | ||
this._error = true; | ||
this.chunks = []; | ||
return this.resumableObj.fire('fileError', this, message); | ||
case "success": | ||
if (!this._error) { | ||
this.resumableObj.fire('fileProgress', this); | ||
if (this.progress() === 1) { | ||
return this.resumableObj.fire('fileSuccess', this, message); | ||
} | ||
} | ||
break; | ||
case "retry": | ||
return this.resumableObj.fire('fileRetry', this); | ||
} | ||
$h.each(domNodes, function(domNode) { | ||
domNode.removeEventListener('dragover', preventDefault); | ||
domNode.removeEventListener('dragenter', preventDefault); | ||
domNode.removeEventListener('drop', onDrop); | ||
}); | ||
}; | ||
ResumableFile.prototype.abort = function() { | ||
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(); | ||
$.isUploading = function(){ | ||
var uploading = false; | ||
$h.each($.files, function(file){ | ||
if (file.isUploading()) { | ||
uploading = true; | ||
return(false); | ||
} | ||
} | ||
return this.resumableObj.fire('fileProgress', this); | ||
}); | ||
return(uploading); | ||
}; | ||
ResumableFile.prototype.cancel = function() { | ||
var c, _chunks, _i, _len; | ||
_chunks = this.chunks; | ||
this.chunks = []; | ||
for (_i = 0, _len = _chunks.length; _i < _len; _i++) { | ||
c = _chunks[_i]; | ||
if (c.status() === 'uploading') { | ||
c.abort(); | ||
this.resumableObj.uploadNextChunk(); | ||
} | ||
$.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(); | ||
} | ||
this.resumableObj.removeFile(this); | ||
return this.resumableObj.fire('fileProgress', this); | ||
}; | ||
ResumableFile.prototype.retry = function() { | ||
this.bootstrap(); | ||
return this.resumableObj.upload(); | ||
$.pause = function(){ | ||
// Resume all chunks currently being uploaded | ||
$h.each($.files, function(file){ | ||
file.abort(); | ||
}); | ||
$.fire('pause'); | ||
}; | ||
ResumableFile.prototype.bootstrap = function() { | ||
var max, offset, round, _i, _ref, _results; | ||
this.abort(); | ||
this._error = false; | ||
this.chunks = []; | ||
this._prevProgress = 0; | ||
if (this.getOpt('forceChunkSize') != null) { | ||
round = Math.ceil; | ||
} else { | ||
round = Math.floor; | ||
$.cancel = function(){ | ||
$.fire('beforeCancel'); | ||
for(var i = $.files.length - 1; i >= 0; i--) { | ||
$.files[i].cancel(); | ||
} | ||
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))); | ||
} | ||
return _results; | ||
$.fire('cancel'); | ||
}; | ||
ResumableFile.prototype.progress = function() { | ||
var c, error, ret, _i, _len, _ref; | ||
if (this._error) { | ||
return 1.; | ||
$.progress = function(){ | ||
var totalDone = 0; | ||
var totalSize = 0; | ||
// Resume all chunks currently being uploaded | ||
$h.each($.files, function(file){ | ||
totalDone += file.progress()*file.size; | ||
totalSize += file.size; | ||
}); | ||
return(totalSize>0 ? totalDone/totalSize : 0); | ||
}; | ||
$.addFile = function(file, event){ | ||
appendFilesFromFileList([file], event); | ||
}; | ||
$.removeFile = function(file){ | ||
for(var i = $.files.length - 1; i >= 0; i--) { | ||
if($.files[i] === file) { | ||
$.files.splice(i, 1); | ||
} | ||
} | ||
ret = 0; | ||
error = false; | ||
_ref = this.chunks; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
c = _ref[_i]; | ||
error = c.status() === 'error'; | ||
ret += c.progress(true); | ||
} | ||
ret = (error || error > 0.99 ? 1 : ret); | ||
ret = Math.max(this._prevProgress, ret); | ||
this._prevProgress = ret; | ||
return ret; | ||
}; | ||
$.getFromUniqueIdentifier = function(uniqueIdentifier){ | ||
var ret = false; | ||
$h.each($.files, function(f){ | ||
if(f.uniqueIdentifier==uniqueIdentifier) ret = f; | ||
}); | ||
return(ret); | ||
}; | ||
$.getSize = function(){ | ||
var totalSize = 0; | ||
$h.each($.files, function(file){ | ||
totalSize += file.size; | ||
}); | ||
return(totalSize); | ||
}; | ||
$.handleDropEvent = function (e) { | ||
onDrop(e); | ||
}; | ||
$.handleChangeEvent = function (e) { | ||
appendFilesFromFileList(e.target.files, e); | ||
e.target.value = ''; | ||
}; | ||
$.updateQuery = function(query){ | ||
$.opts.query = query; | ||
}; | ||
return ResumableFile; | ||
return(this); | ||
}; | ||
})(); | ||
}).call(this); | ||
// Node.js-style export for Node and Component | ||
if (typeof module != 'undefined') { | ||
module.exports = Resumable; | ||
} else if (typeof define === "function" && define.amd) { | ||
// AMD/requirejs: Define the module | ||
define(function(){ | ||
return Resumable; | ||
}); | ||
} else { | ||
// Browser: Expose to window | ||
window.Resumable = Resumable; | ||
} | ||
})(); |
@@ -1,799 +0,1071 @@ | ||
// Generated by CoffeeScript 1.6.1 | ||
(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; }; | ||
/* | ||
* MIT Licensed | ||
* http://www.23developer.com/opensource | ||
* http://github.com/23/resumable.js | ||
* Steffen Tiedemann Christensen, steffen@23company.com | ||
*/ | ||
window.Resumable = Resumable = (function() { | ||
(function(){ | ||
"use strict"; | ||
function Resumable(opt) { | ||
this.opt = opt; | ||
console.log('constructor'); | ||
this.support = (typeof File !== "undefined" && File !== null) && (typeof Blob !== "undefined" && Blob !== null) && (typeof FileList !== "undefined" && FileList !== null) && ((Blob.prototype.webkitSlice != null) || (Blob.prototype.mozSlice != null) || (Blob.prototype.slice != null)); | ||
this.files = []; | ||
this.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: void 0, | ||
chunkRetryInterval: void 0, | ||
permanentErrors: [415, 500, 501], | ||
maxFiles: void 0, | ||
maxFilesErrorCallback: function(files, errorCount) { | ||
var maxFiles, _ref; | ||
maxFiles = this.getOpt('maxFiles'); | ||
return alert('Please upload ' + maxFiles + ' file' + ((_ref = maxFiles === 1) != null ? _ref : { | ||
'': 's' | ||
}) + ' at a time.'); | ||
}, | ||
minFileSize: void 0, | ||
minFileSizeErrorCallback: function(file, errorCount) { | ||
return alert(file.fileName(+' is too small, please upload files larger than ' + this.formatSize(this.getOpt('minFileSize')) + '.')); | ||
}, | ||
maxFileSize: void 0, | ||
maxFileSizeErrorCallback: function(file, errorCount) { | ||
return alert(file.fileName(+' is too large, please upload files less than ' + this.formatSize(this.getOpt('maxFileSize')) + '.')); | ||
} | ||
}; | ||
if (this.opt == null) { | ||
this.opt = {}; | ||
} | ||
this.events = []; | ||
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); | ||
Resumable.prototype.getOpt = function(o) { | ||
var item, opts, _i, _len; | ||
if (o instanceof Array) { | ||
opts = {}; | ||
for (_i = 0, _len = o.length; _i < _len; _i++) { | ||
item = o[_i]; | ||
opts[item] = this.getOpt(item); | ||
} | ||
return opts; | ||
} else { | ||
if (this.opt[o] != null) { | ||
return this.opt[o]; | ||
} else { | ||
return this.defaults[o]; | ||
} | ||
} | ||
}; | ||
Resumable.prototype.formatSize = function(size) { | ||
if (size < 1024) { | ||
return size + ' bytes'; | ||
} else if (size < 1024 * 1024) { | ||
return (size / 1024.0).toFixed(0) + ' KB'; | ||
} else if (size < 1024 * 1024 * 1024) { | ||
return (size / 1024.0 / 1024.0).toFixed(1) + ' MB'; | ||
} else { | ||
return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + ' GB'; | ||
// PROPERTIES | ||
var $ = this; | ||
$.files = []; | ||
$.defaults = { | ||
chunkSize:1*1024*1024, | ||
forceChunkSize:false, | ||
simultaneousUploads:3, | ||
fileParameterName:'file', | ||
chunkNumberParameterName: 'resumableChunkNumber', | ||
chunkSizeParameterName: 'resumableChunkSize', | ||
currentChunkSizeParameterName: 'resumableCurrentChunkSize', | ||
totalSizeParameterName: 'resumableTotalSize', | ||
typeParameterName: 'resumableType', | ||
identifierParameterName: 'resumableIdentifier', | ||
fileNameParameterName: 'resumableFilename', | ||
relativePathParameterName: 'resumableRelativePath', | ||
totalChunksParameterName: 'resumableTotalChunks', | ||
throttleProgressCallbacks: 0.5, | ||
query:{}, | ||
headers:{}, | ||
preprocess:null, | ||
method:'multipart', | ||
uploadMethod: 'POST', | ||
testMethod: 'GET', | ||
prioritizeFirstAndLastChunk:false, | ||
target:'/', | ||
testTarget: null, | ||
parameterNamespace:'', | ||
testChunks:true, | ||
generateUniqueIdentifier:null, | ||
getTarget:null, | ||
maxChunkRetries:100, | ||
chunkRetryInterval:undefined, | ||
permanentErrors:[400, 404, 415, 500, 501], | ||
maxFiles:undefined, | ||
withCredentials:false, | ||
xhrTimeout:0, | ||
clearInput:true, | ||
chunkFormat:'blob', | ||
maxFilesErrorCallback:function (files, errorCount) { | ||
var maxFiles = $.getOpt('maxFiles'); | ||
alert('Please upload no more than ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.'); | ||
}, | ||
minFileSize:1, | ||
minFileSizeErrorCallback:function(file, errorCount) { | ||
alert(file.fileName||file.name +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.'); | ||
}, | ||
maxFileSize:undefined, | ||
maxFileSizeErrorCallback:function(file, errorCount) { | ||
alert(file.fileName||file.name +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.'); | ||
}, | ||
fileType: [], | ||
fileTypeErrorCallback: function(file, errorCount) { | ||
alert(file.fileName||file.name +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.'); | ||
} | ||
}; | ||
Resumable.prototype.stopEvent = function(e) { | ||
console.log('stopEvent'); | ||
e.stopPropagation(); | ||
return e.preventDefault(); | ||
}; | ||
Resumable.prototype.generateUniqueIdentifier = function(file) { | ||
var custom, relativePath, size; | ||
console.log('generateUniqueIdentifier'); | ||
custom = this.getOpt('generateUniqueIdentifier'); | ||
if (typeof custom === 'function') { | ||
return custom(file); | ||
} else { | ||
relativePath = file.webkitRelativePath || file.fileName || file.name; | ||
size = file.size; | ||
return size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, ''); | ||
$.opts = opts||{}; | ||
$.getOpt = function(o) { | ||
var $opt = this; | ||
// Get multiple option if passed an array | ||
if(o instanceof Array) { | ||
var options = {}; | ||
$h.each(o, function(option){ | ||
options[option] = $opt.getOpt(option); | ||
}); | ||
return options; | ||
} | ||
}; | ||
Resumable.prototype.on = function(event, callback) { | ||
console.log("on: " + event); | ||
return this.events.push({ | ||
event: event, | ||
callback: callback | ||
}); | ||
}; | ||
Resumable.prototype.fire = function() { | ||
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]; | ||
if (e.event.toLowerCase() === event) { | ||
e.callback.apply(this, args.slice(1)); | ||
} | ||
if (e.event.toLowerCase() === 'catchall') { | ||
e.callback.apply(null, args); | ||
} | ||
// Otherwise, just return a simple option | ||
if ($opt instanceof ResumableChunk) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { $opt = $opt.fileObj; } | ||
} | ||
if (event === 'fireerror') { | ||
this.fire('error', args[2], args[1]); | ||
if ($opt instanceof ResumableFile) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { $opt = $opt.resumableObj; } | ||
} | ||
if (event === 'fileprogress') { | ||
return this.fire('progress'); | ||
if ($opt instanceof Resumable) { | ||
if (typeof $opt.opts[o] !== 'undefined') { return $opt.opts[o]; } | ||
else { return $opt.defaults[o]; } | ||
} | ||
}; | ||
Resumable.prototype.onDrop = function(event) { | ||
console.log("onDrop"); | ||
this.stopEvent(event); | ||
return this.appendFilesFromFileList(event.dataTransfer.files, event); | ||
// EVENTS | ||
// catchAll(event, ...) | ||
// fileSuccess(file), fileProgress(file), fileAdded(file, event), filesAdded(files, filesSkipped), fileRetry(file), | ||
// fileError(file, message), complete(), progress(), error(message, file), pause() | ||
$.events = []; | ||
$.on = function(event,callback){ | ||
$.events.push(event.toLowerCase(), callback); | ||
}; | ||
Resumable.prototype.onDragOver = function(event) { | ||
console.log("onDragOver"); | ||
return event.preventDefault(); | ||
}; | ||
Resumable.prototype.appendFilesFromFileList = function(fileList, event) { | ||
var errorCount, file, files, maxFileSize, maxFileSizeErrorCallback, maxFiles, maxFilesErrorCallback, minFileSize, minFileSizeErrorCallback, resumableFile, _i, _len, _ref; | ||
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]; | ||
if ((maxFiles != null) && maxFiles < (fileList.length + this.files.length)) { | ||
maxFilesErrorCallback(fileList, errorCount++); | ||
return false; | ||
$.fire = function(){ | ||
// `arguments` is an object, not array, in FF, so: | ||
var args = []; | ||
for (var i=0; i<arguments.length; i++) args.push(arguments[i]); | ||
// Find event listeners, and support pseudo-event `catchAll` | ||
var event = args[0].toLowerCase(); | ||
for (var i=0; i<=$.events.length; i+=2) { | ||
if($.events[i]==event) $.events[i+1].apply($,args.slice(1)); | ||
if($.events[i]=='catchall') $.events[i+1].apply(null,args); | ||
} | ||
files = []; | ||
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++); | ||
return false; | ||
} | ||
if ((maxFileSize != null) && file.size > maxFileSize) { | ||
maxFilesErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
if (file.size > 0 && !this.getFromUniqueIdentifier(this.generateUniqueIdentifier(file))) { | ||
resumableFile = new ResumableFile(this, file); | ||
this.files.push(resumableFile); | ||
files.push(resumableFile); | ||
this.fire('fileAdded', resumableFile, event); | ||
} | ||
} | ||
return this.fire('fileAdded', files); | ||
if(event=='fileerror') $.fire('error', args[2], args[1]); | ||
if(event=='fileprogress') $.fire('progress'); | ||
}; | ||
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; | ||
console.log("uploadNextChunk"); | ||
found = false; | ||
if (this.getOpt('prioritizeFirstAndLastChunk')) { | ||
_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; | ||
break; | ||
// INTERNAL HELPER METHODS (handy, but ultimately not part of uploading) | ||
var $h = { | ||
stopEvent: function(e){ | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
}, | ||
each: function(o,callback){ | ||
if(typeof(o.length)!=='undefined') { | ||
for (var i=0; i<o.length; i++) { | ||
// Array or FileList | ||
if(callback(o[i])===false) return; | ||
} | ||
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; | ||
break; | ||
} else { | ||
for (i in o) { | ||
// Object | ||
if(callback(i,o[i])===false) return; | ||
} | ||
} | ||
if (found) { | ||
return true; | ||
}, | ||
generateUniqueIdentifier:function(file, event){ | ||
var custom = $.getOpt('generateUniqueIdentifier'); | ||
if(typeof custom === 'function') { | ||
return custom(file, event); | ||
} | ||
} | ||
_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; | ||
break; | ||
var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox | ||
var size = file.size; | ||
return(size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')); | ||
}, | ||
contains:function(array,test) { | ||
var result = false; | ||
$h.each(array, function(value) { | ||
if (value == test) { | ||
result = true; | ||
return false; | ||
} | ||
return true; | ||
}); | ||
return result; | ||
}, | ||
formatSize:function(size){ | ||
if(size<1024) { | ||
return size + ' bytes'; | ||
} else if(size<1024*1024) { | ||
return (size/1024.0).toFixed(0) + ' KB'; | ||
} else if(size<1024*1024*1024) { | ||
return (size/1024.0/1024.0).toFixed(1) + ' MB'; | ||
} else { | ||
return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB'; | ||
} | ||
if (found) { | ||
break; | ||
}, | ||
getTarget:function(request, params){ | ||
var target = $.getOpt('target'); | ||
if (request === 'test' && $.getOpt('testTarget')) { | ||
target = $.getOpt('testTarget') === '/' ? $.getOpt('target') : $.getOpt('testTarget'); | ||
} | ||
} | ||
if (found) { | ||
return true; | ||
} | ||
_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]; | ||
status = chunk.status(); | ||
if (status === 'pending' || status === 'uploading' || chunk.preprocessState === 1) { | ||
outstanding = true; | ||
break; | ||
} | ||
if (typeof target === 'function') { | ||
return target(params); | ||
} | ||
if (outstanding) { | ||
break; | ||
} | ||
var separator = target.indexOf('?') < 0 ? '?' : '&'; | ||
var joinedParams = params.join('&'); | ||
return target + separator + joinedParams; | ||
} | ||
if (!outstanding) { | ||
this.fire('complete'); | ||
} | ||
return false; | ||
}; | ||
Resumable.prototype.assignBrowse = function(domNodes, isDirectory) { | ||
var changeHandler, dn, input, maxFiles, _i, _len, | ||
_this = this; | ||
console.log("assignBrowse"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
var onDrop = function(event){ | ||
$h.stopEvent(event); | ||
//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); | ||
} | ||
for (_i = 0, _len = domNodes.length; _i < _len; _i++) { | ||
dn = domNodes[_i]; | ||
if (dn.tagName === 'INPUT' && dn.type === 'file') { | ||
input = dn; | ||
} else { | ||
input = document.createElement('input'); | ||
input.setAttribute('type', 'file'); | ||
dn.style.display = 'inline-block'; | ||
dn.style.position = 'relative'; | ||
input.style.position = 'absolute'; | ||
input.style.top = input.style.left = input.style.bottom = input.style.right = 0; | ||
input.style.opacity = 0; | ||
input.style.cursor = 'pointer'; | ||
dn.appendChild(input); | ||
} | ||
//else handle them as files | ||
else if (event.dataTransfer && event.dataTransfer.files) { | ||
loadFiles(event.dataTransfer.files, event); | ||
} | ||
maxFiles = this.getOpt('maxFiles'); | ||
if ((maxFiles != null) || maxFiles !== 1) { | ||
input.setAttribute('multiple', 'multiple'); | ||
} else { | ||
input.removeAttribute('multiple'); | ||
} | ||
if (isDirectory) { | ||
input.setAttribute('webkitdirectory', 'webkitdirectory'); | ||
} else { | ||
input.removeAttribute('webkitdirectory'); | ||
} | ||
changeHandler = function(e) { | ||
_this.appendFilesFromFileList(e.target.files); | ||
return e.target.value = ''; | ||
}; | ||
return input.addEventListener('change', changeHandler, false); | ||
}; | ||
var preventDefault = function(e) { | ||
e.preventDefault(); | ||
}; | ||
Resumable.prototype.assignDrop = function(domNodes) { | ||
var dn, _i, _len, _results; | ||
console.log("assignDrop"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
/** | ||
* processes a single upload item (file or directory) | ||
* @param {Object} item item to upload, may be file or directory entry | ||
* @param {string} path current file path | ||
* @param {File[]} items list of files to append new items to | ||
* @param {Function} cb callback invoked when item is processed | ||
*/ | ||
function processItem(item, path, items, cb) { | ||
var entry; | ||
if(item.isFile){ | ||
// file provided | ||
return item.file(function(file){ | ||
file.relativePath = path + file.name; | ||
items.push(file); | ||
cb(); | ||
}); | ||
}else if(item.isDirectory){ | ||
// item is already a directory entry, just assign | ||
entry = item; | ||
}else if(item instanceof File) { | ||
items.push(item); | ||
} | ||
_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)); | ||
if('function' === typeof item.webkitGetAsEntry){ | ||
// get entry from file object | ||
entry = item.webkitGetAsEntry(); | ||
} | ||
return _results; | ||
}; | ||
Resumable.prototype.unAssignDrop = function(domNodes) { | ||
var dn, _i, _len, _results; | ||
console.log("unAssignDrop"); | ||
if (domNodes.length == null) { | ||
domNodes = [domNodes]; | ||
if(entry && entry.isDirectory){ | ||
// directory provided, process it | ||
return processDirectory(entry, path + entry.name + '/', items, cb); | ||
} | ||
_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)); | ||
if('function' === typeof item.getAsFile){ | ||
// item represents a File object, convert it | ||
item = item.getAsFile(); | ||
item.relativePath = path + item.name; | ||
items.push(item); | ||
} | ||
return _results; | ||
}; | ||
cb(); // indicate processing is done | ||
} | ||
Resumable.prototype.isUploading = function() { | ||
var chunk, file, uploading, _i, _j, _len, _len1, _ref, _ref1; | ||
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]; | ||
if (chunk.status() === 'uploading') { | ||
uploading = true; | ||
break; | ||
} | ||
} | ||
if (uploading) { | ||
break; | ||
} | ||
} | ||
return uploading; | ||
}; | ||
Resumable.prototype.upload = function() { | ||
var num, _i, _ref, _results; | ||
console.log("upload"); | ||
if (this.isUploading()) { | ||
return; | ||
/** | ||
* 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(); | ||
} | ||
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()); | ||
} | ||
return _results; | ||
}; | ||
// invoke current function, pass the next part as continuation | ||
items[0](function(){ | ||
processCallbacks(items.slice(1), cb); | ||
}); | ||
} | ||
Resumable.prototype.pause = function() { | ||
var file, _i, _len, _ref; | ||
console.log("pause"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
file.abort(); | ||
} | ||
return this.fire('pause'); | ||
}; | ||
/** | ||
* 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 | ||
); | ||
}); | ||
} | ||
Resumable.prototype.cancel = function() { | ||
var file, _i, _len, _ref; | ||
console.log("cancel"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
file.cancel(); | ||
/** | ||
* process items to extract files to be uploaded | ||
* @param {File[]} items items to process | ||
* @param {Event} event event that led to upload | ||
*/ | ||
function loadFiles(items, event) { | ||
if(!items.length){ | ||
return; // nothing to do | ||
} | ||
return this.fire('cancel'); | ||
$.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); | ||
} | ||
} | ||
); | ||
}; | ||
Resumable.prototype.progress = function() { | ||
var file, totalDone, totalSize, _i, _len, _ref; | ||
console.log("progress"); | ||
totalDone = 0; | ||
totalSize = 0; | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
totalDone += file.progress() * file.size; | ||
totalSize += file.size; | ||
var appendFilesFromFileList = function(fileList, event){ | ||
// check for uploading too many files | ||
var errorCount = 0; | ||
var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']); | ||
if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) { | ||
// if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file | ||
if (o.maxFiles===1 && $.files.length===1 && fileList.length===1) { | ||
$.removeFile($.files[0]); | ||
} else { | ||
o.maxFilesErrorCallback(fileList, errorCount++); | ||
return false; | ||
} | ||
} | ||
return (totalSize > 0 ? totalDone / totalSize : 0); | ||
}; | ||
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){ | ||
var fileTypeFound = false; | ||
for(var index in o.fileType){ | ||
var extension = '.' + o.fileType[index]; | ||
if(fileName.toLowerCase().indexOf(extension.toLowerCase(), fileName.length - extension.length) !== -1){ | ||
fileTypeFound = true; | ||
break; | ||
} | ||
} | ||
if (!fileTypeFound) { | ||
o.fileTypeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
} | ||
Resumable.prototype.addFile = function(file) { | ||
console.log("addFile"); | ||
return this.appendFilesFromFileList([file]); | ||
}; | ||
if (typeof(o.minFileSize)!=='undefined' && file.size<o.minFileSize) { | ||
o.minFileSizeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
if (typeof(o.maxFileSize)!=='undefined' && file.size>o.maxFileSize) { | ||
o.maxFileSizeErrorCallback(file, errorCount++); | ||
return false; | ||
} | ||
Resumable.prototype.removeFile = function(file) { | ||
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]; | ||
if (f !== file) { | ||
files.push(f); | ||
function addFile(uniqueIdentifier){ | ||
if (!$.getFromUniqueIdentifier(uniqueIdentifier)) {(function(){ | ||
file.uniqueIdentifier = uniqueIdentifier; | ||
var f = new ResumableFile($, file, uniqueIdentifier); | ||
$.files.push(f); | ||
files.push(f); | ||
f.container = (typeof event != 'undefined' ? event.srcElement : null); | ||
window.setTimeout(function(){ | ||
$.fire('fileAdded', f, event) | ||
},0); | ||
})()} else { | ||
filesSkipped.push(file); | ||
}; | ||
decreaseReamining(); | ||
} | ||
} | ||
return this.files = files; | ||
// directories have size == 0 | ||
var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); | ||
if(uniqueIdentifier && typeof uniqueIdentifier.then === 'function'){ | ||
// Promise or Promise-like object provided as unique identifier | ||
uniqueIdentifier | ||
.then( | ||
function(uniqueIdentifier){ | ||
// unique identifier generation succeeded | ||
addFile(uniqueIdentifier); | ||
}, | ||
function(){ | ||
// unique identifier generation failed | ||
// skip further processing, only decrease file count | ||
decreaseReamining(); | ||
} | ||
); | ||
}else{ | ||
// non-Promise provided as unique identifier, process synchronously | ||
addFile(uniqueIdentifier); | ||
} | ||
}); | ||
}; | ||
Resumable.prototype.getFromUniqueIdentifier = function(uniqueIdentifier) { | ||
var f, _i, _len, _ref; | ||
console.log("getFromUniqueIdentifier"); | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
f = _ref[_i]; | ||
if (f.uniqueIdentifier === uniqueIdentifier) { | ||
return f; | ||
// INTERNAL OBJECT TYPES | ||
function ResumableFile(resumableObj, file, uniqueIdentifier){ | ||
var $ = this; | ||
$.opts = {}; | ||
$.getOpt = resumableObj.getOpt; | ||
$._prevProgress = 0; | ||
$.resumableObj = resumableObj; | ||
$.file = file; | ||
$.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox | ||
$.size = file.size; | ||
$.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; | ||
$.uniqueIdentifier = uniqueIdentifier; | ||
$._pause = false; | ||
$.container = ''; | ||
var _error = uniqueIdentifier !== undefined; | ||
// Callback when something happens within the chunk | ||
var chunkEvent = function(event, message){ | ||
// event can be 'progress', 'success', 'error' or 'retry' | ||
switch(event){ | ||
case 'progress': | ||
$.resumableObj.fire('fileProgress', $, message); | ||
break; | ||
case 'error': | ||
$.abort(); | ||
_error = true; | ||
$.chunks = []; | ||
$.resumableObj.fire('fileError', $, message); | ||
break; | ||
case 'success': | ||
if(_error) return; | ||
$.resumableObj.fire('fileProgress', $); // it's at least progress | ||
if($.isComplete()) { | ||
$.resumableObj.fire('fileSuccess', $, message); | ||
} | ||
break; | ||
case 'retry': | ||
$.resumableObj.fire('fileRetry', $); | ||
break; | ||
} | ||
} | ||
return false; | ||
}; | ||
}; | ||
Resumable.prototype.getSize = function() { | ||
var file, totalSize, _i, _len, _ref; | ||
console.log("getSize"); | ||
totalSize = 0; | ||
_ref = this.files; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
file = _ref[_i]; | ||
totalSize += file.size; | ||
} | ||
return totalSize; | ||
}; | ||
// Main code to set up a file object with chunks, | ||
// packaged to be able to handle retries if needed. | ||
$.chunks = []; | ||
$.abort = function(){ | ||
// Stop current uploads | ||
var abortCount = 0; | ||
$h.each($.chunks, function(c){ | ||
if(c.status()=='uploading') { | ||
c.abort(); | ||
abortCount++; | ||
} | ||
}); | ||
if(abortCount>0) $.resumableObj.fire('fileProgress', $); | ||
}; | ||
$.cancel = function(){ | ||
// Reset this file to be void | ||
var _chunks = $.chunks; | ||
$.chunks = []; | ||
// Stop current uploads | ||
$h.each(_chunks, function(c){ | ||
if(c.status()=='uploading') { | ||
c.abort(); | ||
$.resumableObj.uploadNextChunk(); | ||
} | ||
}); | ||
$.resumableObj.removeFile($); | ||
$.resumableObj.fire('fileProgress', $); | ||
}; | ||
$.retry = function(){ | ||
$.bootstrap(); | ||
var firedRetry = false; | ||
$.resumableObj.on('chunkingComplete', function(){ | ||
if(!firedRetry) $.resumableObj.upload(); | ||
firedRetry = true; | ||
}); | ||
}; | ||
$.bootstrap = function(){ | ||
$.abort(); | ||
_error = false; | ||
// Rebuild stack of chunks from file | ||
$.chunks = []; | ||
$._prevProgress = 0; | ||
var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor; | ||
var maxOffset = Math.max(round($.file.size/$.getOpt('chunkSize')),1); | ||
for (var offset=0; offset<maxOffset; offset++) {(function(offset){ | ||
window.setTimeout(function(){ | ||
$.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent)); | ||
$.resumableObj.fire('chunkingProgress',$,offset/maxOffset); | ||
},0); | ||
})(offset)} | ||
window.setTimeout(function(){ | ||
$.resumableObj.fire('chunkingComplete',$); | ||
},0); | ||
}; | ||
$.progress = function(){ | ||
if(_error) return(1); | ||
// Sum up progress across everything | ||
var ret = 0; | ||
var error = false; | ||
$h.each($.chunks, function(c){ | ||
if(c.status()=='error') error = true; | ||
ret += c.progress(true); // get chunk progress relative to entire file | ||
}); | ||
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); | ||
}; | ||
$.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); | ||
}; | ||
$.pause = function(pause){ | ||
if(typeof(pause)==='undefined'){ | ||
$._pause = ($._pause ? false : true); | ||
}else{ | ||
$._pause = pause; | ||
} | ||
}; | ||
$.isPaused = function() { | ||
return $._pause; | ||
}; | ||
return Resumable; | ||
})(); | ||
// Bootstrap and return | ||
$.resumableObj.fire('chunkingStart', $); | ||
$.bootstrap(); | ||
return(this); | ||
} | ||
window.ResumableChunk = ResumableChunk = (function() { | ||
function ResumableChunk(resumableObj, fileObj, offset, callback) { | ||
this.resumableObj = resumableObj; | ||
this.fileObj = fileObj; | ||
this.offset = offset; | ||
this.callback = callback; | ||
this.opt = {}; | ||
this.fileObjSize = this.fileObj.size; | ||
this.lastProgressCallback = new Date; | ||
this.tested = false; | ||
this.retries = 0; | ||
this.preprocessState = 0; | ||
this.chunkSize = this.getOpt('chunkSize'); | ||
this.loaded = 0; | ||
this.startByte = this.offset * this.chunkSize; | ||
this.endByte = Math.min(this.fileObjSize, (this.offset + 1) * this.chunkSize); | ||
if ((this.fileObjSize - this.endByte < this.chunkSize) && (!this.getOpt('forceChunkSize'))) { | ||
this.endByte = this.fileObjSize; | ||
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; | ||
} | ||
this.xhr = null; | ||
} | ||
$.xhr = null; | ||
ResumableChunk.prototype.getOpt = function(o) { | ||
return this.resumableObj.getOpt(o); | ||
}; | ||
// 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(); | ||
ResumableChunk.prototype.pushParams = function(params, key, value) { | ||
return params.push([encodeURIComponent(key), encodeURIComponent(value)].join('=')); | ||
}; | ||
var testHandler = function(e){ | ||
$.tested = true; | ||
var status = $.status(); | ||
if(status=='success') { | ||
$.callback(status, $.message()); | ||
$.resumableObj.uploadNextChunk(); | ||
} else { | ||
$.send(); | ||
} | ||
}; | ||
$.xhr.addEventListener('load', testHandler, false); | ||
$.xhr.addEventListener('error', testHandler, false); | ||
$.xhr.addEventListener('timeout', testHandler, false); | ||
ResumableChunk.prototype.test = function() { | ||
var customQuery, headers, key, params, testHandler, value, | ||
_this = this; | ||
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(); | ||
// Add data from the query options | ||
var params = []; | ||
var parameterNamespace = $.getOpt('parameterNamespace'); | ||
var customQuery = $.getOpt('query'); | ||
if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); | ||
$h.each(customQuery, function(k,v){ | ||
params.push([encodeURIComponent(parameterNamespace+k), encodeURIComponent(v)].join('=')); | ||
}); | ||
// Add extra data to identify chunk | ||
params = params.concat( | ||
[ | ||
// define key/value pairs for additional parameters | ||
['chunkNumberParameterName', $.offset + 1], | ||
['chunkSizeParameterName', $.getOpt('chunkSize')], | ||
['currentChunkSizeParameterName', $.endByte - $.startByte], | ||
['totalSizeParameterName', $.fileObjSize], | ||
['typeParameterName', $.fileObjType], | ||
['identifierParameterName', $.fileObj.uniqueIdentifier], | ||
['fileNameParameterName', $.fileObj.fileName], | ||
['relativePathParameterName', $.fileObj.relativePath], | ||
['totalChunksParameterName', $.fileObj.chunks.length] | ||
].filter(function(pair){ | ||
// include items that resolve to truthy values | ||
// i.e. exclude false, null, undefined and empty strings | ||
return $.getOpt(pair[0]); | ||
}) | ||
.map(function(pair){ | ||
// map each key/value pair to its final form | ||
return [ | ||
parameterNamespace + $.getOpt(pair[0]), | ||
encodeURIComponent(pair[1]) | ||
].join('='); | ||
}) | ||
); | ||
// Append the relevant chunk and send it | ||
$.xhr.open($.getOpt('testMethod'), $h.getTarget('test', params)); | ||
$.xhr.timeout = $.getOpt('xhrTimeout'); | ||
$.xhr.withCredentials = $.getOpt('withCredentials'); | ||
// Add data from header options | ||
var customHeaders = $.getOpt('headers'); | ||
if(typeof customHeaders === 'function') { | ||
customHeaders = customHeaders($.fileObj, $); | ||
} | ||
$h.each(customHeaders, function(k,v) { | ||
$.xhr.setRequestHeader(k, v); | ||
}); | ||
$.xhr.send(null); | ||
}; | ||
this.xhr.addEventListener('load', testHandler, false); | ||
this.xhr.addEventListener('error', testHandler, false); | ||
params = []; | ||
customQuery = this.getOpt('query'); | ||
if (typeof customQuery === 'function') { | ||
customQuery = customQuery(this.fileObj, this); | ||
} | ||
if (customQuery != null) { | ||
for (key in customQuery) { | ||
value = customQuery[key]; | ||
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.xhr.open('GET', this.getOpt('target') + '?' + params.join('&')); | ||
headers = this.getOpt('headers'); | ||
if (headers == null) { | ||
headers = {}; | ||
} | ||
for (key in headers) { | ||
value = headers[key]; | ||
this.xhr.setRequestHeader(key, value); | ||
} | ||
return this.xhr.send(null); | ||
}; | ||
ResumableChunk.prototype.preprocessFinished = function() { | ||
this.preprocessState = 2; | ||
return this.send(); | ||
}; | ||
$.preprocessFinished = function(){ | ||
$.preprocessState = 2; | ||
$.send(); | ||
}; | ||
ResumableChunk.prototype.send = function() { | ||
var bytes, customQuery, data, doneHandler, func, headers, key, params, preprocess, progressHandler, query, ret, target, value, | ||
_this = this; | ||
preprocess = this.getOpt('preprocess'); | ||
if (typeof preprocess === 'function') { | ||
ret = false; | ||
switch (this.preprocessState) { | ||
case 0: | ||
preprocess(this); | ||
this.preprocessState = 1; | ||
ret = true; | ||
break; | ||
case 1: | ||
ret = true; | ||
break; | ||
case 2: | ||
ret = false; | ||
// send() uploads the actual data in a POST call | ||
$.send = function(){ | ||
var preprocess = $.getOpt('preprocess'); | ||
if(typeof preprocess === 'function') { | ||
switch($.preprocessState) { | ||
case 0: $.preprocessState = 1; preprocess($); return; | ||
case 1: return; | ||
case 2: break; | ||
} | ||
} | ||
if (ret) { | ||
if($.getOpt('testChunks') && !$.tested) { | ||
$.test(); | ||
return; | ||
} | ||
} | ||
if (this.getOpt('testChunks') && !this.tested) { | ||
this.test(); | ||
return; | ||
} | ||
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; | ||
// Set up request and listen for event | ||
$.xhr = new XMLHttpRequest(); | ||
// Progress | ||
$.xhr.upload.addEventListener('progress', function(e){ | ||
if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) { | ||
$.callback('progress'); | ||
$.lastProgressCallback = (new Date); | ||
} | ||
$.loaded=e.loaded||0; | ||
}, false); | ||
$.loaded = 0; | ||
$.pendingRetry = false; | ||
$.callback('progress'); | ||
// Done (either done, failed or retry) | ||
var doneHandler = function(e){ | ||
var status = $.status(); | ||
if(status=='success'||status=='error') { | ||
$.callback(status, $.message()); | ||
$.resumableObj.uploadNextChunk(); | ||
} else { | ||
$.callback('retry', $.message()); | ||
$.abort(); | ||
$.retries++; | ||
var retryInterval = $.getOpt('chunkRetryInterval'); | ||
if(retryInterval !== undefined) { | ||
$.pendingRetry = true; | ||
setTimeout($.send, retryInterval); | ||
} else { | ||
$.send(); | ||
} | ||
} | ||
}; | ||
$.xhr.addEventListener('load', doneHandler, false); | ||
$.xhr.addEventListener('error', doneHandler, false); | ||
$.xhr.addEventListener('timeout', doneHandler, false); | ||
// Set up the basic query data from Resumable | ||
var query = [ | ||
['chunkNumberParameterName', $.offset + 1], | ||
['chunkSizeParameterName', $.getOpt('chunkSize')], | ||
['currentChunkSizeParameterName', $.endByte - $.startByte], | ||
['totalSizeParameterName', $.fileObjSize], | ||
['typeParameterName', $.fileObjType], | ||
['identifierParameterName', $.fileObj.uniqueIdentifier], | ||
['fileNameParameterName', $.fileObj.fileName], | ||
['relativePathParameterName', $.fileObj.relativePath], | ||
['totalChunksParameterName', $.fileObj.chunks.length], | ||
].filter(function(pair){ | ||
// include items that resolve to truthy values | ||
// i.e. exclude false, null, undefined and empty strings | ||
return $.getOpt(pair[0]); | ||
}) | ||
.reduce(function(query, pair){ | ||
// assign query key/value | ||
query[$.getOpt(pair[0])] = pair[1]; | ||
return query; | ||
}, {}); | ||
// Mix in custom data | ||
var customQuery = $.getOpt('query'); | ||
if(typeof customQuery == 'function') customQuery = customQuery($.fileObj, $); | ||
$h.each(customQuery, function(k,v){ | ||
query[k] = v; | ||
}); | ||
var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))); | ||
var bytes = $.fileObj.file[func]($.startByte, $.endByte); | ||
var data = null; | ||
var params = []; | ||
var parameterNamespace = $.getOpt('parameterNamespace'); | ||
if ($.getOpt('method') === 'octet') { | ||
// Add data from the query options | ||
data = bytes; | ||
$h.each(query, function (k, v) { | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
} else { | ||
// Add data from the query options | ||
data = new FormData(); | ||
$h.each(query, function (k, v) { | ||
data.append(parameterNamespace + k, v); | ||
params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join('=')); | ||
}); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), bytes, $.fileObj.fileName); | ||
} | ||
else if ($.getOpt('chunkFormat') == 'base64') { | ||
var fr = new FileReader(); | ||
fr.onload = function (e) { | ||
data.append(parameterNamespace + $.getOpt('fileParameterName'), fr.result); | ||
$.xhr.send(data); | ||
} | ||
fr.readAsDataURL(bytes); | ||
} | ||
} | ||
var target = $h.getTarget('upload', params); | ||
var method = $.getOpt('uploadMethod'); | ||
$.xhr.open(method, target); | ||
if ($.getOpt('method') === 'octet') { | ||
$.xhr.setRequestHeader('Content-Type', 'application/octet-stream'); | ||
} | ||
return _this.loaded = e.loaded || 0; | ||
$.xhr.timeout = $.getOpt('xhrTimeout'); | ||
$.xhr.withCredentials = $.getOpt('withCredentials'); | ||
// Add data from header options | ||
var customHeaders = $.getOpt('headers'); | ||
if(typeof customHeaders === 'function') { | ||
customHeaders = customHeaders($.fileObj, $); | ||
} | ||
$h.each(customHeaders, function(k,v) { | ||
$.xhr.setRequestHeader(k, v); | ||
}); | ||
if ($.getOpt('chunkFormat') == 'blob') { | ||
$.xhr.send(data); | ||
} | ||
}; | ||
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(); | ||
$.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 { | ||
_this.callback('retry', _this.message()); | ||
_this.abort(); | ||
_this.retries++; | ||
retryInterval = getOpt('chunkRetryInterval'); | ||
if (retryInterval != null) { | ||
return setTimeout(_this.send, retryInterval); | ||
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 | ||
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'); | ||
} | ||
} | ||
}; | ||
this.xhr.addEventListener('load', doneHandler, false); | ||
this.xhr.addEventListener('error', doneHandler, false); | ||
headers = this.getOpt('headers'); | ||
if (headers == null) { | ||
headers = {}; | ||
} | ||
for (key in headers) { | ||
value = headers[key]; | ||
this.xhr.setRequestHeader(key, value); | ||
} | ||
if (this.fileObj.file.slice != null) { | ||
func = 'slice'; | ||
} else if (this.fileObj.file.mozSlice != null) { | ||
func = 'mozSlice'; | ||
} else if (this.fileObj.file.webkitSlice != null) { | ||
func = 'webkitSlice'; | ||
} else { | ||
func = 'slice'; | ||
} | ||
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 | ||
$.message = function(){ | ||
return($.xhr ? $.xhr.responseText : ''); | ||
}; | ||
customQuery = this.getOpt('query'); | ||
if (typeof customQuery === 'function') { | ||
customQuery = customQuery(this.fileObj, this); | ||
} | ||
if (customQuery == null) { | ||
customQuery = {}; | ||
} | ||
for (key in customQuery) { | ||
value = customQuery[key]; | ||
pushParams(query, key, value); | ||
} | ||
if (this.getOpt('method') === 'octet') { | ||
data = bytes; | ||
params = []; | ||
for (key in query) { | ||
value = query[key]; | ||
this.pushParams(params, key, value); | ||
$.progress = function(relative){ | ||
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': | ||
case 'error': | ||
return(1*factor); | ||
case 'pending': | ||
return(0*factor); | ||
default: | ||
return($.loaded/($.endByte-$.startByte)*factor); | ||
} | ||
target += '?' + params.join('&'); | ||
} else { | ||
data = new FormData(); | ||
for (key in query) { | ||
value = query[key]; | ||
data.append(key, value); | ||
} | ||
data.append(this.getOpt('fileParameterName'), bytes); | ||
} | ||
this.xhr.open('POST', target); | ||
return this.xhr.send(data); | ||
}; | ||
}; | ||
return(this); | ||
} | ||
ResumableChunk.prototype.abort = function() { | ||
if (this.xhr != null) { | ||
this.xhr.abort(); | ||
} | ||
return this.xhr = null; | ||
}; | ||
// QUEUE | ||
$.uploadNextChunk = function(){ | ||
var found = false; | ||
ResumableChunk.prototype.status = function() { | ||
var maxChunkRetries, permanentErrors, _ref; | ||
permanentErrors = this.getOpt('permanentErrors'); | ||
maxChunkRetries = this.getOpt('maxChunkRetries'); | ||
if (permanentErrors == null) { | ||
permanentErrors = {}; | ||
// In some cases (such as videos) it's really handy to upload the first | ||
// and last chunk of a file quickly; this let's the server check the file's | ||
// metadata and determine if there's even a point in continuing. | ||
if ($.getOpt('prioritizeFirstAndLastChunk')) { | ||
$h.each($.files, function(file){ | ||
if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) { | ||
file.chunks[0].send(); | ||
found = true; | ||
return(false); | ||
} | ||
if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[file.chunks.length-1].preprocessState === 0) { | ||
file.chunks[file.chunks.length-1].send(); | ||
found = true; | ||
return(false); | ||
} | ||
}); | ||
if(found) return(true); | ||
} | ||
if (maxChunkRetries == null) { | ||
maxChunkRetries = 0; | ||
} | ||
if (this.xhr == null) { | ||
return 'pending'; | ||
} else if (this.xhr.readyState < 4) { | ||
return 'uploading'; | ||
} else if (this.xhr.status === 200) { | ||
return 'success'; | ||
} else if ((_ref = this.xhr.status, __indexOf.call(permanentErrors, _ref) >= 0) || (this.retries >= maxChunkRetries)) { | ||
return 'error'; | ||
} else { | ||
this.abort(); | ||
return 'pending'; | ||
} | ||
}; | ||
ResumableChunk.prototype.message = function() { | ||
return (this.xhr != null ? this.xhr.responseText : ''); | ||
}; | ||
// Now, simply look for the next, best thing to upload | ||
$h.each($.files, function(file){ | ||
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); | ||
ResumableChunk.prototype.progress = function(relative) { | ||
var factor; | ||
factor = (relative != null ? (this.endByte - this.startByte) / this.fileObjSize : 1); | ||
switch (this.status()) { | ||
case 'success': | ||
case 'error': | ||
return 1 * factor; | ||
case 'pending': | ||
return 0 * factor; | ||
default: | ||
return this.loaded / (this.endByte - this.startByte) * factor; | ||
// The are no more outstanding chunks to upload, check is everything is done | ||
var outstanding = false; | ||
$h.each($.files, function(file){ | ||
if(!file.isComplete()) { | ||
outstanding = true; | ||
return(false); | ||
} | ||
}); | ||
if(!outstanding) { | ||
// All chunks have been uploaded, complete | ||
$.fire('complete'); | ||
} | ||
return(false); | ||
}; | ||
return ResumableChunk; | ||
})(); | ||
// PUBLIC METHODS FOR RESUMABLE.JS | ||
$.assignBrowse = function(domNodes, isDirectory){ | ||
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; | ||
window.ResumableFile = ResumableFile = (function() { | ||
$h.each(domNodes, function(domNode) { | ||
var input; | ||
if(domNode.tagName==='INPUT' && domNode.type==='file'){ | ||
input = domNode; | ||
} else { | ||
input = document.createElement('input'); | ||
input.setAttribute('type', 'file'); | ||
input.style.display = 'none'; | ||
domNode.addEventListener('click', function(){ | ||
input.style.opacity = 0; | ||
input.style.display='block'; | ||
input.focus(); | ||
input.click(); | ||
input.style.display='none'; | ||
}, false); | ||
domNode.appendChild(input); | ||
} | ||
var maxFiles = $.getOpt('maxFiles'); | ||
if (typeof(maxFiles)==='undefined'||maxFiles!=1){ | ||
input.setAttribute('multiple', 'multiple'); | ||
} else { | ||
input.removeAttribute('multiple'); | ||
} | ||
if(isDirectory){ | ||
input.setAttribute('webkitdirectory', 'webkitdirectory'); | ||
} else { | ||
input.removeAttribute('webkitdirectory'); | ||
} | ||
// When new files are added, simply append them to the overall list | ||
input.addEventListener('change', function(e){ | ||
appendFilesFromFileList(e.target.files,e); | ||
var clearInput = $.getOpt('clearInput'); | ||
if (clearInput) { | ||
e.target.value = ''; | ||
} | ||
}, false); | ||
}); | ||
}; | ||
$.assignDrop = function(domNodes){ | ||
if(typeof(domNodes.length)=='undefined') domNodes = [domNodes]; | ||
function ResumableFile(resumableObj, file) { | ||
this.resumableObj = resumableObj; | ||
this.file = file; | ||
this.opt = {}; | ||
this._prevProgress = 0; | ||
this.fileName = this.file.fileName || this.file.name; | ||
this.size = this.file.size; | ||
this.relativePath = this.file.webkitRelativePath || this.fileName; | ||
this.uniqueIdentifier = this.resumableObj.generateUniqueIdentifier(this.file); | ||
this._error = false; | ||
this.chunks = []; | ||
this.bootstrap(); | ||
} | ||
ResumableFile.prototype.getOpt = function(o) { | ||
return this.resumableObj.getOpt(o); | ||
$h.each(domNodes, function(domNode) { | ||
domNode.addEventListener('dragover', preventDefault, false); | ||
domNode.addEventListener('dragenter', preventDefault, false); | ||
domNode.addEventListener('drop', onDrop, false); | ||
}); | ||
}; | ||
$.unAssignDrop = function(domNodes) { | ||
if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes]; | ||
ResumableFile.prototype.chunkEvent = function(event, message) { | ||
switch (event) { | ||
case "progress": | ||
return this.resumableObj.fire('fileProgress', this); | ||
case "error": | ||
this.abort(); | ||
this._error = true; | ||
this.chunks = []; | ||
return this.resumableObj.fire('fileError', this, message); | ||
case "success": | ||
if (!this._error) { | ||
this.resumableObj.fire('fileProgress', this); | ||
if (this.progress() === 1) { | ||
return this.resumableObj.fire('fileSuccess', this, message); | ||
} | ||
} | ||
break; | ||
case "retry": | ||
return this.resumableObj.fire('fileRetry', this); | ||
} | ||
$h.each(domNodes, function(domNode) { | ||
domNode.removeEventListener('dragover', preventDefault); | ||
domNode.removeEventListener('dragenter', preventDefault); | ||
domNode.removeEventListener('drop', onDrop); | ||
}); | ||
}; | ||
ResumableFile.prototype.abort = function() { | ||
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(); | ||
$.isUploading = function(){ | ||
var uploading = false; | ||
$h.each($.files, function(file){ | ||
if (file.isUploading()) { | ||
uploading = true; | ||
return(false); | ||
} | ||
} | ||
return this.resumableObj.fire('fileProgress', this); | ||
}); | ||
return(uploading); | ||
}; | ||
ResumableFile.prototype.cancel = function() { | ||
var c, _chunks, _i, _len; | ||
_chunks = this.chunks; | ||
this.chunks = []; | ||
for (_i = 0, _len = _chunks.length; _i < _len; _i++) { | ||
c = _chunks[_i]; | ||
if (c.status() === 'uploading') { | ||
c.abort(); | ||
this.resumableObj.uploadNextChunk(); | ||
} | ||
$.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(); | ||
} | ||
this.resumableObj.removeFile(this); | ||
return this.resumableObj.fire('fileProgress', this); | ||
}; | ||
ResumableFile.prototype.retry = function() { | ||
this.bootstrap(); | ||
return this.resumableObj.upload(); | ||
$.pause = function(){ | ||
// Resume all chunks currently being uploaded | ||
$h.each($.files, function(file){ | ||
file.abort(); | ||
}); | ||
$.fire('pause'); | ||
}; | ||
ResumableFile.prototype.bootstrap = function() { | ||
var max, offset, round, _i, _ref, _results; | ||
this.abort(); | ||
this._error = false; | ||
this.chunks = []; | ||
this._prevProgress = 0; | ||
if (this.getOpt('forceChunkSize') != null) { | ||
round = Math.ceil; | ||
} else { | ||
round = Math.floor; | ||
$.cancel = function(){ | ||
$.fire('beforeCancel'); | ||
for(var i = $.files.length - 1; i >= 0; i--) { | ||
$.files[i].cancel(); | ||
} | ||
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))); | ||
} | ||
return _results; | ||
$.fire('cancel'); | ||
}; | ||
ResumableFile.prototype.progress = function() { | ||
var c, error, ret, _i, _len, _ref; | ||
if (this._error) { | ||
return 1.; | ||
$.progress = function(){ | ||
var totalDone = 0; | ||
var totalSize = 0; | ||
// Resume all chunks currently being uploaded | ||
$h.each($.files, function(file){ | ||
totalDone += file.progress()*file.size; | ||
totalSize += file.size; | ||
}); | ||
return(totalSize>0 ? totalDone/totalSize : 0); | ||
}; | ||
$.addFile = function(file, event){ | ||
appendFilesFromFileList([file], event); | ||
}; | ||
$.removeFile = function(file){ | ||
for(var i = $.files.length - 1; i >= 0; i--) { | ||
if($.files[i] === file) { | ||
$.files.splice(i, 1); | ||
} | ||
} | ||
ret = 0; | ||
error = false; | ||
_ref = this.chunks; | ||
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
c = _ref[_i]; | ||
error = c.status() === 'error'; | ||
ret += c.progress(true); | ||
} | ||
ret = (error || error > 0.99 ? 1 : ret); | ||
ret = Math.max(this._prevProgress, ret); | ||
this._prevProgress = ret; | ||
return ret; | ||
}; | ||
$.getFromUniqueIdentifier = function(uniqueIdentifier){ | ||
var ret = false; | ||
$h.each($.files, function(f){ | ||
if(f.uniqueIdentifier==uniqueIdentifier) ret = f; | ||
}); | ||
return(ret); | ||
}; | ||
$.getSize = function(){ | ||
var totalSize = 0; | ||
$h.each($.files, function(file){ | ||
totalSize += file.size; | ||
}); | ||
return(totalSize); | ||
}; | ||
$.handleDropEvent = function (e) { | ||
onDrop(e); | ||
}; | ||
$.handleChangeEvent = function (e) { | ||
appendFilesFromFileList(e.target.files, e); | ||
e.target.value = ''; | ||
}; | ||
$.updateQuery = function(query){ | ||
$.opts.query = query; | ||
}; | ||
return ResumableFile; | ||
return(this); | ||
}; | ||
})(); | ||
}).call(this); | ||
// Node.js-style export for Node and Component | ||
if (typeof module != 'undefined') { | ||
module.exports = Resumable; | ||
} else if (typeof define === "function" && define.amd) { | ||
// AMD/requirejs: Define the module | ||
define(function(){ | ||
return Resumable; | ||
}); | ||
} else { | ||
// Browser: Expose to window | ||
window.Resumable = Resumable; | ||
} | ||
})(); |
@@ -5,2 +5,3 @@ var express = require('express'); | ||
var multipart = require('connect-multiparty'); | ||
var crypto = require('crypto'); | ||
@@ -12,28 +13,30 @@ // Host most stuff in the public folder | ||
// Uncomment to allow CORS | ||
// app.use(function (req, res, next) { | ||
// res.header('Access-Control-Allow-Origin', '*'); | ||
// 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){ | ||
// 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 | ||
@@ -44,4 +47,4 @@ app.get('/upload', function(req, res){ | ||
res.send((status == 'found' ? 200 : 404), status); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -48,0 +51,0 @@ app.get('/download/:identifier', function(req, res){ |
# 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. | ||
@@ -13,7 +14,11 @@ To install and run: | ||
## 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' |
@@ -196,3 +196,3 @@ var fs = require('fs'), path = require('path'), util = require('util'), Stream = require('stream').Stream; | ||
fs.unlink(chunkFilename, function(err) { | ||
if (options.onError) opentions.onError(err); | ||
if (err && options.onError) options.onError(err); | ||
}); | ||
@@ -213,2 +213,2 @@ | ||
return $; | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
256236
43
3763
193
2