1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6  
  7 //TODO: 超时timeout,失败重连次数maxTries,更多的下载器Loader,队列暂停恢复等。
  8 
  9 /**
 10  * @language=zh
 11  * @class LoadQueue是一个队列下载工具。
 12  * @param {Object} source 要下载的资源。可以是单个资源对象或多个资源的数组。
 13  * @module hilo/loader/LoadQueue
 14  * @requires hilo/core/Class
 15  * @requires hilo/event/EventMixin
 16  * @requires hilo/loader/ImageLoader
 17  * @requires hilo/loader/ScriptLoader
 18  * @property {Int} maxConnections 同时下载的最大连接数。默认为2。
 19  */
 20 var LoadQueue = Class.create(/** @lends LoadQueue.prototype */{
 21     Mixes: EventMixin,
 22     constructor: function(source){
 23         this._source = [];
 24         this.add(source);
 25     },
 26 
 27     maxConnections: 2, //TODO: 应该是每个host的最大连接数。
 28 
 29     _source: null,
 30     _loaded: 0,
 31     _connections: 0,
 32     _currentIndex: -1,
 33 
 34     /**
 35      * @language=zh
 36      * 增加要下载的资源。可以是单个资源对象或多个资源的数组。
 37      * @param {Object|Array} source 资源对象或资源对象数组。每个资源对象包含以下属性:
 38      * <ul>
 39      * <li><b>id</b> - 资源的唯一标识符。可用于从下载队列获取目标资源。</li>
 40      * <li><b>src</b> - 资源的地址url。</li>
 41      * <li><b>type</b> - 指定资源的类型。默认会根据资源文件的后缀来自动判断类型,不同的资源类型会使用不同的加载器来加载资源。</li>
 42      * <li><b>loader</b> - 指定资源的加载器。默认会根据资源类型来自动选择加载器,若指定loader,则会使用指定的loader来加载资源。</li>
 43      * <li><b>noCache</b> - 指示加载资源时是否增加时间标签以防止缓存。</li>
 44      * <li><b>size</b> - 资源对象的预计大小。可用于预估下载进度。</li>
 45      * </ul>
 46      * @returns {LoadQueue} 下载队列实例本身。
 47      */
 48     add: function(source){
 49         var me = this;
 50         if(source){
 51             source = source instanceof Array ? source : [source];
 52             me._source = me._source.concat(source);
 53         }
 54         return me;
 55     },
 56 
 57     /**
 58      * @language=zh
 59      * 根据id或src地址获取资源对象。
 60      * @param {String} id 指定资源的id或src。
 61      * @returns {Object} 资源对象。
 62      */
 63     get: function(id){
 64         if(id){
 65             var source = this._source;
 66             for(var i = 0; i < source.length; i++){
 67                 var item = source[i];
 68                 if(item.id === id || item.src === id){
 69                     return item;
 70                 }
 71             }
 72         }
 73         return null;
 74     },
 75 
 76     /**
 77      * @language=zh
 78      * 根据id或src地址获取资源内容。
 79      * @param {String} id 指定资源的id或src。
 80      * @returns {Object} 资源内容。
 81      */
 82     getContent: function(id){
 83         var item = this.get(id);
 84         return item && item.content;
 85     },
 86 
 87     /**
 88      * @language=zh
 89      * 开始下载队列。
 90      * @returns {LoadQueue} 下载队列实例本身。
 91      */
 92     start: function(){
 93         var me = this;
 94         me._loadNext();
 95         return me;
 96     },
 97 
 98     /**
 99      * @language=zh
100      * @private
101      */
102     _loadNext: function(){
103         var me = this, source = me._source, len = source.length;
104 
105         //all items loaded
106         if(me._loaded >= len){
107             me.fire('complete');
108             return;
109         }
110 
111         if(me._currentIndex < len - 1 && me._connections < me.maxConnections){
112             var index = ++me._currentIndex;
113             var item = source[index];
114             var loader = me._getLoader(item);
115 
116             if(loader){
117                 var onLoad = loader.onLoad, onError = loader.onError;
118 
119                 loader.onLoad = function(e){
120                     loader.onLoad = onLoad;
121                     loader.onError = onError;
122                     var content = onLoad && onLoad.call(loader, e) || e.target;
123                     me._onItemLoad(index, content);
124                 };
125                 loader.onError = function(e){
126                     loader.onLoad = onLoad;
127                     loader.onError = onError;
128                     onError && onError.call(loader, e);
129                     me._onItemError(index, e);
130                 };
131                 me._connections++;
132             }
133 
134             me._loadNext();
135             loader && loader.load(item);
136         }
137     },
138 
139     /**
140      * @language=zh
141      * @private
142      */
143     _getLoader: function(item){
144         var me = this, loader = item.loader;
145         if(loader) return loader;
146 
147         var type = item.type || getExtension(item.src);
148 
149         switch(type){
150             case 'png':
151             case 'jpg':
152             case 'jpeg':
153             case 'gif':
154                 loader = new ImageLoader();
155                 break;
156             case 'js':
157             case 'jsonp':
158                 loader = new ScriptLoader();
159                 break;
160         }
161 
162         return loader;
163     },
164 
165     /**
166      * @language=zh
167      * @private
168      */
169     _onItemLoad: function(index, content){
170         var me = this, item = me._source[index];
171         item.loaded = true;
172         item.content = content;
173         me._connections--;
174         me._loaded++;
175         me.fire('load', item);
176         me._loadNext();
177     },
178 
179     /**
180      * @language=zh
181      * @private
182      */
183     _onItemError: function(index, e){
184         var me = this, item = me._source[index];
185         item.error = e;
186         me._connections--;
187         me._loaded++;
188         me.fire('error', item);
189         me._loadNext();
190     },
191 
192     /**
193      * @language=zh
194      * 获取全部或已下载的资源的字节大小。
195      * @param {Boolean} loaded 指示是已下载的资源还是全部资源。默认为全部。
196      * @returns {Number} 指定资源的字节大小。
197      */
198     getSize: function(loaded){
199         var size = 0, source = this._source;
200         for(var i = 0; i < source.length; i++){
201             var item = source[i];
202             size += (loaded ? item.loaded && item.size : item.size) || 0;
203         }
204         return size;
205     },
206 
207     /**
208      * @language=zh
209      * 获取已下载的资源数量。
210      * @returns {Uint} 已下载的资源数量。
211      */
212     getLoaded: function(){
213         return this._loaded;
214     },
215 
216     /**
217      * @language=zh
218      * 获取所有资源的数量。
219      * @returns {Uint} 所有资源的数量。
220      */
221     getTotal: function(){
222         return this._source.length;
223     }
224 
225 });
226 
227 /**
228  * @language=zh
229  * @private
230  */
231 function getExtension(src){
232     var extRegExp = /\/?[^/]+\.(\w+)(\?\S+)?$/i, match, extension;
233     if(match = src.match(extRegExp)){
234         extension = match[1].toLowerCase();
235     }
236     return extension || null;
237 }