fix(web): clear cache when asset changes (#26257)

* fix(web): clear cache when asset changes

* formatting
This commit is contained in:
Michel Heusschen
2026-02-17 11:43:08 +01:00
committed by GitHub
parent de7b42eb23
commit ceef65154d
3 changed files with 31 additions and 18 deletions

View File

@@ -1,25 +1,23 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import { getAssetInfo, getAssetOcr, type AssetOcrResponseDto, type AssetResponseDto } from '@immich/sdk'; import { getAssetInfo, getAssetOcr } from '@immich/sdk';
const defaultSerializer = <K>(params: K) => JSON.stringify(params); const defaultSerializer = <K>(params: K) => JSON.stringify(params);
class AsyncCache<V> { class AsyncCache<K, V> {
#cache = new Map<string, V>(); #cache = new Map<string, V>();
async getOrFetch<K>( constructor(private fetcher: (params: K) => Promise<V>) {}
params: K,
fetcher: (params: K) => Promise<V>, async getOrFetch(params: K, updateCache: boolean): Promise<V> {
keySerializer: (params: K) => string = defaultSerializer, const cacheKey = defaultSerializer(params);
updateCache: boolean,
): Promise<V> {
const cacheKey = keySerializer(params);
const cached = this.#cache.get(cacheKey); const cached = this.#cache.get(cacheKey);
if (cached) { if (cached) {
return cached; return cached;
} }
const value = await fetcher(params); const value = await this.fetcher(params);
if (value && updateCache) { if (value && updateCache) {
this.#cache.set(cacheKey, value); this.#cache.set(cacheKey, value);
} }
@@ -27,30 +25,43 @@ class AsyncCache<V> {
return value; return value;
} }
clearKey(params: K) {
const cacheKey = defaultSerializer(params);
this.#cache.delete(cacheKey);
}
clear() { clear() {
this.#cache.clear(); this.#cache.clear();
} }
} }
class AssetCacheManager { class AssetCacheManager {
#assetCache = new AsyncCache<AssetResponseDto>(); #assetCache = new AsyncCache(getAssetInfo);
#ocrCache = new AsyncCache<AssetOcrResponseDto[]>(); #ocrCache = new AsyncCache(getAssetOcr);
constructor() { constructor() {
eventManager.on({ eventManager.on({
AssetEditsApplied: () => { AssetEditsApplied: (assetId) => {
this.#assetCache.clear(); this.invalidateAsset(assetId);
this.#ocrCache.clear(); },
AssetUpdate: (asset) => {
this.invalidateAsset(asset.id);
}, },
}); });
} }
async getAsset(assetIdentifier: { key?: string; slug?: string; id: string }, updateCache = true) { async getAsset({ id, key, slug }: { id: string; key?: string; slug?: string }, updateCache = true) {
return this.#assetCache.getOrFetch(assetIdentifier, getAssetInfo, defaultSerializer, updateCache); return this.#assetCache.getOrFetch({ id, key, slug }, updateCache);
} }
async getAssetOcr(id: string) { async getAssetOcr(id: string) {
return this.#ocrCache.getOrFetch({ id }, getAssetOcr, (params) => params.id, true); return this.#ocrCache.getOrFetch({ id }, true);
}
invalidateAsset(id: string) {
const { key, slug } = authManager.params;
this.#assetCache.clearKey({ id, key, slug });
this.#ocrCache.clearKey({ id });
} }
clearAssetCache() { clearAssetCache() {

View File

@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
// Mock the SDK // Mock the SDK
vi.mock('@immich/sdk', () => ({ vi.mock('@immich/sdk', () => ({
getAssetInfo: vi.fn(),
getAssetOcr: vi.fn(), getAssetOcr: vi.fn(),
})); }));

View File

@@ -77,6 +77,7 @@ websocket
.on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event)) .on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event))
.on('on_session_delete', () => authManager.logout()) .on('on_session_delete', () => authManager.logout())
.on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id })) .on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id }))
.on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset))
.on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id })) .on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id }))
.on('on_notification', () => notificationManager.refresh()) .on('on_notification', () => notificationManager.refresh())
.on('connect_error', (e) => console.log('Websocket Connect Error', e)); .on('connect_error', (e) => console.log('Websocket Connect Error', e));