Files
MajoorWaldi-ComfyUI-Majoor-…/ui/features/viewer/metadata.ts
T

198 lines
6.4 KiB
TypeScript

import { noop, safeCall } from "../../utils/safeCall.js";
export function createViewerMetadataHydrator({
state,
VIEWER_MODES,
APP_CONFIG,
getAssetMetadata,
getAssetsBatch,
}: Record<string, any> = {}): Record<string, any> {
// Metadata hydration cache for viewer-visible assets (so rating/tags show even if the grid
// passed a lightweight asset object).
const cache = new Map(); // id -> { at:number, data:any }
const TTL_MS = APP_CONFIG?.VIEWER_META_TTL_MS ?? 30_000;
const MAX_ENTRIES = APP_CONFIG?.VIEWER_META_MAX_ENTRIES ?? 500;
// Track hydration requests to prevent race conditions
let requestId = 0;
let abortController: any = null;
const cleanupCache = () => {
if (cache.size <= MAX_ENTRIES) return;
const now = Date.now();
// Remove expired entries first.
try {
for (const [key, value] of cache.entries()) {
if (!value) continue;
if (now - (value.at || 0) > TTL_MS) {
cache.delete(key);
}
}
} catch (e: any) {
console.debug?.(e);
}
if (cache.size <= MAX_ENTRIES) return;
// Still too large: remove oldest by timestamp.
try {
const sorted = Array.from(cache.entries()).sort(
(a, b) => (a?.[1]?.at || 0) - (b?.[1]?.at || 0),
);
const excess = cache.size - MAX_ENTRIES;
for (let i = 0; i < excess; i++) {
const key = sorted[i]?.[0];
if (key != null) cache.delete(key);
}
} catch (e: any) {
console.debug?.(e);
}
};
const applyToAsset = (asset: any, meta: any) => {
if (!asset || !meta || typeof meta !== "object") return;
try {
if (meta.rating !== undefined) asset.rating = meta.rating;
} catch (e: any) {
console.debug?.(e);
}
try {
if (meta.tags !== undefined) asset.tags = meta.tags;
} catch (e: any) {
console.debug?.(e);
}
};
const hydrateAssetsMetadataBatch = async (assets: any, { signal }: Record<string, any> = {}) => {
const list = Array.isArray(assets) ? assets : [];
const now = Date.now();
const toFetch = [];
for (const asset of list) {
const id = asset?.id;
if (id == null) continue;
const key = String(id);
const cached = cache.get(key);
if (cached && now - (cached.at || 0) < TTL_MS) {
applyToAsset(asset, cached.data);
continue;
}
toFetch.push(id);
}
if (!toFetch.length) return;
try {
const res = await getAssetsBatch?.(toFetch, signal ? { signal } : {});
const items = Array.isArray(res?.data) ? res.data : [];
for (const meta of items) {
const id = meta?.id;
if (id == null) continue;
const key = String(id);
cache.set(key, { at: now, data: meta });
}
cleanupCache();
// Apply to local asset objects
for (const asset of list) {
const id = asset?.id;
if (id == null) continue;
const cached = cache.get(String(id));
if (cached && cached.data) applyToAsset(asset, cached.data);
}
} catch (e: any) {
console.debug?.(e);
}
};
const hydrateAssetMetadata = async (asset: any, { signal }: Record<string, any> = {}) => {
const id = asset?.id;
if (id == null) return;
const key = String(id);
const now = Date.now();
const cached = cache.get(key);
if (cached && now - (cached.at || 0) < TTL_MS) {
applyToAsset(asset, cached.data);
return;
}
try {
const res = await getAssetMetadata?.(id, signal ? { signal } : {});
if (res?.ok && res.data) {
cache.set(key, { at: now, data: res.data });
cleanupCache();
applyToAsset(asset, res.data);
}
} catch (e: any) {
console.debug?.(e);
}
};
const hydrateVisibleMetadata = async () => {
const current = state?.assets?.[state?.currentIndex];
const compare = state?.compareAsset;
const mode = state?.mode;
// Increment request ID to invalidate previous requests
const thisId = ++requestId;
try {
abortController?.abort?.();
} catch (e: any) {
console.debug?.(e);
}
abortController = new AbortController();
const signal = abortController.signal;
try {
if (mode === VIEWER_MODES?.SINGLE) {
if (current) await hydrateAssetsMetadataBatch([current], { signal });
if (thisId !== requestId) return;
return;
}
const visible = Array.isArray(state?.assets) ? state.assets.slice(0, 4) : [];
const batch = visible.slice();
if (compare) batch.push(compare);
await hydrateAssetsMetadataBatch(batch, { signal });
} catch (e: any) {
console.debug?.(e);
}
};
return {
hydrateVisibleMetadata,
hydrateAssetMetadata,
hydrateAssetsMetadataBatch,
getCached: (assetId: any) => {
try {
return cache.get(String(assetId));
} catch {
return null;
}
},
setCached: (assetId: any, data: any) => {
try {
cache.set(String(assetId), { at: Date.now(), data });
cleanupCache();
} catch (e: any) {
console.debug?.(e);
}
},
deleteCached: (assetId: any) => {
try {
cache.delete(String(assetId));
} catch (e: any) {
console.debug?.(e);
}
},
abort: () => {
safeCall(() => abortController?.abort?.());
abortController = null;
},
dispose: () => {
safeCall(() => abortController?.abort?.());
abortController = null;
safeCall(() => cache.clear());
},
cleanupCache: () => { safeCall(cleanupCache); return undefined; },
_noop: noop,
};
}