import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'; import { coverEnsureQueued, coverEnsureRelease } from './ensureQueue'; import { coverPeekQueued } from './peekQueue'; import { getDiskSrcForGrid, seedGridDiskSrcCache } from './diskSrcLookup'; import { forgetDiskSrcPrefix, getDiskSrcCacheGeneration, subscribeDiskSrcCache, } from './diskSrcCache'; import { subscribeCoverDiskReady } from './diskHandoff'; import { coverServerReachable } from './reachability'; import { coverStorageKeyFromRef } from './storageKeys'; import { resolveCoverDisplayTier } from './tiers'; import type { CoverArtHandle, CoverArtRef, CoverPrefetchPriority, CoverSurfaceKind, } from './types'; /** * Disk cache in Rust (WebP tiers) — no webview `getCoverArt` fetch when server is reachable. */ export function useCoverArt( coverRef: CoverArtRef | null | undefined, displayCssPx: number, opts?: { surface?: CoverSurfaceKind; fullRes?: boolean; fetchQueueBias?: number; observeRootMargin?: string; alt?: string; ensurePriority?: CoverPrefetchPriority; /** Dense grid: true after first viewport intersection — allows middle-tier scroll-ahead. */ seenViewport?: boolean; }, ): CoverArtHandle { const ref = coverRef ?? null; const surface = opts?.surface ?? 'sparse'; const reachable = ref ? coverServerReachable(ref.serverScope) : false; const tier = useMemo( () => ref ? resolveCoverDisplayTier(displayCssPx, { surface, fullRes: opts?.fullRes, }) : 128, [ref, displayCssPx, surface, opts?.fullRes], ); const storageKey = useMemo( () => (ref ? coverStorageKeyFromRef(ref, tier) : ''), [ref, tier], ); const ensurePriority: CoverPrefetchPriority = opts?.ensurePriority ?? 'middle'; const seenViewport = opts?.seenViewport ?? false; const deferEnsureUntilVisible = surface === 'dense' && !seenViewport && ensurePriority !== 'high'; const readCachedSrc = useCallback(() => { if (!ref) return ''; return getDiskSrcForGrid(ref, tier); }, [ref, tier]); useSyncExternalStore(subscribeDiskSrcCache, getDiskSrcCacheGeneration); const cachedSrc = readCachedSrc(); const applyDiskPath = useCallback((path: string) => { if (!ref) return; if (!path) { forgetDiskSrcPrefix(ref); return; } seedGridDiskSrcCache(ref, tier, path); }, [ref, tier]); useEffect(() => { if (!ref || !storageKey) return; if (readCachedSrc()) return; let cancelled = false; void (async () => { await coverPeekQueued(storageKey, ref, tier); if (cancelled) return; if (readCachedSrc()) return; if (reachable && !deferEnsureUntilVisible) { const result = await coverEnsureQueued(storageKey, ref, tier, ensurePriority); if (cancelled) return; if (result.hit && result.path) { applyDiskPath(result.path); } } })(); const unsubDisk = subscribeCoverDiskReady(storageKey, path => { if (!cancelled && path) applyDiskPath(path); }); return () => { cancelled = true; unsubDisk(); }; }, [ ref, storageKey, tier, reachable, ensurePriority, deferEnsureUntilVisible, applyDiskPath, readCachedSrc, ]); useEffect(() => { if (!storageKey) return; return () => coverEnsureRelease(storageKey); }, [storageKey]); const src = cachedSrc; const provisional = Boolean(ref && storageKey && !src); const onImgError = useCallback(() => { if (!ref) return; forgetDiskSrcPrefix(ref); if (reachable) { void coverEnsureQueued(storageKey, ref, tier, 'high').then(result => { if (result.hit && result.path) applyDiskPath(result.path); }); } }, [storageKey, ref, tier, reachable, applyDiskPath]); return { src, storageKey, cacheKey: storageKey, tier, provisional, onImgError }; }