| 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 |