mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
7e9cb60763
Move the Subsonic + Navidrome protocol clients and the library IPC facade (subsonic*, navidrome*, library.ts + tests) from src/api/ into src/lib/api/, the feature-free infra layer (plan M4, decision §10.3: shared client core → lib/). Pure move: deep-path import specifiers @/api/* -> @/lib/api/* across ~150 consumers; no behaviour change. Domain REST (lyrics/events) and cover/analysis infra stay in src/api/ pending their own M4 placement. tsc 0, lint 0/0, full suite 319 files / 2353 tests green.
65 lines
2.8 KiB
TypeScript
65 lines
2.8 KiB
TypeScript
import { listen } from '@tauri-apps/api/event';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
import { libraryGetTrack } from '@/lib/api/library';
|
|
import { useAuthStore } from './store/authStore';
|
|
import { useLocalPlaybackStore } from './store/localPlaybackStore';
|
|
import { layoutFingerprintFromLibraryTrack } from './utils/media/mediaLayout';
|
|
import { getMediaDir } from './utils/media/mediaDir';
|
|
import { runLegacyOfflineFileMigration } from '@/features/offline';
|
|
import { reconcileLibraryTierForServer } from '@/features/offline';
|
|
import { resolveServerIdForIndexKey } from './utils/server/serverLookup';
|
|
import { serverIndexKeyFromUrl } from './utils/server/serverIndexKey';
|
|
|
|
async function invalidateEntriesForLibraryServer(libraryServerId: string): Promise<void> {
|
|
const store = useLocalPlaybackStore.getState();
|
|
const mediaDir = getMediaDir();
|
|
const targets = Object.values(store.entries).filter(
|
|
e =>
|
|
(e.tier === 'library' || e.tier === 'favorite-auto')
|
|
&& resolveServerIdForIndexKey(e.serverIndexKey) === libraryServerId,
|
|
);
|
|
|
|
for (const entry of targets) {
|
|
const track = await libraryGetTrack(libraryServerId, entry.trackId).catch(() => null);
|
|
if (!track) {
|
|
await invoke('delete_media_file', { localPath: entry.localPath, mediaDir }).catch(() => {});
|
|
store.removeEntry(entry.trackId, entry.serverIndexKey, 'sync-track-removed');
|
|
continue;
|
|
}
|
|
if (!entry.layoutFingerprint) continue;
|
|
const nextFp = layoutFingerprintFromLibraryTrack(track, entry.suffix);
|
|
if (nextFp !== entry.layoutFingerprint) {
|
|
await invoke('delete_media_file', { localPath: entry.localPath, mediaDir }).catch(() => {});
|
|
store.removeEntry(entry.trackId, entry.serverIndexKey, 'sync-layout-changed');
|
|
}
|
|
}
|
|
}
|
|
|
|
function serverIndexKeyForLibraryId(libraryServerId: string): string | undefined {
|
|
const server = useAuthStore.getState().servers.find(s => s.id === libraryServerId);
|
|
if (!server) return undefined;
|
|
return serverIndexKeyFromUrl(server.url) || server.id;
|
|
}
|
|
|
|
/** Drop stale local files after library sync; relocate legacy offline bytes when index is ready. */
|
|
export function initLocalPlaybackInvalidation(): () => void {
|
|
let unlisten: (() => void) | null = null;
|
|
void listen<{ serverId?: string }>('library:sync-idle', ({ payload }) => {
|
|
const scopeId = payload?.serverId?.trim();
|
|
if (!scopeId) return;
|
|
void (async () => {
|
|
const profileId = resolveServerIdForIndexKey(scopeId) || scopeId;
|
|
const indexKey = serverIndexKeyForLibraryId(profileId);
|
|
await runLegacyOfflineFileMigration(indexKey);
|
|
await reconcileLibraryTierForServer(profileId);
|
|
await invalidateEntriesForLibraryServer(profileId);
|
|
})();
|
|
}).then(fn => {
|
|
unlisten = fn;
|
|
});
|
|
return () => {
|
|
unlisten?.();
|
|
unlisten = null;
|
|
};
|
|
}
|