import { useEffect, useSyncExternalStore } from 'react'; import { coverCacheRestHost, libraryCoverBackfillConfigure, libraryCoverBackfillResetCursor, libraryCoverBackfillRunFullPass, libraryCoverBackfillSetBaseUrl, librarySqlServerId, } from '../api/coverCache'; import { coverStrategyAllowsLibraryBackfill } from '../utils/library/coverStrategy'; import { useAuthStore } from '../store/authStore'; import { useCoverStrategyStore } from '../store/coverStrategyStore'; import { subscribeLibraryCoverBackfillWake } from '../utils/library/coverBackfillWake'; import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey'; import { subscribeConnectCache } from '../utils/server/serverEndpoint'; /** * Library cover warm-up — configure session in Rust; full pass runs natively. * * - `library_cover_backfill_run_full_pass` on configure / manual wake * - `library:sync-idle` handled in Rust (not throttled with the webview) */ export function useLibraryCoverBackfill(enabled = true): void { const activeServerId = useAuthStore(s => s.activeServerId); const strategy = useCoverStrategyStore(s => s.getStrategyForServer(activeServerId), ); const server = useAuthStore(s => s.activeServerId ? s.servers.find(srv => srv.id === s.activeServerId) : undefined, ); // Runtime-probed connect URL: it flips when the sticky endpoint changes (e.g. // laptop moves off the LAN). The native worklist is URL-agnostic — we push the // live URL separately (below) rather than baking it into the session. const connectBaseUrl = useSyncExternalStore( subscribeConnectCache, () => useAuthStore.getState().getBaseUrl(), () => useAuthStore.getState().getBaseUrl(), ); useEffect(() => { const kick = () => { void libraryCoverBackfillRunFullPass(); }; const unsubWake = subscribeLibraryCoverBackfillWake(kick); return unsubWake; }, []); // Session config (server identity, credentials, strategy, enable). The connect // URL is intentionally NOT a dependency here: it changes far more often than // these, and the worklist no longer carries it — see the flip effect below. useEffect(() => { const disable = () => { void libraryCoverBackfillConfigure({ enabled: false, serverIndexKey: '', libraryServerId: '', restBaseUrl: '', username: '', password: '', }); }; if ( !enabled || !coverStrategyAllowsLibraryBackfill(strategy) || !activeServerId || !server ) { disable(); return disable; } const indexKey = serverIndexKeyForProfile(server); void (async () => { // Seed the URL with the current best guess; the flip effect keeps it fresh. const seedUrl = useAuthStore.getState().getBaseUrl(); await libraryCoverBackfillConfigure({ enabled: true, serverIndexKey: indexKey, libraryServerId: librarySqlServerId(activeServerId), restBaseUrl: seedUrl ? coverCacheRestHost(seedUrl) : '', username: server.username, password: server.password, }); await libraryCoverBackfillResetCursor(); await libraryCoverBackfillRunFullPass(); })(); return disable; }, [enabled, strategy, activeServerId, server?.url, server?.username, server?.password]); // Connect-URL flip: push the new reachable address live. The native worker // swaps a single cell, so even an in-flight pass downloads its remaining // covers against it; a real change also clears the stale fetch-failed backoff // and kicks a retry pass for whatever failed on the old address. useEffect(() => { if ( !enabled || !coverStrategyAllowsLibraryBackfill(strategy) || !activeServerId || !server || !connectBaseUrl ) { return; } void libraryCoverBackfillSetBaseUrl(coverCacheRestHost(connectBaseUrl)); }, [connectBaseUrl, enabled, strategy, activeServerId, server?.url]); }