OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
James Hawkins
2013/02/20 04:31:06
2013
mtomasz
2013/02/21 05:19:24
Done.
| |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 var ImageLoader = ImageLoader || {}; | |
6 | |
7 /** | |
8 * Image loader's extension id. | |
9 * @const | |
10 * @type {string} | |
11 */ | |
12 ImageLoader.EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp'; | |
13 | |
14 /** | |
15 * Client used to connect to the remote ImageLoader class working in a separate | |
James Hawkins
2013/02/20 04:31:06
What is the 'separate extension'?
mtomasz
2013/02/21 05:19:24
Clarified the comment. Client runs in Files.app ex
| |
16 * extension. This class runs in the extension, when the client.js is included. | |
17 * It sends remote requests using IPC to the ImageLoader class and forwards | |
18 * its responses. | |
19 * | |
20 * Implements cache, which is stored in the calling extension. | |
21 * | |
22 * @constructor | |
23 */ | |
24 ImageLoader.Client = function() { | |
25 this.port_ = chrome.extension.connect(ImageLoader.EXTENSION_ID); | |
26 this.port_.onMessage.addListener(this.handleMessage_.bind(this)); | |
27 | |
28 this.tasks_ = {}; | |
James Hawkins
2013/02/20 04:31:06
nit: Document member variables.
mtomasz
2013/02/21 05:19:24
Done.
| |
29 this.lastTaskId_ = 0; | |
30 this.cache_ = new ImageLoader.Client.Cache(); | |
31 }; | |
32 | |
33 /** | |
34 * Returns a singleton instance. Static. | |
James Hawkins
2013/02/20 04:31:06
nit: No need to specify 'static'. Here and elsewh
mtomasz
2013/02/21 05:19:24
I wanted to use @static, however it is not accepte
| |
35 * @return {ImageLoader.Client} ImageLoader.Client instance. | |
36 */ | |
37 ImageLoader.Client.getInstance = function() { | |
38 if (!ImageLoader.Client.instance_) | |
39 ImageLoader.Client.instance_ = new ImageLoader.Client(); | |
40 return ImageLoader.Client.instance_; | |
41 }; | |
42 | |
43 /** | |
44 * Loads and resizes and image. Use opt_isValid to easily cancel requests | |
James Hawkins
2013/02/20 04:31:06
s/and/an/?
mtomasz
2013/02/21 05:19:24
Done.
| |
45 * which are not valid anymore, which will reduce cpu consumption. Static. | |
46 * | |
47 * @param {string} url Url of the requested image. | |
48 * @param {function} callback Callback used to return response. | |
49 * @param {Object=} opt_options Loader options, such as: scale, maxHeight, | |
50 * width, height and/or cache. | |
51 * @param {function=} opt_isValid Function returning false in case | |
52 * a request is not valid anymore, eg. parent node has been detached. | |
53 * @return {?number} Remote task id or null if loaded from cache. | |
54 */ | |
55 ImageLoader.Client.load = function(url, callback, opt_options, opt_isValid) { | |
James Hawkins
2013/02/20 04:31:06
Please remove all of these 'singleton' methods and
mtomasz
2013/02/21 05:19:24
Done.
| |
56 return ImageLoader.Client.getInstance().load( | |
57 url, callback, opt_options, opt_isValid); | |
58 }; | |
59 | |
60 /** | |
61 * Cancels the request. Static. | |
62 * @param {number} taskId Task id returned by ImageLoader.Client.load(). | |
63 */ | |
64 ImageLoader.Client.cancel = function(taskId) { | |
65 ImageLoader.Client.getInstance().cancel(taskId); | |
66 }; | |
67 | |
68 /** | |
69 * Prints the cache usage statistics. Static. | |
70 */ | |
71 ImageLoader.Client.stat = function() { | |
72 ImageLoader.Client.getInstance().stat(); | |
73 }; | |
74 | |
75 /** | |
76 * Handles a message from the remote image loader and calls the registered | |
77 * callback to pass the response back to the requester. | |
78 * | |
79 * @param {Object} message Response message as a hash array. | |
80 * @private | |
81 */ | |
82 ImageLoader.Client.prototype.handleMessage_ = function(message) { | |
83 if (!(message.taskId in this.tasks_)) { | |
84 // This task has been canceled, but was already fetched, so it's result | |
85 // should be discarded anyway. | |
86 return; | |
87 } | |
88 | |
89 var task = this.tasks_[message.taskId]; | |
90 | |
91 // Check if the task is still valid. | |
92 if (task.isValid()) | |
93 task.accept(message); | |
94 | |
95 delete this.tasks_[message.taskId]; | |
96 }; | |
97 | |
98 /** | |
99 * Loads and resizes and image. Use opt_isValid to easily cancel requests | |
100 * which are not valid anymore, which will reduce cpu consumption. | |
101 * | |
102 * @param {string} url Url of the requested image. | |
103 * @param {function} callback Callback used to return response. | |
104 * @param {Object=} opt_options Loader options, such as: scale, maxHeight, | |
105 * width, height and/or cache. | |
106 * @param {function=} opt_isValid Function returning false in case | |
107 * a request is not valid anymore, eg. parent node has been detached. | |
108 * @return {?number} Remote task id or null if loaded from cache. | |
109 */ | |
110 ImageLoader.Client.prototype.load = function( | |
111 url, callback, opt_options, opt_isValid) { | |
112 opt_options = opt_options || {}; | |
113 opt_isValid = opt_isValid || function() { return true; }; | |
114 | |
115 // Cancel old, invalid tasks. | |
116 var taskKeys = Object.keys(this.tasks_); | |
117 for (var index = 0; index < taskKeys.length; index++) { | |
118 var taskKey = taskKeys[index]; | |
119 var task = this.tasks_[taskKey]; | |
120 if (!task.isValid()) { | |
121 // Cancel this task since it is not valid anymore. | |
122 this.cancel(taskKey); | |
123 delete this.tasks_[taskKey]; | |
124 } | |
125 } | |
126 | |
127 // Replace exchange id. | |
James Hawkins
2013/02/20 04:31:06
What does 'exchange id' mean?
James Hawkins
2013/02/20 04:31:06
nit: s/id/ID/
mtomasz
2013/02/21 05:19:24
Should be extension id. Fixed.
mtomasz
2013/02/21 05:19:24
Done.
mtomasz
2013/02/21 05:19:24
git grep 'extension id' | wc -l
152
git grep 'ext
James Hawkins
2013/02/21 23:20:36
I'm not going to block the CL on this, but for ref
| |
128 var sourceId = chrome.i18n.getMessage('@@extension_id'); | |
129 var targetId = ImageLoader.EXTENSION_ID; | |
130 | |
131 url = url.replace('filesystem:chrome-extension://' + sourceId, | |
132 'filesystem:chrome-extension://' + targetId); | |
133 | |
134 // Try to load from cache, if available. | |
135 var cacheKey = ImageLoader.Client.Cache.createKey(url, opt_options); | |
136 if (opt_options.cache) { | |
137 // Load from cache. | |
138 // TODO(mtomasz): Add cache invalidating if the file has changed. | |
139 var cachedData = this.cache_.loadImage(cacheKey); | |
140 if (cachedData) { | |
141 callback({ status: 'success', data: cachedData }); | |
142 return null; | |
143 } | |
144 } else { | |
145 // Remove from cache. | |
146 this.cache_.removeImage(cacheKey); | |
147 } | |
148 | |
149 // Not available in cache, performing a request to a remote extension. | |
150 request = opt_options; | |
151 this.lastTaskId_++; | |
152 var task = { isValid: opt_isValid, accept: function(result) { | |
153 // Save to cache. | |
154 if (result.status == 'success' && opt_options.cache) | |
155 this.cache_.saveImage(cacheKey, result.data); | |
156 callback(result); | |
157 }.bind(this) }; | |
158 this.tasks_[this.lastTaskId_] = task; | |
159 | |
160 request.url = url; | |
161 request.taskId = this.lastTaskId_; | |
162 | |
163 this.port_.postMessage(request); | |
164 return request.taskId; | |
165 }; | |
166 | |
167 /** | |
168 * Cancels the request. | |
169 * @param {number} taskId Task id returned by ImageLoader.Client.load(). | |
170 */ | |
171 ImageLoader.Client.prototype.cancel = function(taskId) { | |
172 this.port_.postMessage({ taskId: taskId, cancel: true }); | |
173 }; | |
174 | |
175 /** | |
176 * Prints the cache usage statistics. | |
177 */ | |
178 ImageLoader.Client.prototype.stat = function() { | |
179 this.cache_.stat(); | |
180 }; | |
181 | |
182 /** | |
183 * Least Recently Used (LRU) cache implementation to be used by | |
184 * ImageLoader.Client class. It has memory constraints, so it will never | |
185 * exceed specified memory limit defined in MEMORY_LIMIT. | |
186 * | |
187 * @constructor | |
188 */ | |
189 ImageLoader.Client.Cache = function() { | |
190 this.images_ = []; | |
191 this.size_ = 0; | |
192 }; | |
193 | |
194 /** | |
195 * Memory limit for images data in bytes. | |
196 * | |
197 * @const | |
198 * @type {number} | |
199 */ | |
200 ImageLoader.Client.Cache.MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MB. | |
201 | |
202 /** | |
203 * Creates a cache key. Static. | |
204 * | |
205 * @param {string} url Image url. | |
206 * @param {Object=} opt_options Loader options as a hash array. | |
207 * @return {string} Cache key. | |
208 */ | |
209 ImageLoader.Client.Cache.createKey = function(url, opt_options) { | |
210 var array = opt_options || {}; | |
211 array.url = url; | |
212 return JSON.stringify(array); | |
213 }; | |
214 | |
215 /** | |
216 * Evicts the least used elements in cache to make space for a new image. | |
217 * | |
218 * @param {number} size Requested size. | |
219 * @private | |
220 */ | |
221 ImageLoader.Client.Cache.prototype.evictCache_ = function(size) { | |
222 // Sort from the most recent to the oldest. | |
223 this.images_.sort(function(a, b) { | |
224 return b.timestamp - a.timestamp; | |
225 }); | |
226 | |
227 while (this.images_.length > 0 && | |
228 (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ < size)) { | |
229 var entry = this.images_.pop(); | |
230 this.size_ -= entry.data.length; | |
231 } | |
232 }; | |
233 | |
234 /** | |
235 * Saves an image in the cache. | |
236 * | |
237 * @param {string} key Cache key. | |
238 * @param {string} data Image data. | |
239 */ | |
240 ImageLoader.Client.Cache.prototype.saveImage = function(key, data) { | |
241 this.evictCache_(data.length); | |
242 if (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ >= data.length) { | |
243 this.images_[key] = { timestamp: Date.now(), data: data }; | |
244 this.size_ += data.length; | |
245 } | |
246 }; | |
247 | |
248 /** | |
249 * Loads an image from the cache (if available) or returns null. | |
250 * | |
251 * @param {string} key Cache key. | |
252 * @return {?string} Data of the loaded image or null. | |
253 */ | |
254 ImageLoader.Client.Cache.prototype.loadImage = function(key) { | |
255 if (!(key in this.images_)) | |
256 return null; | |
257 | |
258 var entry = this.images_[key]; | |
259 entry.timestamp = Date.now(); | |
260 return entry.data; | |
261 }; | |
262 | |
263 /** | |
264 * Prints the cache usage stats. | |
265 */ | |
266 ImageLoader.Client.Cache.prototype.stat = function() { | |
267 console.log('Cache entries: ' + Object.keys(this.images_).length); | |
268 console.log('Usage: ' + Math.round(this.size_ / | |
269 ImageLoader.Client.Cache.MEMORY_LIMIT * 100.0) + '%'); | |
270 }; | |
271 | |
272 /** | |
273 * Removes the image from the cache. | |
274 * @param {string} key Cache key. | |
275 */ | |
276 ImageLoader.Client.Cache.prototype.removeImage = function(key) { | |
277 if (!(key in this.images_)) | |
278 return; | |
279 | |
280 var entry = this.images_[key]; | |
281 this.size_ -= entry.data.length; | |
282 delete this.images_[key]; | |
283 }; | |
284 | |
285 // Helper functions. | |
286 | |
287 /** | |
288 * Loads and resizes and image. Use opt_isValid to easily cancel requests | |
289 * which are not valid anymore, which will reduce cpu consumption. | |
290 * | |
291 * @param {string} url Url of the requested image. | |
292 * @param {Image} image Image node to load the requested picture into. | |
293 * @param {Object} options Loader options, such as: scale, maxHeight, width, | |
294 * height and/or cache. | |
295 * @param {function=} onSuccess Callback for success. | |
296 * @param {function=} onError Callback for failure. | |
297 * @param {function=} opt_isValid Function returning false in case | |
298 * a request is not valid anymore, eg. parent node has been detached. | |
299 * @return {?number} Remote task id or null if loaded from cache. | |
300 */ | |
301 ImageLoader.Client.loadToImage = function(url, image, options, onSuccess, | |
302 onError, opt_isValid) { | |
303 var callback = function(result) { | |
304 if (result.status == 'error') { | |
305 onError(); | |
306 return; | |
307 } | |
308 image.src = result.data; | |
309 onSuccess(); | |
310 }; | |
311 | |
312 return ImageLoader.Client.load(url, callback, options, opt_isValid); | |
313 }; | |
OLD | NEW |