1 //TODO: 超时timeout,失败重连次数maxTries,更多的下载器Loader,队列暂停恢复等。
  2 
  3 /**
  4  * @class LoadQueue是一个队列下载工具。
  5  * @param {Object} source 要下载的资源。可以是单个资源对象或多个资源的数组。
  6  * @module hilo/loader/LoadQueue
  7  * @requires hilo/core/Class
  8  * @requires hilo/event/EventMixin
  9  * @property {Int} maxConnections 同时下载的最大连接数。默认为2。
 10  */
 11 var LoadQueue = Class.create(/** @lends LoadQueue.prototype */{
 12     Mixes: EventMixin,
 13     constructor: function LoadQueue(source){
 14         this._source = [];
 15         this.add(source);
 16     },
 17 
 18     maxConnections: 2, //TODO: 应该是每个host的最大连接数。
 19     
 20     _source: null,
 21     _loaded: 0,
 22     _connections: 0,
 23     _currentIndex: -1,
 24 
 25     /**
 26      * 增加要下载的资源。可以是单个资源对象或多个资源的数组。
 27      * @returns {LoadQueue} 下载队列实例本身。
 28      */
 29     add: function(source){
 30         var me = this;
 31         if(source){
 32             source = source instanceof Array ? source : [source];
 33             me._source = me._source.concat(source);
 34         }
 35         return me;
 36     },
 37 
 38     /**
 39      * 根据id或src地址获取资源对象。
 40      * @returns {Object} 资源对象。
 41      */
 42     get: function(id){
 43         if(id){
 44             var source = this._source;
 45             for(var i = 0; i < source.length; i++){
 46                 var item = source[i];
 47                 if(item.id === id || item.src === id){
 48                     return item;
 49                 }
 50             }
 51         }
 52         return null;
 53     },
 54 
 55     /**
 56      * 开始下载队列。
 57      * @returns {LoadQueue} 下载队列实例本身。
 58      */
 59     start: function(){
 60         var me = this;
 61         me._loadNext();
 62         return me;
 63     },
 64 
 65     /**
 66      * @private
 67      */
 68     _loadNext: function(){
 69         var me = this, source = me._source, len = source.length;
 70 
 71         //all items loaded
 72         if(me._loaded >= len){
 73             me.fire('complete');
 74             return;
 75         }
 76 
 77         if(me._currentIndex < len - 1 && me._connections < me.maxConnections){
 78             var index = ++me._currentIndex;
 79             var item = source[index];
 80             var loader = me._getLoader(item);
 81 
 82             if(loader){
 83                 var onLoad = loader.onLoad, onError = loader.onError;
 84 
 85                 loader.onLoad = function(e){
 86                     loader.onLoad = onLoad;
 87                     loader.onError = onError;
 88                     var content = onLoad && onLoad.call(loader, e) || e.target;
 89                     me._onItemLoad(index, content);
 90                 };
 91                 loader.onError = function(e){
 92                     loader.onLoad = onLoad;
 93                     loader.onError = onError;
 94                     onError && onError.call(loader, e);
 95                     me._onItemError(index, e);
 96                 };
 97                 loader.load(item);
 98                 me._connections++;
 99             }
100 
101             me._loadNext();
102         }
103     },
104 
105     /**
106      * @private
107      */
108     _getLoader: function(item){
109         var me = this, loader = item.loader;
110         if(loader) return loader;
111 
112         var type = item.type || getExtension(item.src);
113 
114         switch(type){
115             case 'png':
116             case 'jpg':
117             case 'jpeg':
118             case 'gif':
119                 loader = new ImageLoader();
120                 break;
121             case 'js':
122             case 'jsonp':
123                 loader = new ScriptLoader();
124                 break;
125         }
126 
127         return loader;
128     },
129 
130     /**
131      * @private
132      */
133     _onItemLoad: function(index, content){
134         var me = this, item = me._source[index];
135         item.loaded = true;
136         item.content = content;
137         me._connections--;
138         me._loaded++;
139         me.fire('load', item);
140         me._loadNext();
141     },
142 
143     /**
144      * @private
145      */
146     _onItemError: function(index, e){
147         var me = this, item = me._source[index];
148         item.error = e;
149         me._connections--;
150         me._loaded++;
151         me.fire('error', item);
152         me._loadNext();
153     },
154 
155     /**
156      * 获取全部或已下载的资源的字节大小。
157      * @param {Boolean} loaded 指示是已下载的资源还是全部资源。默认为全部。
158      * @returns {Number} 指定资源的字节大小。
159      */
160     getSize: function(loaded){
161         var size = 0, source = this._source;
162         for(var i = 0; i < source.length; i++){
163             var item = source[i];
164             size += (loaded ? item.loaded && item.size : item.size) || 0;
165         }
166         return size;
167     },
168 
169     /**
170      * 获取已下载的资源数量。
171      * @returns {Uint} 已下载的资源数量。
172      */
173     getLoaded: function(){
174         return this._loaded;
175     },
176 
177     /**
178      * 获取所有资源的数量。
179      * @returns {Uint} 所有资源的数量。
180      */
181     getTotal: function(){
182         return this._source.length;
183     }
184 
185 });
186 
187 /**
188  * @private
189  */
190 function getExtension(src){
191     var extRegExp = /\/?[^/]+\.(\w+)/i, match, extension;
192     if(match = src.match(extRegExp)){
193         extension = match[1].toLowerCase();
194     }
195     return extension || null;
196 }