Skip to content

Commit 79585f6

Browse files
committed
feat(cache): adds cache eviction when nearing capacity
1 parent 2eea1ff commit 79585f6

File tree

6 files changed

+65
-18
lines changed

6 files changed

+65
-18
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"dependencies": {
5454
"@dvcol/base-http-client": "^1.1.2",
5555
"@dvcol/tmdb-http-client": "^1.1.1",
56-
"@dvcol/trakt-http-client": "^1.2.0",
56+
"@dvcol/trakt-http-client": "^1.3.0",
5757
"@dvcol/tvdb-http-client": "^1.1.1",
5858
"@dvcol/web-extension-utils": "^2.3.4",
5959
"@vue/devtools": "^7.0.15",

pnpm-lock.yaml

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/services/trakt.service.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import { useAuthSettingsStore } from '~/stores/settings/auth.store';
5252
import { logger } from '~/stores/settings/log.store';
5353
import { useUserSettingsStore } from '~/stores/settings/user.store';
5454
import { createTab } from '~/utils/browser/browser.utils';
55-
import { ChromeCacheStore } from '~/utils/cache.utils';
55+
import { CachePrefix, ChromeCacheStore } from '~/utils/cache.utils';
5656

5757
export const shouldEvict = (date?: string | number | Date, cache?: CacheResponse<unknown>): boolean => {
5858
// no cache skip
@@ -95,15 +95,15 @@ export class TraktService {
9595
static {
9696
this.caches = {
9797
trakt: new ChromeCacheStore<TraktApiResponse>({
98-
prefix: 'trakt-cache',
98+
prefix: CachePrefix.Trakt,
9999
retention: CacheRetention.Week,
100100
}),
101101
tvdb: new ChromeCacheStore<TvdbApiResponse>({
102-
prefix: 'tvdb-cache',
102+
prefix: CachePrefix.Tvdb,
103103
retention: CacheRetention.Year,
104104
}),
105105
tmdb: new ChromeCacheStore<TmdbApiResponse>({
106-
prefix: 'tmdb-cache',
106+
prefix: CachePrefix.Tmdb,
107107
retention: CacheRetention.Year,
108108
}),
109109
};

src/stores/data/image.store.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import type { TmdbConfiguration, TmdbImage } from '@dvcol/tmdb-http-client/model
66

77
import { TraktService } from '~/services/trakt.service';
88
import { logger } from '~/stores/settings/log.store';
9-
import { storage } from '~/utils/browser/browser-storage.utils';
9+
import { localCache, storage } from '~/utils/browser/browser-storage.utils';
1010
import { getShortLocale } from '~/utils/browser/browser.utils';
11+
import { CachePrefix } from '~/utils/cache.utils';
1112
import { debounce } from '~/utils/debounce.utils';
1213
import { arrayMax, findClosestMatch } from '~/utils/math.utils';
1314

@@ -84,11 +85,11 @@ export const useImageStore = defineStore(ImageStoreConstants.Store, () => {
8485
const saveState = debounce(
8586
(_images = images) =>
8687
Promise.all([
87-
storage.local.set(ImageStoreConstants.LocalMovie, _images.movie),
88-
storage.local.set(ImageStoreConstants.LocalShow, _images.show),
89-
storage.local.set(ImageStoreConstants.LocalSeason, _images.season),
90-
storage.local.set(ImageStoreConstants.LocalEpisode, _images.episode),
91-
storage.local.set(ImageStoreConstants.LocalPerson, _images.person),
88+
localCache(ImageStoreConstants.LocalMovie, _images.movie, CachePrefix.Tmdb),
89+
localCache(ImageStoreConstants.LocalShow, _images.show, CachePrefix.Tmdb),
90+
localCache(ImageStoreConstants.LocalSeason, _images.season, CachePrefix.Tmdb),
91+
localCache(ImageStoreConstants.LocalEpisode, _images.episode, CachePrefix.Tmdb),
92+
localCache(ImageStoreConstants.LocalPerson, _images.person, CachePrefix.Tmdb),
9293
]),
9394
1000,
9495
);

src/utils/browser/browser-storage.utils.ts

+37
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ const filterObject = (object: Record<string, unknown>, regex: string | RegExp) =
2121
const reverseFilterObject = (object: Record<string, unknown>, regex: string | RegExp) =>
2222
Object.fromEntries(Object.entries(object).filter(([key]) => !(typeof regex === 'string' ? new RegExp(regex) : regex).test(key)));
2323

24+
/**
25+
* This function is used to get the total size of the local storage.
26+
* @param storage The storage area to get the size of.
27+
*/
28+
const getLocalStorageSize = (storage = window.localStorage) => {
29+
return Object.entries(storage).reduce((acc, [key, value]) => acc + key.length + value.length, 0);
30+
};
31+
2432
/**
2533
* This function is used to wrap the storage areas to provide type inference and a more convenient interface.
2634
* @param area The storage area to wrap.
@@ -53,6 +61,7 @@ export const storageWrapper = (area: chrome.storage.StorageArea, name: string) =
5361

5462
window.trakt = { ...window.trakt, [name]: storage };
5563
return {
64+
getBytesInUse: async (): Promise<number> => getLocalStorageSize(window.localStorage),
5665
getAll: async <T>(regex?: string | RegExp): Promise<T> => (regex ? filterObject(storage.values, regex) : storage.values) as T,
5766
get: async <T>(key: string): Promise<T> => storage.values[key] as T,
5867
set: async <T>(key: string, value: T): Promise<void> => storage.setItem(key, value),
@@ -64,6 +73,7 @@ export const storageWrapper = (area: chrome.storage.StorageArea, name: string) =
6473
};
6574
}
6675
return {
76+
getBytesInUse: (): Promise<number> => area.getBytesInUse(),
6777
getAll: <T>(regex?: string | RegExp): Promise<T> => area.get().then(data => (regex ? filterObject(data, regex) : data) as T),
6878
get: <T>(key: string): Promise<T> => area.get(key).then(({ [key]: value }) => value),
6979
set: <T>(key: string, value: T): Promise<void> => area.set({ [key]: value }),
@@ -92,3 +102,30 @@ export const storage = {
92102
local: storageWrapper(localStorage, 'local'),
93103
session: storageWrapper(sessionStorage, 'session'),
94104
};
105+
106+
export const defaultMaxLocalStorageSize = 10485760;
107+
108+
export const localCache: <T>(key: string, value: T, regex?: string | RegExp) => Promise<void> = async (key, value, regex) => {
109+
let inUse = await storage.local.getBytesInUse();
110+
const max = globalThis?.chrome?.storage?.local.QUOTA_BYTES ?? defaultMaxLocalStorageSize;
111+
const payload = JSON.stringify(value).length;
112+
113+
if (payload > max) {
114+
console.warn('Payload is too large to store in local storage.', { payload, max, inUse });
115+
return Promise.resolve();
116+
}
117+
118+
if (inUse + payload >= max) {
119+
console.warn('Local storage is full, clearing cache.', { payload, max, inUse });
120+
if (regex) await storage.local.removeAll(regex);
121+
else await storage.local.clear();
122+
}
123+
124+
inUse = await storage.local.getBytesInUse();
125+
if (inUse + payload >= max) {
126+
console.warn('Local storage is still full, skipping cache.', { payload, max, inUse });
127+
return Promise.resolve();
128+
}
129+
130+
return storage.local.set(key, value);
131+
};

src/utils/cache.utils.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CacheRetention } from '@dvcol/base-http-client/utils/cache';
33
import type { ResponseOrTypedResponse, TypedResponse } from '@dvcol/base-http-client';
44
import type { CacheStore, CacheStoreEntity } from '@dvcol/base-http-client/utils/cache';
55

6-
import { storage, type StorageArea } from '~/utils/browser/browser-storage.utils';
6+
import { localCache, storage, type StorageArea } from '~/utils/browser/browser-storage.utils';
77

88
type FlatResponse<T extends Response = ResponseOrTypedResponse> = Record<keyof T, unknown>;
99

@@ -53,7 +53,10 @@ export class ChromeCacheStore<T> implements CacheStore<T> {
5353
}) {
5454
this.evictOnError = evictOnError;
5555
this.retention = retention;
56-
this.store = store;
56+
this.store = {
57+
...store,
58+
set: <V>(key: string, value: V) => localCache(key, value, this.prefix),
59+
};
5760
this.prefix = prefix;
5861
}
5962

@@ -83,3 +86,9 @@ export class ChromeCacheStore<T> implements CacheStore<T> {
8386
return this.store.removeAll(`^${this.prefix}:${regex}`);
8487
}
8588
}
89+
90+
export const CachePrefix = {
91+
Trakt: 'trakt-cache' as const,
92+
Tmdb: 'tmdb-cache' as const,
93+
Tvdb: 'tvdb-cache' as const,
94+
} as const;

0 commit comments

Comments
 (0)