      1 /**
      2  * Hilo
      3  * Copyright 2015 alibaba.com
      4  * Licensed under the MIT License
      5  */
      6
      7 //TODO: 超时timeout，失败重连次数maxTries，更多的下载器Loader，队列暂停恢复等。
      8
      9 /**
     10  * @class LoadQueue is a queue-like loader.
     11  * @mixes EventMixin
     12  * @borrows EventMixin#on as #on
     13  * @borrows EventMixin#off as #off
     14  * @borrows EventMixin#fire as #fire
     15  * @param {Object} source resource that need to be loaded,could be a single object or array resource.
     16  * @module hilo/loader/LoadQueue
     17  * @requires hilo/core/Class
     18  * @requires hilo/event/EventMixin
     19  * @requires hilo/loader/ImageLoader
     20  * @requires hilo/loader/ScriptLoader
     21  * @property {Int} maxConnections the limited concurrent connections. default value  2.
     22  */
     23 var LoadQueue = Class.create(/** @lends LoadQueue.prototype */{
     24     Mixes: EventMixin,
     25     constructor: function(source){
     26         this._source = [];
     27         this.add(source);
     28     },
     29
     30     maxConnections: 2, //TODO: 应该是每个host的最大连接数。
     31
     32     _source: null,
     33     _loaded: 0,
     34     _connections: 0,
     35     _currentIndex: -1,
     36
     37     /**
     38      * Add desired resource,could be a single object or array resource.
     39      * @param {Object|Array} source ,a single object or array resource. Each resource contains properties like below:
     40      * <ul>
     41      * <li><b>id</b> - resource identifier</li>
     42      * <li><b>src</b> - resource url</li>
     43      * <li><b>type</b> - resource type. By default, we automatically identify resource by file suffix and choose the relevant loader for you</li>
     44      * <li><b>loader</b> - specified resource loader. If you specify this,we abandon choosing loader inside</li>
     45      * <li><b>noCache</b> - a tag that set on or off to prevent cache,implemented by adding timestamp inside</li>
     46      * <li><b>size</b> - predicted resource size, help calculating loading progress</li>
     47      * <li><b>crossOrigin</b> - Whether cross-domain is needed, default is false</li>
     48      * </ul>
     49      * @returns {LoadQueue} 下载队列实例本身。
     50      */
     51     add: function(source){
     52         var me = this;
     53         if(source){
     54             source = source instanceof Array ? source : [source];
     55             me._source = me._source.concat(source);
     56         }
     57         return me;
     58     },
     59
     60     /**
     61      * get resource object by id or src
     62      * @param {String}  specified id or src
     63      * @returns {Object} resource object
     64      */
     65     get: function(id){
     66         if(id){
     67             var source = this._source;
     68             for(var i = 0; i < source.length; i++){
     69                 var item = source[i];
     70                 if(item.id === id || item.src === id){
     71                     return item;
     72                 }
     73             }
     74         }
     75         return null;
     76     },
     77
     78     /**
     79      * get resource object content  by id or src
     80      * @param {String} specified id or src
     81      * @returns {Object} resource object content
     82      */
     83     getContent: function(id){
     84         var item = this.get(id);
     85         return item && item.content;
     86     },
     87     /**
     88      * remove resource object content  by id or src
     89      * @param {String} specified id or src
     90      */
     91     removeContent: function(id){
     92         if(id){
     93             var source = this._source;
     94             for(var i = 0; i < source.length; i++){
     95                 var item = source[i];
     96                 if(item.id === id || item.src === id){
     97                     source.splice(i, 1);
     98                     return;
     99                 }
    100             }
    101         }
    102     },
    103
    104     /**
    105      * start loading
    106      * @returns {LoadQueue} the loading instance
    107      */
    108     start: function(){
    109         var me = this;
    110         me._loadNext();
    111         return me;
    112     },
    113
    114     /**
    115      * @private
    116      */
    117     _loadNext: function(){
    118         var me = this, source = me._source, len = source.length;
    119
    120         //all items loaded
    121         if(me._loaded >= len){
    122             me.fire('complete');
    123             return;
    124         }
    125
    126         if(me._currentIndex < len - 1 && me._connections < me.maxConnections){
    127             var index = ++me._currentIndex;
    128             var item = source[index];
    129             var loader = me._getLoader(item);
    130
    131             if(loader){
    132                 var onLoad = loader.onLoad, onError = loader.onError;
    133
    134                 loader.onLoad = function(e){
    135                     loader.onLoad = onLoad;
    136                     loader.onError = onError;
    137                     var content = onLoad && onLoad.call(loader, e) || e.target;
    138                     me._onItemLoad(index, content);
    139                 };
    140                 loader.onError = function(e){
    141                     loader.onLoad = onLoad;
    142                     loader.onError = onError;
    143                     onError && onError.call(loader, e);
    144                     me._onItemError(index, e);
    145                 };
    146                 me._connections++;
    147             }
    148
    149             me._loadNext();
    150             loader && loader.load(item);
    151         }
    152     },
    153
    154     /**
    155      * @private
    156      */
    157     _getLoader: function(item){
    158         var loader = item.loader;
    159         if(loader) return loader;
    160
    161         var type = item.type || getExtension(item.src);
    162
    163         switch(type){
    164             case 'png':
    165             case 'jpg':
    166             case 'jpeg':
    167             case 'gif':
    168             case 'webp':
    169                 loader = new ImageLoader();
    170                 break;
    171             case 'js':
    172             case 'jsonp':
    173                 loader = new ScriptLoader();
    174                 break;
    175         }
    176
    177         return loader;
    178     },
    179
    180     /**
    181      * @private
    182      */
    183     _onItemLoad: function(index, content){
    184         var me = this, item = me._source[index];
    185         item.loaded = true;
    186         item.content = content;
    187         me._connections--;
    188         me._loaded++;
    189         me.fire('load', item);
    190         me._loadNext();
    191     },
    192
    193     /**
    194      * @private
    195      */
    196     _onItemError: function(index, e){
    197         var me = this, item = me._source[index];
    198         item.error = e;
    199         me._connections--;
    200         me._loaded++;
    201         me.fire('error', item);
    202         me._loadNext();
    203     },
    204
    205     /**
    206      *  get resource size, loaded or all.
    207      * @param {Boolean} identify loaded or all resource. default is false, return all resource size. when set true, return loaded resource size.
    208      * @returns {Number} resource size.
    209      */
    210     getSize: function(loaded){
    211         var size = 0, source = this._source;
    212         for(var i = 0; i < source.length; i++){
    213             var item = source[i];
    214             size += (loaded ? item.loaded && item.size : item.size) || 0;
    215         }
    216         return size;
    217     },
    218
    219     /**
    220      * get loaded resource count
    221      * @returns {Uint} loaded resource count
    222      */
    223     getLoaded: function(){
    224         return this._loaded;
    225     },
    226
    227     /**
    228      * get all resource count
    229      * @returns {Uint} all resource count
    230      */
    231     getTotal: function(){
    232         return this._source.length;
    233     }
    234
    235 });
    236
    237 /**
    238  * @private
    239  */
    240 function getExtension(src){
    241     var extRegExp = /\/?[^/]+\.(\w+)(\?\S+)?$/i, match, extension;
    242     if(match = src.match(extRegExp)){
    243         extension = match[1].toLowerCase();
    244     }
    245     return extension || null;
    246 }