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