blob: 5e8ed7b3b8679880f0ac1e7257b7db1993f975cf [file]
// 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 = [];
}
}