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=en 11 * @class LoadQueue is a queue-like loader. 12 * @param {Object} source ,resource that need to be loaded,could be a single object or array resource. 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 ,the limited concurrent connections. default value 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=en 36 * Add desired resource,could be a single object or array resource. 37 * @param {Object|Array} source ,a single object or array resource. Each resource contains properties like below: 38 * <ul> 39 * <li><b>id</b> - resource identifier</li> 40 * <li><b>src</b> - resource url</li> 41 * <li><b>type</b> - resource type. By default, we automatically identify resource by file suffix and choose the relevant loader for you</li> 42 * <li><b>loader</b> - specified resource loader. If you specify this,we abandon choosing loader inside</li> 43 * <li><b>noCache</b> - a tag that set on or off to prevent cache,implemented by adding timestamp inside</li> 44 * <li><b>size</b> - predicted resource size, help calculating loading progress</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=en 59 * get resource object by id or src 60 * @param {String} specified id or src 61 * @returns {Object} resource 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=en 78 * get resource object content by id or src 79 * @param {String} specified id or src 80 * @returns {Object} resource object content 81 */ 82 getContent: function(id){ 83 var item = this.get(id); 84 return item && item.content; 85 }, 86 87 /** 88 * @language=en 89 * start loading 90 * @returns {LoadQueue} the loading instance 91 */ 92 start: function(){ 93 var me = this; 94 me._loadNext(); 95 return me; 96 }, 97 98 /** 99 * @language=en 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=en 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=en 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=en 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=en 194 * get resource size, loaded or all. 195 * @param {Boolean} identify loaded or all resource. default is false, return all resource size. when set true, return loaded resource size. 196 * @returns {Number} resource size. 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=en 209 * get loaded resource count 210 * @returns {Uint} loaded resource count 211 */ 212 getLoaded: function(){ 213 return this._loaded; 214 }, 215 216 /** 217 * @language=en 218 * get all resource count 219 * @returns {Uint} all resource count 220 */ 221 getTotal: function(){ 222 return this._source.length; 223 } 224 225 }); 226 227 /** 228 * @language=en 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 }