mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(decouple): extract local-playback resolve substrate to store/
Pull findLocalPlaybackUrl/findLocalPlaybackEntry/hasLocalPersistentPlaybackBytes
+ the entry/index-key membership helpers (entryBelongsToServer,
indexKeyBelongsToServer, findFavoriteAutoEntry, hasLocal{Library,FavoriteAuto}Bytes)
out of features/offline/utils/offlineLibraryHelpers into a new core module
store/localPlaybackResolve.ts. The substrate depends only on authStore +
localPlaybackStore + serverIndexKey utils (no useOfflineStore), so the audio
core can resolve on-disk bytes without inverting into @/features/offline.
Repointed the audio-core consumers (crossfadePreload, playbackUrlRouting,
resolvePlaybackUrl, playTrackAction, promoteStreamCache, hotCachePrefetch,
hotCacheStore) + offline-internal callers + favorites hook; moved the two
barrel vi.mocks onto the new leaf module. offlineLibraryHelpers re-imports the
two primitives it still needs internally.
Decouple Step 2a. Removes the local-bytes offline edge from the audio core;
the resolve* media-resolution family (Step 2b) + orbit seam (Step 3) remain.
tsc 0, lint 0, full suite 319/2353 green.
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
entryBelongsToServer,
|
||||
hasLocalLibraryBytes,
|
||||
hasLocalFavoriteAutoBytes,
|
||||
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
} from '@/store/localPlaybackResolve';
|
||||
|
||||
const CONCURRENCY = 2;
|
||||
const DEBOUNCE_MS = 600;
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
entryBelongsToServer,
|
||||
findLocalPlaybackEntry,
|
||||
indexKeyBelongsToServer,
|
||||
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
} from '@/store/localPlaybackResolve';
|
||||
|
||||
interface LibraryTrackProbeResult {
|
||||
path: string;
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { CoverServerScope } from '@/cover/types';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '@/store/localPlaybackStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlaybackResolve';
|
||||
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||
import { resolveTrackCoverArtId, trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||
@@ -34,100 +35,6 @@ export function resolveOfflineAlbumMeta(
|
||||
return albums[`${indexKey}:${albumId}`] ?? albums[`${serverId}:${albumId}`];
|
||||
}
|
||||
|
||||
function serverIndexKeysForServerId(serverId: string): string[] {
|
||||
const servers = useAuthStore.getState().servers;
|
||||
const server = servers.find(s => s.id === serverId);
|
||||
const keys = new Set<string>();
|
||||
if (server) {
|
||||
const profileKey = serverIndexKeyForProfile(server);
|
||||
if (profileKey) keys.add(profileKey);
|
||||
keys.add(server.id);
|
||||
}
|
||||
keys.add(resolveIndexKey(serverId));
|
||||
keys.add(serverId);
|
||||
return [...keys].filter(Boolean);
|
||||
}
|
||||
|
||||
export function entryBelongsToServer(entry: LocalPlaybackEntry, serverId: string): boolean {
|
||||
return serverIndexKeysForServerId(serverId).includes(entry.serverIndexKey);
|
||||
}
|
||||
|
||||
export function indexKeyBelongsToServer(serverIndexKey: string, serverId: string): boolean {
|
||||
return serverIndexKeysForServerId(serverId).includes(serverIndexKey);
|
||||
}
|
||||
|
||||
/** Resolve a library-tier row across legacy UUID / URL index-key variants. */
|
||||
export function findLocalPlaybackEntry(
|
||||
trackId: string,
|
||||
serverId: string,
|
||||
): LocalPlaybackEntry | null {
|
||||
const lp = useLocalPlaybackStore.getState();
|
||||
for (const key of serverIndexKeysForServerId(serverId)) {
|
||||
const hit = lp.getEntry(trackId, key);
|
||||
if (hit?.tier === 'library') return hit;
|
||||
}
|
||||
for (const entry of Object.values(lp.entries)) {
|
||||
if (entry.trackId !== trackId || entry.tier !== 'library') continue;
|
||||
if (entryBelongsToServer(entry, serverId)) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Index cache; run {@link reconcileLibraryTierForAlbum} / server reconcile so rows match disk. */
|
||||
export function hasLocalLibraryBytes(trackId: string, serverId: string): boolean {
|
||||
return !!findLocalPlaybackEntry(trackId, serverId)?.localPath;
|
||||
}
|
||||
|
||||
/** Resolve a `favorite-auto` tier row across index-key variants. */
|
||||
export function findFavoriteAutoEntry(
|
||||
trackId: string,
|
||||
serverId: string,
|
||||
): LocalPlaybackEntry | null {
|
||||
const lp = useLocalPlaybackStore.getState();
|
||||
for (const key of serverIndexKeysForServerId(serverId)) {
|
||||
const hit = lp.getEntry(trackId, key);
|
||||
if (hit?.tier === 'favorite-auto') return hit;
|
||||
}
|
||||
for (const entry of Object.values(lp.entries)) {
|
||||
if (entry.trackId !== trackId || entry.tier !== 'favorite-auto') continue;
|
||||
if (entryBelongsToServer(entry, serverId)) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function hasLocalFavoriteAutoBytes(trackId: string, serverId: string): boolean {
|
||||
return !!findFavoriteAutoEntry(trackId, serverId)?.localPath;
|
||||
}
|
||||
|
||||
/** Manual offline library or favorites auto-sync — skip redundant hot-cache prefetch/promote. */
|
||||
export function hasLocalPersistentPlaybackBytes(trackId: string, serverId: string): boolean {
|
||||
return hasLocalLibraryBytes(trackId, serverId) || hasLocalFavoriteAutoBytes(trackId, serverId);
|
||||
}
|
||||
|
||||
/** Resolve `psysonic-local://` across legacy UUID / host index-key variants. */
|
||||
export function findLocalPlaybackUrl(
|
||||
trackId: string,
|
||||
serverId: string,
|
||||
tier: 'library' | 'ephemeral' | 'favorite-auto',
|
||||
): string | null {
|
||||
if (tier === 'library') {
|
||||
const entry = findLocalPlaybackEntry(trackId, serverId);
|
||||
if (entry?.localPath) return `psysonic-local://${entry.localPath}`;
|
||||
return null;
|
||||
}
|
||||
if (tier === 'favorite-auto') {
|
||||
const entry = findFavoriteAutoEntry(trackId, serverId);
|
||||
if (entry?.localPath) return `psysonic-local://${entry.localPath}`;
|
||||
return null;
|
||||
}
|
||||
const lp = useLocalPlaybackStore.getState();
|
||||
for (const key of serverIndexKeysForServerId(serverId)) {
|
||||
const url = lp.getLocalUrl(trackId, key, 'ephemeral');
|
||||
if (url) return url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Songs that still need a library-tier pin (used to skip redundant downloads). */
|
||||
export function pendingOfflinePinSongs<T extends { id: string }>(
|
||||
songs: T[],
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||
import { sortSubsonicAlbums } from '@/utils/library/albumBrowseSort';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { entryBelongsToServer } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
import { entryBelongsToServer } from '@/store/localPlaybackResolve';
|
||||
|
||||
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
|
||||
return [...songs].sort((a, b) => a.title.localeCompare(b.title));
|
||||
|
||||
@@ -8,8 +8,8 @@ import { isManualOfflinePlaylist } from '@/features/offline/utils/pinnedOfflineS
|
||||
import {
|
||||
hasLocalLibraryBytes,
|
||||
indexKeyBelongsToServer,
|
||||
resolveOfflineAlbumMeta,
|
||||
} from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
} from '@/store/localPlaybackResolve';
|
||||
import { resolveOfflineAlbumMeta } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
|
||||
function listPlaylistPinnedGroupsForServer(serverId: string): PinnedGroup[] {
|
||||
return useLocalPlaybackStore.getState()
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from '@/utils/network/activeServerReachability';
|
||||
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
import { findLocalPlaybackEntry } from '@/features/offline/utils/offlineLibraryHelpers';
|
||||
import { findLocalPlaybackEntry } from '@/store/localPlaybackResolve';
|
||||
import { enqueueOfflinePin } from '@/features/offline/utils/offlinePinQueue';
|
||||
|
||||
export type OfflinePinKind = PinSource['kind'];
|
||||
|
||||
Reference in New Issue
Block a user