import type { QueueItemRef } from '@/features/playback/store/playerStoreTypes'; import { create } from 'zustand'; import type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes'; import { useLocalPlaybackStore, type LocalPlaybackEntry } from '@/store/localPlaybackStore'; import { entryBelongsToServer } from '@/store/localPlaybackResolve'; import { invoke } from '@tauri-apps/api/core'; import { getMediaDir } from '@/utils/media/mediaDir'; export type { HotCacheEntry } from '@/features/playback/store/hotCacheStoreTypes'; /** @deprecated Use {@link LOCAL_PLAYBACK_PROTECT_AFTER_CURRENT}. */ export const HOT_CACHE_PROTECT_AFTER_CURRENT = 1; interface HotCacheState { getLocalUrl: (trackId: string, serverId: string) => string | null; setEntry: ( trackId: string, serverId: string, localPath: string, sizeBytes: number, debugSource?: string, layoutFingerprint?: string, suffix?: string, ) => void; touchPlayed: (trackId: string, serverId: string) => void; removeEntry: (trackId: string, serverId: string) => Promise; totalBytes: () => number; evictToFit: ( queue: QueueItemRef[], queueIndex: number, maxBytes: number, activeServerId: string, mediaDir: string | null, ) => Promise; clearAllDisk: (mediaDir: string | null) => Promise; } /** Ephemeral-tier view for UI selectors (Settings track count, prefetch helpers). */ export function selectHotCacheEntries( entries: Record, ): Record { const out: Record = {}; for (const [key, e] of Object.entries(entries)) { if (e.tier !== 'ephemeral') continue; out[key] = { localPath: e.localPath, sizeBytes: e.sizeBytes, cachedAt: e.cachedAt, lastPlayedAt: e.lastPlayedAt, }; } return out; } /** Ephemeral-tier row count for Settings (optional active-server scope). */ export function countHotCacheTracks( entries: Record, scopeServerId?: string, ): number { let n = 0; for (const e of Object.values(entries)) { if (e.tier !== 'ephemeral') continue; if (scopeServerId && !entryBelongsToServer(e, scopeServerId)) continue; n++; } return n; } export const useHotCacheStore = create()(() => ({ getLocalUrl: (trackId, serverId) => useLocalPlaybackStore.getState().getLocalUrl(trackId, serverId, 'ephemeral'), setEntry: (trackId, serverId, localPath, sizeBytes, _debugSource, layoutFingerprint = '', suffix = 'mp3') => { useLocalPlaybackStore.getState().upsertEntry({ serverIndexKey: serverId, trackId, localPath, sizeBytes, layoutFingerprint, tier: 'ephemeral', suffix, }); }, touchPlayed: (trackId, serverId) => { useLocalPlaybackStore.getState().touchPlayed(trackId, serverId); }, removeEntry: async (trackId, serverId) => { const lp = useLocalPlaybackStore.getState(); const e = lp.getEntry(trackId, serverId); if (e?.tier === 'ephemeral' && e.localPath) { await invoke('delete_media_file', { localPath: e.localPath, mediaDir: getMediaDir() }).catch( () => {}, ); lp.removeEntry(trackId, serverId, 'hot-cache-shim'); } }, totalBytes: () => useLocalPlaybackStore.getState().ephemeralTotalBytes(), evictToFit: async (queue, queueIndex, maxBytes, activeServerId, mediaDir) => { await useLocalPlaybackStore.getState().evictEphemeralToFit( queue, queueIndex, maxBytes, activeServerId, mediaDir, ); }, clearAllDisk: async (mediaDir) => { await useLocalPlaybackStore.getState().purgeEphemeralDisk(mediaDir); }, }));