| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {CacheObject} from './cache-object'; |
| |
| /** A localStorage-backed cached JSON object. */ |
| export class LRUStorageCacheObject { |
| capacity: number; |
| |
| cacheKey: string; |
| |
| accessKeys: any[]; |
| |
| /** |
| * Creates a new LRUStorageCacheObject instance. The items stored must be |
| * (de)serializable. |
| * |
| * This class uses n+1 storage keys. cacheKey is used to store an array of |
| * accessKeys that are used to access value objects in storage. After |
| * initializing an instance of LRUStorageCacheObject and inserting n objects |
| * storage will contain the following fields: |
| * |
| * { |
| * cacheKey: {accessKeys: [K1, ..., Kn]}, |
| * formatKey(K1): {value: V1}, |
| * ... |
| * formatKey(Kn): {value: Vn}, |
| * } |
| * |
| * TODO(gavinmak): Support batch operations for improved performance. |
| * |
| * @param capacity The max capacity of the LRUStorageCacheObject. |
| * @param cacheKey The key name for LRUStorageCacheObject in storage. |
| */ |
| constructor(capacity: number, cacheKey: string) { |
| this.capacity = capacity; |
| this.cacheKey = cacheKey; |
| this.accessKeys = this.get(this.cacheKey).accessKeys || []; |
| this.resizeCache(this.capacity); |
| } |
| |
| /** |
| * Returns a formatted key to be used in storage. |
| * |
| * @param key The accessor for a value in storage. |
| */ |
| formatKey(key: string): string { |
| return `${this.cacheKey}-${key}`; |
| } |
| |
| /** |
| * Reads an object from storage. |
| * |
| * @param key The accessor for a value in storage. |
| */ |
| private get(key: string): any { |
| return new CacheObject(key).read(); |
| } |
| |
| /** |
| * Writes an object to storage. |
| * |
| * @param key The accessor for a value in storage. |
| * @param value The value to set in storage. |
| */ |
| private set(key: string, value: any): void { |
| const cacheObj = new CacheObject(key); |
| try { |
| cacheObj.write(value); |
| } catch (e: any) { |
| if (e.name === 'QuotaExceededError') { |
| console.warn('Cache storage limit exceeded'); |
| this.resizeCache(this.accessKeys.length / 2); |
| cacheObj.write(value); |
| } else { |
| throw e; |
| } |
| } |
| } |
| |
| /** |
| * Removes the least-recently used elements until the number of entries is |
| * less than or equal to size. |
| * |
| * @param size The maximum size of the cache. |
| */ |
| resizeCache(size: number): void { |
| while (this.accessKeys.length > size) { |
| new CacheObject(this.formatKey(this.accessKeys[0])).clear(); |
| this.accessKeys.splice(0, 1); |
| } |
| } |
| |
| /** |
| * Reads the cached object. |
| * |
| * @param key The accessor for a value in the LRUStorageCacheObject. |
| */ |
| read(key: string): any { |
| const index = this.accessKeys.indexOf(key); |
| if (index < 0) { |
| return undefined; |
| } |
| |
| const {value} = this.get(this.formatKey(key)); |
| this.accessKeys.splice(index, 1); |
| if (value !== undefined) { |
| this.accessKeys.push(key); |
| } |
| |
| // Update LRU key order. |
| this.set(this.cacheKey, {accessKeys: this.accessKeys}); |
| return value; |
| } |
| |
| /** |
| * Writes value using key in the cache. |
| * |
| * @param key The key to use for LRUStorageCacheObject. |
| * @param value The value to be set. |
| */ |
| write(key: string, value: any): void { |
| this.set(this.formatKey(key), {value}); |
| const index = this.accessKeys.indexOf(key); |
| if (index >= 0) { |
| this.accessKeys.splice(index, 1); |
| } |
| this.accessKeys.push(key); |
| this.resizeCache(this.capacity); |
| |
| // Update LRU key order. |
| this.set(this.cacheKey, {accessKeys: this.accessKeys}); |
| } |
| |
| /** |
| * Deletes a key from the cache. |
| * |
| * @param key The key to delete in LRUStorageCacheObject. |
| */ |
| delete(key: string): void { |
| const index = this.accessKeys.indexOf(key); |
| if (index < 0) { |
| return; |
| } |
| new CacheObject(this.formatKey(key)).clear(); |
| this.accessKeys.splice(index, 1); |
| |
| // Update LRU key order. |
| this.set(this.cacheKey, {accessKeys: this.accessKeys}); |
| } |
| |
| /** |
| * Resets the cache and deletes all items from storage. |
| */ |
| clear(): void { |
| new CacheObject(this.cacheKey).clear(); |
| for (const accessKey of this.accessKeys) { |
| new CacheObject(this.formatKey(accessKey)).clear(); |
| } |
| this.accessKeys = []; |
| } |
| } |