OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Persistent cache storing images in an indexed database on the hard disk. | 6 * Persistent cache storing images in an indexed database on the hard disk. |
7 * @constructor | 7 * @constructor |
8 */ | 8 */ |
9 function Cache() { | 9 function ImageCache() { |
10 /** | 10 /** |
11 * IndexedDB database handle. | 11 * IndexedDB database handle. |
12 * @type {IDBDatabase} | 12 * @type {IDBDatabase} |
13 * @private | 13 * @private |
14 */ | 14 */ |
15 this.db_ = null; | 15 this.db_ = null; |
16 } | 16 } |
17 | 17 |
18 /** | 18 /** |
19 * Cache database name. | 19 * Cache database name. |
20 * @type {string} | 20 * @type {string} |
21 * @const | 21 * @const |
22 */ | 22 */ |
23 Cache.DB_NAME = 'image-loader'; | 23 ImageCache.DB_NAME = 'image-loader'; |
24 | 24 |
25 /** | 25 /** |
26 * Cache database version. | 26 * Cache database version. |
27 * @type {number} | 27 * @type {number} |
28 * @const | 28 * @const |
29 */ | 29 */ |
30 Cache.DB_VERSION = 12; | 30 ImageCache.DB_VERSION = 12; |
31 | 31 |
32 /** | 32 /** |
33 * Memory limit for images data in bytes. | 33 * Memory limit for images data in bytes. |
34 * | 34 * |
35 * @const | 35 * @const |
36 * @type {number} | 36 * @type {number} |
37 */ | 37 */ |
38 Cache.MEMORY_LIMIT = 250 * 1024 * 1024; // 250 MB. | 38 ImageCache.MEMORY_LIMIT = 250 * 1024 * 1024; // 250 MB. |
39 | 39 |
40 /** | 40 /** |
41 * Minimal amount of memory freed per eviction. Used to limit number of | 41 * Minimal amount of memory freed per eviction. Used to limit number of |
42 * evictions which are expensive. | 42 * evictions which are expensive. |
43 * | 43 * |
44 * @const | 44 * @const |
45 * @type {number} | 45 * @type {number} |
46 */ | 46 */ |
47 Cache.EVICTION_CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB. | 47 ImageCache.EVICTION_CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB. |
48 | 48 |
49 /** | 49 /** |
50 * Creates a cache key. | 50 * Creates a cache key. |
51 * | 51 * |
52 * @param {Object} request Request options. | 52 * @param {Object} request Request options. |
53 * @return {?string} Cache key. It may be null if the cache does not support | 53 * @return {?string} Cache key. It may be null if the cache does not support |
54 * |request|. e.g. Data URI. | 54 * |request|. e.g. Data URI. |
55 */ | 55 */ |
56 Cache.createKey = function(request) { | 56 ImageCache.createKey = function(request) { |
57 if (/^data:/i.test(request.url)) | 57 if (/^data:/i.test(request.url)) |
58 return null; | 58 return null; |
59 return JSON.stringify({ | 59 return JSON.stringify({ |
60 url: request.url, | 60 url: request.url, |
61 scale: request.scale, | 61 scale: request.scale, |
62 width: request.width, | 62 width: request.width, |
63 height: request.height, | 63 height: request.height, |
64 maxWidth: request.maxWidth, | 64 maxWidth: request.maxWidth, |
65 maxHeight: request.maxHeight}); | 65 maxHeight: request.maxHeight}); |
66 }; | 66 }; |
67 | 67 |
68 /** | 68 /** |
69 * Initializes the cache database. | 69 * Initializes the cache database. |
70 * @param {function()} callback Completion callback. | 70 * @param {function()} callback Completion callback. |
71 */ | 71 */ |
72 Cache.prototype.initialize = function(callback) { | 72 ImageCache.prototype.initialize = function(callback) { |
73 // Establish a connection to the database or (re)create it if not available | 73 // Establish a connection to the database or (re)create it if not available |
74 // or not up to date. After changing the database's schema, increment | 74 // or not up to date. After changing the database's schema, increment |
75 // Cache.DB_VERSION to force database recreating. | 75 // ImageCache.DB_VERSION to force database recreating. |
76 var openRequest = window.indexedDB.open(Cache.DB_NAME, Cache.DB_VERSION); | 76 var openRequest = window.indexedDB.open( |
| 77 ImageCache.DB_NAME, ImageCache.DB_VERSION); |
77 | 78 |
78 openRequest.onsuccess = function(e) { | 79 openRequest.onsuccess = function(e) { |
79 this.db_ = e.target.result; | 80 this.db_ = e.target.result; |
80 callback(); | 81 callback(); |
81 }.bind(this); | 82 }.bind(this); |
82 | 83 |
83 openRequest.onerror = callback; | 84 openRequest.onerror = callback; |
84 | 85 |
85 openRequest.onupgradeneeded = function(e) { | 86 openRequest.onupgradeneeded = function(e) { |
86 console.info('Cache database creating or upgrading.'); | 87 console.info('Cache database creating or upgrading.'); |
(...skipping 11 matching lines...) Expand all Loading... |
98 }; | 99 }; |
99 | 100 |
100 /** | 101 /** |
101 * Sets size of the cache. | 102 * Sets size of the cache. |
102 * | 103 * |
103 * @param {number} size Size in bytes. | 104 * @param {number} size Size in bytes. |
104 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not | 105 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not |
105 * provided, then a new one is created. | 106 * provided, then a new one is created. |
106 * @private | 107 * @private |
107 */ | 108 */ |
108 Cache.prototype.setCacheSize_ = function(size, opt_transaction) { | 109 ImageCache.prototype.setCacheSize_ = function(size, opt_transaction) { |
109 var transaction = opt_transaction || | 110 var transaction = opt_transaction || |
110 this.db_.transaction(['settings'], 'readwrite'); | 111 this.db_.transaction(['settings'], 'readwrite'); |
111 var settingsStore = transaction.objectStore('settings'); | 112 var settingsStore = transaction.objectStore('settings'); |
112 | 113 |
113 settingsStore.put({key: 'size', value: size}); // Update asynchronously. | 114 settingsStore.put({key: 'size', value: size}); // Update asynchronously. |
114 }; | 115 }; |
115 | 116 |
116 /** | 117 /** |
117 * Fetches current size of the cache. | 118 * Fetches current size of the cache. |
118 * | 119 * |
119 * @param {function(number)} onSuccess Callback to return the size. | 120 * @param {function(number)} onSuccess Callback to return the size. |
120 * @param {function()} onFailure Failure callback. | 121 * @param {function()} onFailure Failure callback. |
121 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not | 122 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not |
122 * provided, then a new one is created. | 123 * provided, then a new one is created. |
123 * @private | 124 * @private |
124 */ | 125 */ |
125 Cache.prototype.fetchCacheSize_ = function( | 126 ImageCache.prototype.fetchCacheSize_ = function( |
126 onSuccess, onFailure, opt_transaction) { | 127 onSuccess, onFailure, opt_transaction) { |
127 var transaction = opt_transaction || | 128 var transaction = opt_transaction || |
128 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); | 129 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); |
129 var settingsStore = transaction.objectStore('settings'); | 130 var settingsStore = transaction.objectStore('settings'); |
130 var sizeRequest = settingsStore.get('size'); | 131 var sizeRequest = settingsStore.get('size'); |
131 | 132 |
132 sizeRequest.onsuccess = function(e) { | 133 sizeRequest.onsuccess = function(e) { |
133 if (e.target.result) | 134 if (e.target.result) |
134 onSuccess(e.target.result.value); | 135 onSuccess(e.target.result.value); |
135 else | 136 else |
(...skipping 10 matching lines...) Expand all Loading... |
146 * Evicts the least used elements in cache to make space for a new image and | 147 * Evicts the least used elements in cache to make space for a new image and |
147 * updates size of the cache taking into account the upcoming item. | 148 * updates size of the cache taking into account the upcoming item. |
148 * | 149 * |
149 * @param {number} size Requested size. | 150 * @param {number} size Requested size. |
150 * @param {function()} onSuccess Success callback. | 151 * @param {function()} onSuccess Success callback. |
151 * @param {function()} onFailure Failure callback. | 152 * @param {function()} onFailure Failure callback. |
152 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not | 153 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not |
153 * provided, then a new one is created. | 154 * provided, then a new one is created. |
154 * @private | 155 * @private |
155 */ | 156 */ |
156 Cache.prototype.evictCache_ = function( | 157 ImageCache.prototype.evictCache_ = function( |
157 size, onSuccess, onFailure, opt_transaction) { | 158 size, onSuccess, onFailure, opt_transaction) { |
158 var transaction = opt_transaction || | 159 var transaction = opt_transaction || |
159 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); | 160 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); |
160 | 161 |
161 // Check if the requested size is smaller than the cache size. | 162 // Check if the requested size is smaller than the cache size. |
162 if (size > Cache.MEMORY_LIMIT) { | 163 if (size > ImageCache.MEMORY_LIMIT) { |
163 onFailure(); | 164 onFailure(); |
164 return; | 165 return; |
165 } | 166 } |
166 | 167 |
167 var onCacheSize = function(cacheSize) { | 168 var onCacheSize = function(cacheSize) { |
168 if (size < Cache.MEMORY_LIMIT - cacheSize) { | 169 if (size < ImageCache.MEMORY_LIMIT - cacheSize) { |
169 // Enough space, no need to evict. | 170 // Enough space, no need to evict. |
170 this.setCacheSize_(cacheSize + size, transaction); | 171 this.setCacheSize_(cacheSize + size, transaction); |
171 onSuccess(); | 172 onSuccess(); |
172 return; | 173 return; |
173 } | 174 } |
174 | 175 |
175 var bytesToEvict = Math.max(size, Cache.EVICTION_CHUNK_SIZE); | 176 var bytesToEvict = Math.max(size, ImageCache.EVICTION_CHUNK_SIZE); |
176 | 177 |
177 // Fetch all metadata. | 178 // Fetch all metadata. |
178 var metadataEntries = []; | 179 var metadataEntries = []; |
179 var metadataStore = transaction.objectStore('metadata'); | 180 var metadataStore = transaction.objectStore('metadata'); |
180 var dataStore = transaction.objectStore('data'); | 181 var dataStore = transaction.objectStore('data'); |
181 | 182 |
182 var onEntriesFetched = function() { | 183 var onEntriesFetched = function() { |
183 metadataEntries.sort(function(a, b) { | 184 metadataEntries.sort(function(a, b) { |
184 return b.lastLoadTimestamp - a.lastLoadTimestamp; | 185 return b.lastLoadTimestamp - a.lastLoadTimestamp; |
185 }); | 186 }); |
(...skipping 27 matching lines...) Expand all Loading... |
213 /** | 214 /** |
214 * Saves an image in the cache. | 215 * Saves an image in the cache. |
215 * | 216 * |
216 * @param {string} key Cache key. | 217 * @param {string} key Cache key. |
217 * @param {string} data Image data. | 218 * @param {string} data Image data. |
218 * @param {number} width Image width. | 219 * @param {number} width Image width. |
219 * @param {number} height Image height. | 220 * @param {number} height Image height. |
220 * @param {number} timestamp Last modification timestamp. Used to detect | 221 * @param {number} timestamp Last modification timestamp. Used to detect |
221 * if the cache entry becomes out of date. | 222 * if the cache entry becomes out of date. |
222 */ | 223 */ |
223 Cache.prototype.saveImage = function(key, data, width, height, timestamp) { | 224 ImageCache.prototype.saveImage = function(key, data, width, height, timestamp) { |
224 if (!this.db_) { | 225 if (!this.db_) { |
225 console.warn('Cache database not available.'); | 226 console.warn('Cache database not available.'); |
226 return; | 227 return; |
227 } | 228 } |
228 | 229 |
229 var onNotFoundInCache = function() { | 230 var onNotFoundInCache = function() { |
230 var metadataEntry = { | 231 var metadataEntry = { |
231 key: key, | 232 key: key, |
232 timestamp: timestamp, | 233 timestamp: timestamp, |
233 width: width, | 234 width: width, |
(...skipping 23 matching lines...) Expand all Loading... |
257 /** | 258 /** |
258 * Loads an image from the cache (if available) or returns null. | 259 * Loads an image from the cache (if available) or returns null. |
259 * | 260 * |
260 * @param {string} key Cache key. | 261 * @param {string} key Cache key. |
261 * @param {number} timestamp Last modification timestamp. If different | 262 * @param {number} timestamp Last modification timestamp. If different |
262 * that the one in cache, then the entry will be invalidated. | 263 * that the one in cache, then the entry will be invalidated. |
263 * @param {function(string, number, number)} onSuccess Success callback with | 264 * @param {function(string, number, number)} onSuccess Success callback with |
264 * the image's data, width, height. | 265 * the image's data, width, height. |
265 * @param {function()} onFailure Failure callback. | 266 * @param {function()} onFailure Failure callback. |
266 */ | 267 */ |
267 Cache.prototype.loadImage = function(key, timestamp, onSuccess, onFailure) { | 268 ImageCache.prototype.loadImage = function( |
| 269 key, timestamp, onSuccess, onFailure) { |
268 if (!this.db_) { | 270 if (!this.db_) { |
269 console.warn('Cache database not available.'); | 271 console.warn('Cache database not available.'); |
270 onFailure(); | 272 onFailure(); |
271 return; | 273 return; |
272 } | 274 } |
273 | 275 |
274 var transaction = this.db_.transaction(['settings', 'metadata', 'data'], | 276 var transaction = this.db_.transaction(['settings', 'metadata', 'data'], |
275 'readwrite'); | 277 'readwrite'); |
276 var metadataStore = transaction.objectStore('metadata'); | 278 var metadataStore = transaction.objectStore('metadata'); |
277 var dataStore = transaction.objectStore('data'); | 279 var dataStore = transaction.objectStore('data'); |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
341 | 343 |
342 /** | 344 /** |
343 * Removes the image from the cache. | 345 * Removes the image from the cache. |
344 * | 346 * |
345 * @param {string} key Cache key. | 347 * @param {string} key Cache key. |
346 * @param {function()=} opt_onSuccess Success callback. | 348 * @param {function()=} opt_onSuccess Success callback. |
347 * @param {function()=} opt_onFailure Failure callback. | 349 * @param {function()=} opt_onFailure Failure callback. |
348 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not | 350 * @param {IDBTransaction=} opt_transaction Transaction to be reused. If not |
349 * provided, then a new one is created. | 351 * provided, then a new one is created. |
350 */ | 352 */ |
351 Cache.prototype.removeImage = function( | 353 ImageCache.prototype.removeImage = function( |
352 key, opt_onSuccess, opt_onFailure, opt_transaction) { | 354 key, opt_onSuccess, opt_onFailure, opt_transaction) { |
353 if (!this.db_) { | 355 if (!this.db_) { |
354 console.warn('Cache database not available.'); | 356 console.warn('Cache database not available.'); |
355 return; | 357 return; |
356 } | 358 } |
357 | 359 |
358 var transaction = opt_transaction || | 360 var transaction = opt_transaction || |
359 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); | 361 this.db_.transaction(['settings', 'metadata', 'data'], 'readwrite'); |
360 var metadataStore = transaction.objectStore('metadata'); | 362 var metadataStore = transaction.objectStore('metadata'); |
361 var dataStore = transaction.objectStore('data'); | 363 var dataStore = transaction.objectStore('data'); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
407 metadataReceived = true; | 409 metadataReceived = true; |
408 onPartialSuccess(); | 410 onPartialSuccess(); |
409 }; | 411 }; |
410 | 412 |
411 metadataRequest.onerror = function() { | 413 metadataRequest.onerror = function() { |
412 console.error('Failed to remove an image.'); | 414 console.error('Failed to remove an image.'); |
413 metadataReceived = true; | 415 metadataReceived = true; |
414 onPartialSuccess(); | 416 onPartialSuccess(); |
415 }; | 417 }; |
416 }; | 418 }; |
OLD | NEW |