/**
* Hilo
* Copyright 2015 alibaba.com
* Licensed under the MIT License
*/
//TODO: 超时timeout,失败重连次数maxTries,更多的下载器Loader,队列暂停恢复等。
/**
* @class LoadQueue is a queue-like loader.
* @mixes EventMixin
* @borrows EventMixin#on as #on
* @borrows EventMixin#off as #off
* @borrows EventMixin#fire as #fire
* @param {Object} source resource that need to be loaded,could be a single object or array resource.
* @module hilo/loader/LoadQueue
* @requires hilo/core/Class
* @requires hilo/event/EventMixin
* @requires hilo/loader/ImageLoader
* @requires hilo/loader/ScriptLoader
* @property {Int} maxConnections the limited concurrent connections. default value 2.
*/
var LoadQueue = Class.create(/** @lends LoadQueue.prototype */{
Mixes: EventMixin,
constructor: function(source){
this._source = [];
this.add(source);
},
maxConnections: 2, //TODO: 应该是每个host的最大连接数。
_source: null,
_loaded: 0,
_connections: 0,
_currentIndex: -1,
/**
* Add desired resource,could be a single object or array resource.
* @param {Object|Array} source ,a single object or array resource. Each resource contains properties like below:
*
* - id - resource identifier
* - src - resource url
* - type - resource type. By default, we automatically identify resource by file suffix and choose the relevant loader for you
* - loader - specified resource loader. If you specify this,we abandon choosing loader inside
* - noCache - a tag that set on or off to prevent cache,implemented by adding timestamp inside
* - size - predicted resource size, help calculating loading progress
* - crossOrigin - Whether cross-domain is needed, default is false
*
* @returns {LoadQueue} 下载队列实例本身。
*/
add: function(source){
var me = this;
if(source){
source = source instanceof Array ? source : [source];
me._source = me._source.concat(source);
}
return me;
},
/**
* get resource object by id or src
* @param {String} specified id or src
* @returns {Object} resource object
*/
get: function(id){
if(id){
var source = this._source;
for(var i = 0; i < source.length; i++){
var item = source[i];
if(item.id === id || item.src === id){
return item;
}
}
}
return null;
},
/**
* get resource object content by id or src
* @param {String} specified id or src
* @returns {Object} resource object content
*/
getContent: function(id){
var item = this.get(id);
return item && item.content;
},
/**
* remove resource object content by id or src
* @param {String} specified id or src
*/
removeContent: function(id){
if(id){
var source = this._source;
for(var i = 0; i < source.length; i++){
var item = source[i];
if(item.id === id || item.src === id){
source.splice(i, 1);
return;
}
}
}
},
/**
* start loading
* @returns {LoadQueue} the loading instance
*/
start: function(){
var me = this;
me._loadNext();
return me;
},
/**
* @private
*/
_loadNext: function(){
var me = this, source = me._source, len = source.length;
//all items loaded
if(me._loaded >= len){
me.fire('complete');
return;
}
if(me._currentIndex < len - 1 && me._connections < me.maxConnections){
var index = ++me._currentIndex;
var item = source[index];
var loader = me._getLoader(item);
if(loader){
var onLoad = loader.onLoad, onError = loader.onError;
loader.onLoad = function(e){
loader.onLoad = onLoad;
loader.onError = onError;
var content = onLoad && onLoad.call(loader, e) || e.target;
me._onItemLoad(index, content);
};
loader.onError = function(e){
loader.onLoad = onLoad;
loader.onError = onError;
onError && onError.call(loader, e);
me._onItemError(index, e);
};
me._connections++;
}
me._loadNext();
loader && loader.load(item);
}
},
/**
* @private
*/
_getLoader: function(item){
var loader = item.loader;
if(loader) return loader;
var type = item.type || getExtension(item.src);
switch(type){
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
case 'webp':
loader = new ImageLoader();
break;
case 'js':
case 'jsonp':
loader = new ScriptLoader();
break;
}
return loader;
},
/**
* @private
*/
_onItemLoad: function(index, content){
var me = this, item = me._source[index];
item.loaded = true;
item.content = content;
me._connections--;
me._loaded++;
me.fire('load', item);
me._loadNext();
},
/**
* @private
*/
_onItemError: function(index, e){
var me = this, item = me._source[index];
item.error = e;
me._connections--;
me._loaded++;
me.fire('error', item);
me._loadNext();
},
/**
* get resource size, loaded or all.
* @param {Boolean} identify loaded or all resource. default is false, return all resource size. when set true, return loaded resource size.
* @returns {Number} resource size.
*/
getSize: function(loaded){
var size = 0, source = this._source;
for(var i = 0; i < source.length; i++){
var item = source[i];
size += (loaded ? item.loaded && item.size : item.size) || 0;
}
return size;
},
/**
* get loaded resource count
* @returns {Uint} loaded resource count
*/
getLoaded: function(){
return this._loaded;
},
/**
* get all resource count
* @returns {Uint} all resource count
*/
getTotal: function(){
return this._source.length;
}
});
/**
* @private
*/
function getExtension(src){
var extRegExp = /\/?[^/]+\.(\w+)(\?\S+)?$/i, match, extension;
if(match = src.match(extRegExp)){
extension = match[1].toLowerCase();
}
return extension || null;
}