/*!
* Uploader - Uploader library implements html5 file upload and provides multiple simultaneous, stable, fault tolerant and resumable uploads
* @version v0.5.6
* @author dolymood
* @link https://github.com/simple-uploader/Uploader
* @license MIT
*/
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Uploader=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1) {
// HTTP 200, perfect
// HTTP 202 Accepted - The request has been accepted for processing, but the processing has not been completed.
_status = STATUS.SUCCESS
} else if (this.uploader.opts.permanentErrors.indexOf(this.xhr.status) > -1 ||
!isTest && this.retries >= this.uploader.opts.maxChunkRetries) {
// HTTP 415/500/501, permanent error
_status = STATUS.ERROR
} else {
// this should never happen, but we'll reset and queue a retry
// a likely case for this would be 503 service unavailable
this.abort()
_status = STATUS.PENDING
}
var processedState = this.processedState
if (processedState && processedState.err) {
_status = STATUS.ERROR
}
return _status
}
},
message: function () {
return this.xhr ? this.xhr.responseText : ''
},
progress: function () {
if (this.pendingRetry) {
return 0
}
var s = this.status()
if (s === STATUS.SUCCESS || s === STATUS.ERROR) {
return 1
} else if (s === STATUS.PENDING) {
return 0
} else {
return this.total > 0 ? this.loaded / this.total : 0
}
},
sizeUploaded: function () {
var size = this.endByte - this.startByte
// can't return only chunk.loaded value, because it is bigger than chunk size
if (this.status() !== STATUS.SUCCESS) {
size = this.progress() * size
}
return size
},
prepareXhrRequest: function (method, isTest, paramsMethod, blob) {
// Add data from the query options
var query = utils.evalOpts(this.uploader.opts.query, this.file, this, isTest)
query = utils.extend(this.getParams(), query)
// processParams
query = this.uploader.opts.processParams(query, this.file, this, isTest)
var target = utils.evalOpts(this.uploader.opts.target, this.file, this, isTest)
var data = null
if (method === 'GET' || paramsMethod === 'octet') {
// Add data from the query options
var params = []
utils.each(query, function (v, k) {
params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='))
})
target = this.getTarget(target, params)
data = blob || null
} else {
// Add data from the query options
data = new FormData()
utils.each(query, function (v, k) {
data.append(k, v)
})
if (typeof blob !== 'undefined') {
data.append(this.uploader.opts.fileParameterName, blob, this.file.name)
}
}
this.xhr.open(method, target, true)
this.xhr.withCredentials = this.uploader.opts.withCredentials
// Add data from header options
utils.each(utils.evalOpts(this.uploader.opts.headers, this.file, this, isTest), function (v, k) {
this.xhr.setRequestHeader(k, v)
}, this)
return data
}
})
module.exports = Chunk
},{"./utils":5}],2:[function(_dereq_,module,exports){
var each = _dereq_('./utils').each
var event = {
_eventData: null,
on: function (name, func) {
if (!this._eventData) this._eventData = {}
if (!this._eventData[name]) this._eventData[name] = []
var listened = false
each(this._eventData[name], function (fuc) {
if (fuc === func) {
listened = true
return false
}
})
if (!listened) {
this._eventData[name].push(func)
}
},
off: function (name, func) {
if (!this._eventData) this._eventData = {}
if (!this._eventData[name] || !this._eventData[name].length) return
if (func) {
each(this._eventData[name], function (fuc, i) {
if (fuc === func) {
this._eventData[name].splice(i, 1)
return false
}
}, this)
} else {
this._eventData[name] = []
}
},
trigger: function (name) {
if (!this._eventData) this._eventData = {}
if (!this._eventData[name]) return true
var args = this._eventData[name].slice.call(arguments, 1)
var preventDefault = false
each(this._eventData[name], function (fuc) {
preventDefault = fuc.apply(this, args) === false || preventDefault
}, this)
return !preventDefault
}
}
module.exports = event
},{"./utils":5}],3:[function(_dereq_,module,exports){
var utils = _dereq_('./utils')
var event = _dereq_('./event')
var File = _dereq_('./file')
var Chunk = _dereq_('./chunk')
var version = '0.5.6'
var isServer = typeof window === 'undefined'
// ie10+
var ie10plus = isServer ? false : window.navigator.msPointerEnabled
var support = (function () {
if (isServer) {
return false
}
var sliceName = 'slice'
var _support = utils.isDefined(window.File) && utils.isDefined(window.Blob) &&
utils.isDefined(window.FileList)
var bproto = null
if (_support) {
bproto = window.Blob.prototype
utils.each(['slice', 'webkitSlice', 'mozSlice'], function (n) {
if (bproto[n]) {
sliceName = n
return false
}
})
_support = !!bproto[sliceName]
}
if (_support) Uploader.sliceName = sliceName
bproto = null
return _support
})()
var supportDirectory = (function () {
if (isServer) {
return false
}
var input = window.document.createElement('input')
input.type = 'file'
var sd = 'webkitdirectory' in input || 'directory' in input
input = null
return sd
})()
function Uploader (opts) {
this.support = support
/* istanbul ignore if */
if (!this.support) {
return
}
this.supportDirectory = supportDirectory
utils.defineNonEnumerable(this, 'filePaths', {})
this.opts = utils.extend({}, Uploader.defaults, opts || {})
this.preventEvent = utils.bind(this._preventEvent, this)
File.call(this, this)
}
/**
* Default read function using the webAPI
*
* @function webAPIFileRead(fileObj, fileType, startByte, endByte, chunk)
*
*/
var webAPIFileRead = function (fileObj, fileType, startByte, endByte, chunk) {
chunk.readFinished(fileObj.file[Uploader.sliceName](startByte, endByte, fileType))
}
Uploader.version = version
Uploader.defaults = {
chunkSize: 1024 * 1024,
forceChunkSize: false,
simultaneousUploads: 3,
singleFile: false,
fileParameterName: 'file',
progressCallbacksInterval: 500,
speedSmoothingFactor: 0.1,
query: {},
headers: {},
withCredentials: false,
preprocess: null,
method: 'multipart',
testMethod: 'GET',
uploadMethod: 'POST',
prioritizeFirstAndLastChunk: false,
allowDuplicateUploads: false,
target: '/',
testChunks: true,
generateUniqueIdentifier: null,
maxChunkRetries: 0,
chunkRetryInterval: null,
permanentErrors: [404, 415, 500, 501],
successStatuses: [200, 201, 202],
onDropStopPropagation: false,
initFileFn: null,
readFileFn: webAPIFileRead,
checkChunkUploadedByResponse: null,
initialPaused: false,
processResponse: function (response, cb) {
cb(null, response)
},
processParams: function (params) {
return params
}
}
Uploader.utils = utils
Uploader.event = event
Uploader.File = File
Uploader.Chunk = Chunk
// inherit file
Uploader.prototype = utils.extend({}, File.prototype)
// inherit event
utils.extend(Uploader.prototype, event)
utils.extend(Uploader.prototype, {
constructor: Uploader,
_trigger: function (name) {
var args = utils.toArray(arguments)
var preventDefault = !this.trigger.apply(this, arguments)
if (name !== 'catchAll') {
args.unshift('catchAll')
preventDefault = !this.trigger.apply(this, args) || preventDefault
}
return !preventDefault
},
_triggerAsync: function () {
var args = arguments
utils.nextTick(function () {
this._trigger.apply(this, args)
}, this)
},
addFiles: function (files, evt) {
var _files = []
var oldFileListLen = this.fileList.length
utils.each(files, function (file) {
// Uploading empty file IE10/IE11 hangs indefinitely
// Directories have size `0` and name `.`
// Ignore already added files if opts.allowDuplicateUploads is set to false
if ((!ie10plus || ie10plus && file.size > 0) && !(file.size % 4096 === 0 && (file.name === '.' || file.fileName === '.'))) {
var uniqueIdentifier = this.generateUniqueIdentifier(file)
if (this.opts.allowDuplicateUploads || !this.getFromUniqueIdentifier(uniqueIdentifier)) {
var _file = new File(this, file, this)
_file.uniqueIdentifier = uniqueIdentifier
if (this._trigger('fileAdded', _file, evt)) {
_files.push(_file)
} else {
File.prototype.removeFile.call(this, _file)
}
}
}
}, this)
// get new fileList
var newFileList = this.fileList.slice(oldFileListLen)
if (this._trigger('filesAdded', _files, newFileList, evt)) {
utils.each(_files, function (file) {
if (this.opts.singleFile && this.files.length > 0) {
this.removeFile(this.files[0])
}
this.files.push(file)
}, this)
this._trigger('filesSubmitted', _files, newFileList, evt)
} else {
utils.each(newFileList, function (file) {
File.prototype.removeFile.call(this, file)
}, this)
}
},
addFile: function (file, evt) {
this.addFiles([file], evt)
},
cancel: function () {
for (var i = this.fileList.length - 1; i >= 0; i--) {
this.fileList[i].cancel()
}
},
removeFile: function (file) {
File.prototype.removeFile.call(this, file)
this._trigger('fileRemoved', file)
},
generateUniqueIdentifier: function (file) {
var custom = this.opts.generateUniqueIdentifier
if (utils.isFunction(custom)) {
return custom(file)
}
/* istanbul ignore next */
// Some confusion in different versions of Firefox
var relativePath = file.relativePath || file.webkitRelativePath || file.fileName || file.name
/* istanbul ignore next */
return file.size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, '')
},
getFromUniqueIdentifier: function (uniqueIdentifier) {
var ret = false
utils.each(this.files, function (file) {
if (file.uniqueIdentifier === uniqueIdentifier) {
ret = file
return false
}
})
return ret
},
uploadNextChunk: function (preventEvents) {
var found = false
var pendingStatus = Chunk.STATUS.PENDING
var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
if (this.opts.prioritizeFirstAndLastChunk) {
utils.each(this.files, function (file) {
if (file.paused) {
return
}
if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
// waiting for current file's first chunk response
return
}
if (file.chunks.length && file.chunks[0].status() === pendingStatus) {
file.chunks[0].send()
found = true
return false
}
if (file.chunks.length > 1 && file.chunks[file.chunks.length - 1].status() === pendingStatus) {
file.chunks[file.chunks.length - 1].send()
found = true
return false
}
})
if (found) {
return found
}
}
// Now, simply look for the next, best thing to upload
utils.each(this.files, function (file) {
if (!file.paused) {
if (checkChunkUploaded && !file._firstResponse && file.isUploading()) {
// waiting for current file's first chunk response
return
}
utils.each(file.chunks, function (chunk) {
if (chunk.status() === pendingStatus) {
chunk.send()
found = true
return false
}
})
}
if (found) {
return false
}
})
if (found) {
return true
}
// The are no more outstanding chunks to upload, check is everything is done
var outstanding = false
utils.each(this.files, function (file) {
if (!file.isComplete()) {
outstanding = true
return false
}
})
// should check files now
// if now files in list
// should not trigger complete event
if (!outstanding && !preventEvents && this.files.length) {
// All chunks have been uploaded, complete
this._triggerAsync('complete')
}
return outstanding
},
upload: function (preventEvents) {
// Make sure we don't start too many uploads at once
var ret = this._shouldUploadNext()
if (ret === false) {
return
}
!preventEvents && this._trigger('uploadStart')
var started = false
for (var num = 1; num <= this.opts.simultaneousUploads - ret; num++) {
started = this.uploadNextChunk(!preventEvents) || started
if (!started && preventEvents) {
// completed
break
}
}
if (!started && !preventEvents) {
this._triggerAsync('complete')
}
},
/**
* should upload next chunk
* @function
* @returns {Boolean|Number}
*/
_shouldUploadNext: function () {
var num = 0
var should = true
var simultaneousUploads = this.opts.simultaneousUploads
var uploadingStatus = Chunk.STATUS.UPLOADING
utils.each(this.files, function (file) {
utils.each(file.chunks, function (chunk) {
if (chunk.status() === uploadingStatus) {
num++
if (num >= simultaneousUploads) {
should = false
return false
}
}
})
return should
})
// if should is true then return uploading chunks's length
return should && num
},
/**
* Assign a browse action to one or more DOM nodes.
* @function
* @param {Element|Array.} domNodes
* @param {boolean} isDirectory Pass in true to allow directories to
* @param {boolean} singleFile prevent multi file upload
* @param {Object} attributes set custom attributes:
* http://www.w3.org/TR/html-markup/input.file.html#input.file-attributes
* eg: accept: 'image/*'
* be selected (Chrome only).
*/
assignBrowse: function (domNodes, isDirectory, singleFile, attributes) {
if (typeof domNodes.length === 'undefined') {
domNodes = [domNodes]
}
utils.each(domNodes, function (domNode) {
var input
if (domNode.tagName === 'INPUT' && domNode.type === 'file') {
input = domNode
} else {
input = document.createElement('input')
input.setAttribute('type', 'file')
// display:none - not working in opera 12
utils.extend(input.style, {
visibility: 'hidden',
position: 'absolute',
width: '1px',
height: '1px'
})
// for opera 12 browser, input must be assigned to a document
domNode.appendChild(input)
// https://developer.mozilla.org/en/using_files_from_web_applications)
// event listener is executed two times
// first one - original mouse click event
// second - input.click(), input is inside domNode
domNode.addEventListener('click', function (e) {
if (domNode.tagName.toLowerCase() === 'label') {
return
}
input.click()
}, false)
}
if (!this.opts.singleFile && !singleFile) {
input.setAttribute('multiple', 'multiple')
}
if (isDirectory) {
input.setAttribute('webkitdirectory', 'webkitdirectory')
}
attributes && utils.each(attributes, function (value, key) {
input.setAttribute(key, value)
})
// When new files are added, simply append them to the overall list
var that = this
input.addEventListener('change', function (e) {
that._trigger(e.type, e)
if (e.target.value) {
that.addFiles(e.target.files, e)
e.target.value = ''
}
}, false)
}, this)
},
onDrop: function (evt) {
this._trigger(evt.type, evt)
if (this.opts.onDropStopPropagation) {
evt.stopPropagation()
}
evt.preventDefault()
this._parseDataTransfer(evt.dataTransfer, evt)
},
_parseDataTransfer: function (dataTransfer, evt) {
if (dataTransfer.items && dataTransfer.items[0] &&
dataTransfer.items[0].webkitGetAsEntry) {
this.webkitReadDataTransfer(dataTransfer, evt)
} else {
this.addFiles(dataTransfer.files, evt)
}
},
webkitReadDataTransfer: function (dataTransfer, evt) {
var self = this
var queue = dataTransfer.items.length
var files = []
utils.each(dataTransfer.items, function (item) {
var entry = item.webkitGetAsEntry()
if (!entry) {
decrement()
return
}
if (entry.isFile) {
// due to a bug in Chrome's File System API impl - #149735
fileReadSuccess(item.getAsFile(), entry.fullPath)
} else {
readDirectory(entry.createReader())
}
})
function readDirectory (reader) {
reader.readEntries(function (entries) {
if (entries.length) {
queue += entries.length
utils.each(entries, function (entry) {
if (entry.isFile) {
var fullPath = entry.fullPath
entry.file(function (file) {
fileReadSuccess(file, fullPath)
}, readError)
} else if (entry.isDirectory) {
readDirectory(entry.createReader())
}
})
readDirectory(reader)
} else {
decrement()
}
}, readError)
}
function fileReadSuccess (file, fullPath) {
// relative path should not start with "/"
file.relativePath = fullPath.substring(1)
files.push(file)
decrement()
}
function readError (fileError) {
throw fileError
}
function decrement () {
if (--queue === 0) {
self.addFiles(files, evt)
}
}
},
_assignHelper: function (domNodes, handles, remove) {
if (typeof domNodes.length === 'undefined') {
domNodes = [domNodes]
}
var evtMethod = remove ? 'removeEventListener' : 'addEventListener'
utils.each(domNodes, function (domNode) {
utils.each(handles, function (handler, name) {
domNode[evtMethod](name, handler, false)
}, this)
}, this)
},
_preventEvent: function (e) {
utils.preventEvent(e)
this._trigger(e.type, e)
},
/**
* Assign one or more DOM nodes as a drop target.
* @function
* @param {Element|Array.} domNodes
*/
assignDrop: function (domNodes) {
this._onDrop = utils.bind(this.onDrop, this)
this._assignHelper(domNodes, {
dragover: this.preventEvent,
dragenter: this.preventEvent,
dragleave: this.preventEvent,
drop: this._onDrop
})
},
/**
* Un-assign drop event from DOM nodes
* @function
* @param domNodes
*/
unAssignDrop: function (domNodes) {
this._assignHelper(domNodes, {
dragover: this.preventEvent,
dragenter: this.preventEvent,
dragleave: this.preventEvent,
drop: this._onDrop
}, true)
this._onDrop = null
}
})
module.exports = Uploader
},{"./chunk":1,"./event":2,"./file":4,"./utils":5}],4:[function(_dereq_,module,exports){
var utils = _dereq_('./utils')
var Chunk = _dereq_('./chunk')
function File (uploader, file, parent) {
utils.defineNonEnumerable(this, 'uploader', uploader)
this.isRoot = this.isFolder = uploader === this
utils.defineNonEnumerable(this, 'parent', parent || null)
utils.defineNonEnumerable(this, 'files', [])
utils.defineNonEnumerable(this, 'fileList', [])
utils.defineNonEnumerable(this, 'chunks', [])
utils.defineNonEnumerable(this, '_errorFiles', [])
utils.defineNonEnumerable(this, 'file', null)
this.id = utils.uid()
if (this.isRoot || !file) {
this.file = null
} else {
if (utils.isString(file)) {
// folder
this.isFolder = true
this.file = null
this.path = file
if (this.parent.path) {
file = file.substr(this.parent.path.length)
}
this.name = file.charAt(file.length - 1) === '/' ? file.substr(0, file.length - 1) : file
} else {
this.file = file
this.fileType = this.file.type
this.name = file.fileName || file.name
this.size = file.size
this.relativePath = file.relativePath || file.webkitRelativePath || this.name
this._parseFile()
}
}
this.paused = uploader.opts.initialPaused
this.error = false
this.allError = false
this.aborted = false
this.completed = false
this.averageSpeed = 0
this.currentSpeed = 0
this._lastProgressCallback = Date.now()
this._prevUploadedSize = 0
this._prevProgress = 0
this.bootstrap()
}
utils.extend(File.prototype, {
_parseFile: function () {
var ppaths = parsePaths(this.relativePath)
if (ppaths.length) {
var filePaths = this.uploader.filePaths
utils.each(ppaths, function (path, i) {
var folderFile = filePaths[path]
if (!folderFile) {
folderFile = new File(this.uploader, path, this.parent)
filePaths[path] = folderFile
this._updateParentFileList(folderFile)
}
this.parent = folderFile
folderFile.files.push(this)
if (!ppaths[i + 1]) {
folderFile.fileList.push(this)
}
}, this)
} else {
this._updateParentFileList()
}
},
_updateParentFileList: function (file) {
if (!file) {
file = this
}
var p = this.parent
if (p) {
p.fileList.push(file)
}
},
_eachAccess: function (eachFn, fileFn) {
if (this.isFolder) {
utils.each(this.files, function (f, i) {
return eachFn.call(this, f, i)
}, this)
return
}
fileFn.call(this, this)
},
bootstrap: function () {
if (this.isFolder) return
var opts = this.uploader.opts
if (utils.isFunction(opts.initFileFn)) {
opts.initFileFn.call(this, this)
}
this.abort(true)
this._resetError()
// Rebuild stack of chunks from file
this._prevProgress = 0
var round = opts.forceChunkSize ? Math.ceil : Math.floor
var chunks = Math.max(round(this.size / opts.chunkSize), 1)
for (var offset = 0; offset < chunks; offset++) {
this.chunks.push(new Chunk(this.uploader, this, offset))
}
},
_measureSpeed: function () {
var smoothingFactor = this.uploader.opts.speedSmoothingFactor
var timeSpan = Date.now() - this._lastProgressCallback
if (!timeSpan) {
return
}
var uploaded = this.sizeUploaded()
// Prevent negative upload speed after file upload resume
this.currentSpeed = Math.max((uploaded - this._prevUploadedSize) / timeSpan * 1000, 0)
this.averageSpeed = smoothingFactor * this.currentSpeed + (1 - smoothingFactor) * this.averageSpeed
this._prevUploadedSize = uploaded
if (this.parent && this.parent._checkProgress()) {
this.parent._measureSpeed()
}
},
_checkProgress: function (file) {
return Date.now() - this._lastProgressCallback >= this.uploader.opts.progressCallbacksInterval
},
_chunkEvent: function (chunk, evt, message) {
var uploader = this.uploader
var STATUS = Chunk.STATUS
var that = this
var rootFile = this.getRoot()
var triggerProgress = function () {
that._measureSpeed()
uploader._trigger('fileProgress', rootFile, that, chunk)
that._lastProgressCallback = Date.now()
}
switch (evt) {
case STATUS.PROGRESS:
if (this._checkProgress()) {
triggerProgress()
}
break
case STATUS.ERROR:
this._error()
this.abort(true)
uploader._trigger('fileError', rootFile, this, message, chunk)
break
case STATUS.SUCCESS:
this._updateUploadedChunks(message, chunk)
if (this.error) {
return
}
clearTimeout(this._progeressId)
this._progeressId = 0
var timeDiff = Date.now() - this._lastProgressCallback
if (timeDiff < uploader.opts.progressCallbacksInterval) {
this._progeressId = setTimeout(triggerProgress, uploader.opts.progressCallbacksInterval - timeDiff)
}
if (this.isComplete()) {
clearTimeout(this._progeressId)
triggerProgress()
this.currentSpeed = 0
this.averageSpeed = 0
uploader._trigger('fileSuccess', rootFile, this, message, chunk)
if (rootFile.isComplete()) {
uploader._trigger('fileComplete', rootFile, this)
}
} else if (!this._progeressId) {
triggerProgress()
}
break
case STATUS.RETRY:
uploader._trigger('fileRetry', rootFile, this, chunk)
break
}
},
_updateUploadedChunks: function (message, chunk) {
var checkChunkUploaded = this.uploader.opts.checkChunkUploadedByResponse
if (checkChunkUploaded) {
var xhr = chunk.xhr
utils.each(this.chunks, function (_chunk) {
if (!_chunk.tested) {
var uploaded = checkChunkUploaded.call(this, _chunk, message)
if (_chunk === chunk && !uploaded) {
// fix the first chunk xhr status
// treated as success but checkChunkUploaded is false
// so the current chunk should be uploaded again
_chunk.xhr = null
}
if (uploaded) {
// first success and other chunks are uploaded
// then set xhr, so the uploaded chunks
// will be treated as success too
_chunk.xhr = xhr
}
_chunk.tested = true
}
}, this)
if (!this._firstResponse) {
this._firstResponse = true
this.uploader.upload(true)
} else {
this.uploader.uploadNextChunk()
}
} else {
this.uploader.uploadNextChunk()
}
},
_error: function () {
this.error = this.allError = true
var parent = this.parent
while (parent && parent !== this.uploader) {
parent._errorFiles.push(this)
parent.error = true
if (parent._errorFiles.length === parent.files.length) {
parent.allError = true
}
parent = parent.parent
}
},
_resetError: function () {
this.error = this.allError = false
var parent = this.parent
var index = -1
while (parent && parent !== this.uploader) {
index = parent._errorFiles.indexOf(this)
parent._errorFiles.splice(index, 1)
parent.allError = false
if (!parent._errorFiles.length) {
parent.error = false
}
parent = parent.parent
}
},
isComplete: function () {
if (!this.completed) {
var outstanding = false
this._eachAccess(function (file) {
if (!file.isComplete()) {
outstanding = true
return false
}
}, function () {
if (this.error) {
outstanding = true
} else {
var STATUS = Chunk.STATUS
utils.each(this.chunks, function (chunk) {
var status = chunk.status()
if (status === STATUS.ERROR || status === STATUS.PENDING || status === STATUS.UPLOADING || status === STATUS.READING || chunk.preprocessState === 1 || chunk.readState === 1) {
outstanding = true
return false
}
})
}
})
this.completed = !outstanding
}
return this.completed
},
isUploading: function () {
var uploading = false
this._eachAccess(function (file) {
if (file.isUploading()) {
uploading = true
return false
}
}, function () {
var uploadingStatus = Chunk.STATUS.UPLOADING
utils.each(this.chunks, function (chunk) {
if (chunk.status() === uploadingStatus) {
uploading = true
return false
}
})
})
return uploading
},
resume: function () {
this._eachAccess(function (f) {
f.resume()
}, function () {
this.paused = false
this.aborted = false
this.uploader.upload()
})
this.paused = false
this.aborted = false
},
pause: function () {
this._eachAccess(function (f) {
f.pause()
}, function () {
this.paused = true
this.abort()
})
this.paused = true
},
cancel: function () {
this.uploader.removeFile(this)
},
retry: function (file) {
var fileRetry = function (file) {
if (file.error) {
file.bootstrap()
}
}
if (file) {
file.bootstrap()
} else {
this._eachAccess(fileRetry, function () {
this.bootstrap()
})
}
this.uploader.upload()
},
abort: function (reset) {
if (this.aborted) {
return
}
this.currentSpeed = 0
this.averageSpeed = 0
this.aborted = !reset
var chunks = this.chunks
if (reset) {
this.chunks = []
}
var uploadingStatus = Chunk.STATUS.UPLOADING
utils.each(chunks, function (c) {
if (c.status() === uploadingStatus) {
c.abort()
this.uploader.uploadNextChunk()
}
}, this)
},
progress: function () {
var totalDone = 0
var totalSize = 0
var ret = 0
this._eachAccess(function (file, index) {
totalDone += file.progress() * file.size
totalSize += file.size
if (index === this.files.length - 1) {
ret = totalSize > 0 ? totalDone / totalSize : this.isComplete() ? 1 : 0
}
}, function () {
if (this.error) {
ret = 1
return
}
if (this.chunks.length === 1) {
this._prevProgress = Math.max(this._prevProgress, this.chunks[0].progress())
ret = this._prevProgress
return
}
// Sum up progress across everything
var bytesLoaded = 0
utils.each(this.chunks, function (c) {
// get chunk progress relative to entire file
bytesLoaded += c.progress() * (c.endByte - c.startByte)
})
var percent = bytesLoaded / this.size
// We don't want to lose percentages when an upload is paused
this._prevProgress = Math.max(this._prevProgress, percent > 0.9999 ? 1 : percent)
ret = this._prevProgress
})
return ret
},
getSize: function () {
var size = 0
this._eachAccess(function (file) {
size += file.size
}, function () {
size += this.size
})
return size
},
getFormatSize: function () {
var size = this.getSize()
return utils.formatSize(size)
},
getRoot: function () {
if (this.isRoot) {
return this
}
var parent = this.parent
while (parent) {
if (parent.parent === this.uploader) {
// find it
return parent
}
parent = parent.parent
}
return this
},
sizeUploaded: function () {
var size = 0
this._eachAccess(function (file) {
size += file.sizeUploaded()
}, function () {
utils.each(this.chunks, function (chunk) {
size += chunk.sizeUploaded()
})
})
return size
},
timeRemaining: function () {
var ret = 0
var sizeDelta = 0
var averageSpeed = 0
this._eachAccess(function (file, i) {
if (!file.paused && !file.error) {
sizeDelta += file.size - file.sizeUploaded()
averageSpeed += file.averageSpeed
}
if (i === this.files.length - 1) {
ret = calRet(sizeDelta, averageSpeed)
}
}, function () {
if (this.paused || this.error) {
ret = 0
return
}
var delta = this.size - this.sizeUploaded()
ret = calRet(delta, this.averageSpeed)
})
return ret
function calRet (delta, averageSpeed) {
if (delta && !averageSpeed) {
return Number.POSITIVE_INFINITY
}
if (!delta && !averageSpeed) {
return 0
}
return Math.floor(delta / averageSpeed)
}
},
removeFile: function (file) {
if (file.isFolder) {
while (file.files.length) {
var f = file.files[file.files.length - 1]
this._removeFile(f)
}
}
this._removeFile(file)
},
_delFilePath: function (file) {
if (file.path && this.filePaths) {
delete this.filePaths[file.path]
}
utils.each(file.fileList, function (file) {
this._delFilePath(file)
}, this)
},
_removeFile: function (file) {
if (!file.isFolder) {
utils.each(this.files, function (f, i) {
if (f === file) {
this.files.splice(i, 1)
return false
}
}, this)
file.abort()
var parent = file.parent
var newParent
while (parent && parent !== this) {
newParent = parent.parent
parent._removeFile(file)
parent = newParent
}
}
file.parent === this && utils.each(this.fileList, function (f, i) {
if (f === file) {
this.fileList.splice(i, 1)
return false
}
}, this)
if (!this.isRoot && this.isFolder && !this.files.length) {
this.parent._removeFile(this)
this.uploader._delFilePath(this)
}
file.parent = null
},
getType: function () {
if (this.isFolder) {
return 'folder'
}
return this.file.type && this.file.type.split('/')[1]
},
getExtension: function () {
if (this.isFolder) {
return ''
}
return this.name.substr((~-this.name.lastIndexOf('.') >>> 0) + 2).toLowerCase()
}
})
module.exports = File
function parsePaths (path) {
var ret = []
var paths = path.split('/')
var len = paths.length
var i = 1
paths.splice(len - 1, 1)
len--
if (paths.length) {
while (i <= len) {
ret.push(paths.slice(0, i++).join('/') + '/')
}
}
return ret
}
},{"./chunk":1,"./utils":5}],5:[function(_dereq_,module,exports){
var oproto = Object.prototype
var aproto = Array.prototype
var serialize = oproto.toString
var isFunction = function (fn) {
return serialize.call(fn) === '[object Function]'
}
var isArray = Array.isArray || /* istanbul ignore next */ function (ary) {
return serialize.call(ary) === '[object Array]'
}
var isPlainObject = function (obj) {
return serialize.call(obj) === '[object Object]' && Object.getPrototypeOf(obj) === oproto
}
var i = 0
var utils = {
uid: function () {
return ++i
},
noop: function () {},
bind: function (fn, context) {
return function () {
return fn.apply(context, arguments)
}
},
preventEvent: function (evt) {
evt.preventDefault()
},
stop: function (evt) {
evt.preventDefault()
evt.stopPropagation()
},
nextTick: function (fn, context) {
setTimeout(utils.bind(fn, context), 0)
},
toArray: function (ary, start, end) {
if (start === undefined) start = 0
if (end === undefined) end = ary.length
return aproto.slice.call(ary, start, end)
},
isPlainObject: isPlainObject,
isFunction: isFunction,
isArray: isArray,
isObject: function (obj) {
return Object(obj) === obj
},
isString: function (s) {
return typeof s === 'string'
},
isUndefined: function (a) {
return typeof a === 'undefined'
},
isDefined: function (a) {
return typeof a !== 'undefined'
},
each: function (ary, func, context) {
if (utils.isDefined(ary.length)) {
for (var i = 0, len = ary.length; i < len; i++) {
if (func.call(context, ary[i], i, ary) === false) {
break
}
}
} else {
for (var k in ary) {
if (func.call(context, ary[k], k, ary) === false) {
break
}
}
}
},
/**
* If option is a function, evaluate it with given params
* @param {*} data
* @param {...} args arguments of a callback
* @returns {*}
*/
evalOpts: function (data, args) {
if (utils.isFunction(data)) {
// `arguments` is an object, not array, in FF, so:
args = utils.toArray(arguments)
data = data.apply(null, args.slice(1))
}
return data
},
extend: function () {
var options
var name
var src
var copy
var copyIsArray
var clone
var target = arguments[0] || {}
var i = 1
var length = arguments.length
var force = false
// 如果第一个参数为布尔,判定是否深拷贝
if (typeof target === 'boolean') {
force = target
target = arguments[1] || {}
i++
}
// 确保接受方为一个复杂的数据类型
if (typeof target !== 'object' && !isFunction(target)) {
target = {}
}
// 如果只有一个参数,那么新成员添加于 extend 所在的对象上
if (i === length) {
target = this
i--
}
for (; i < length; i++) {
// 只处理非空参数
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name]
copy = options[name]
// 防止环引用
if (target === copy) {
continue
}
if (force && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false
clone = src && isArray(src) ? src : []
} else {
clone = src && isPlainObject(src) ? src : {}
}
target[name] = utils.extend(force, clone, copy)
} else if (copy !== undefined) {
target[name] = copy
}
}
}
}
return target
},
formatSize: function (size) {
if (size < 1024) {
return size.toFixed(0) + ' 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'
}
},
defineNonEnumerable: function (target, key, value) {
Object.defineProperty(target, key, {
enumerable: false,
configurable: true,
writable: true,
value: value
})
}
}
module.exports = utils
},{}]},{},[3])
(3)
});