mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
refactor(lib): relocate the local-library subsystem utils/library -> lib/library
utils/library is the local-index query/browse/sync engine -- feature-free shared infra consumed by the album, search, genre, artist and offline features. With its last feature edges removed (queue resolver, shuffleArray, albumBrowseCatalogChunk, genreBrowsePlayback all relocated earlier this branch), the whole directory is pure infra and belongs under lib/. Whole-directory move (58 files, 76 consumers rewritten) via resolver-based rewrite: cross-cluster relatives absolutized to @/, intra-cluster siblings kept relative. utils/library is gone; lib/library imports zero @/features. Three stale doc comments in the album/search/artist barrels repointed. tsc 0, lint 0, full suite 2353/2353, production build OK.
This commit is contained in:
@@ -20,8 +20,8 @@ import { sanitizeHtml } from '@/lib/util/sanitizeHtml';
|
||||
import { OpenArtistRefInline } from '@/features/artist';
|
||||
import { tooltipAttrs } from '@/ui/tooltipAttrs';
|
||||
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
|
||||
import { deriveAlbumGenreTags } from '@/utils/library/genreTags';
|
||||
import { genreColor } from '@/utils/library/genreColor';
|
||||
import { deriveAlbumGenreTags } from '@/lib/library/genreTags';
|
||||
import { genreColor } from '@/lib/library/genreColor';
|
||||
|
||||
/** True when the album artist label means "no single artist" — `getArtistInfo`
|
||||
* has nothing meaningful to return for these, so the Artist Bio entry is hidden.
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ndListLosslessAlbumsPage } from '@/lib/api/navidromeBrowse';
|
||||
import AlbumRow from '@/features/album/components/AlbumRow';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { runLocalLosslessAlbums } from '@/utils/library/browseTextSearch';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { runLocalLosslessAlbums } from '@/lib/library/browseTextSearch';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
|
||||
interface Props {
|
||||
disableArtwork?: boolean;
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from '@/cover/coverTraffic';
|
||||
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation';
|
||||
import type { AlbumCompFilter } from '@/utils/library/albumCompilation';
|
||||
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/lib/library/albumCompilation';
|
||||
import type { AlbumCompFilter } from '@/lib/library/albumCompilation';
|
||||
import {
|
||||
albumBrowseHasGenreFilter,
|
||||
albumBrowseHasServerFilters,
|
||||
@@ -24,12 +24,12 @@ import {
|
||||
fetchLocalAlbumCatalogChunk,
|
||||
type AlbumBrowseQuery,
|
||||
type GenreFilterOption,
|
||||
} from '@/utils/library/albumBrowseLoad';
|
||||
} from '@/lib/library/albumBrowseLoad';
|
||||
import { libraryScopeForServer } from '@/lib/api/subsonicClient';
|
||||
import {
|
||||
ALBUM_YEAR_FILTER_DEBOUNCE_MS,
|
||||
resolveAlbumYearBounds,
|
||||
} from '@/utils/library/albumYearFilter';
|
||||
} from '@/lib/library/albumYearFilter';
|
||||
import { loadOfflineAlbumBrowseInitial } from '@/features/offline';
|
||||
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||
import {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
isAlbumDetailPath,
|
||||
useAlbumBrowseSessionStore,
|
||||
} from '@/features/album/store/albumBrowseSessionStore';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
|
||||
import type { AlbumBrowseSort } from '@/lib/library/browseTextSearch';
|
||||
import { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
ALBUM_YEAR_MAX,
|
||||
ALBUM_YEAR_MIN,
|
||||
type AlbumCatalogYearRange,
|
||||
} from '@/utils/library/albumYearFilter';
|
||||
import { fetchAlbumCatalogYearBounds } from '@/utils/library/albumCatalogYearBounds';
|
||||
} from '@/lib/library/albumYearFilter';
|
||||
import { fetchAlbumCatalogYearBounds } from '@/lib/library/albumCatalogYearBounds';
|
||||
|
||||
const DEFAULT: AlbumCatalogYearRange = { min: ALBUM_YEAR_MIN, max: ALBUM_YEAR_MAX };
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
offlineLocalBrowseEnabled,
|
||||
} from '@/features/offline';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
import {
|
||||
shouldAttemptSubsonicForActiveServer,
|
||||
shouldAttemptSubsonicForServer,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
raceBrowseWithLocalFallback,
|
||||
runLocalBrowseAlbums,
|
||||
runNetworkBrowseAlbums,
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
} from '@/lib/library/browseTextSearch';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline';
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
|
||||
import {
|
||||
fetchGenreAlbumPage,
|
||||
GENRE_ALBUM_CATALOG_CHUNK,
|
||||
GENRE_ALBUM_FIRST_PAGE,
|
||||
} from '@/utils/library/genreAlbumBrowse';
|
||||
} from '@/lib/library/genreAlbumBrowse';
|
||||
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
|
||||
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* the album session store, per-album offline status, and album export helpers.
|
||||
*
|
||||
* Stays OUT (library-core / cross-cutting, consumed by this feature, not owned):
|
||||
* the `utils/library/album*` browse query engine (shared with offline + other
|
||||
* the `lib/library/album*` browse query engine (shared with offline + other
|
||||
* browse surfaces → M4), `albumDetailNavigation` (cross-cutting nav → M4),
|
||||
* `playAlbum` (playback action → playback), `starredAlbumIndexSync` (index
|
||||
* sync, core), the album context-menu items (context-menu subsystem), the
|
||||
|
||||
@@ -43,8 +43,8 @@ import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
import { albumGridWarmCovers } from '@/cover/layoutSizes';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import LosslessModeBanner from '@/components/LosslessModeBanner';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { isLosslessMode } from '@/utils/library/losslessMode';
|
||||
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
|
||||
import { isLosslessMode } from '@/lib/library/losslessMode';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { offlineActionPolicy } from '@/features/offline';
|
||||
|
||||
@@ -43,15 +43,15 @@ import { peekAlbumBrowseScrollRestore } from '@/features/album/store/albumBrowse
|
||||
import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation';
|
||||
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
|
||||
import { useAlbumCatalogYearBounds } from '@/features/album/hooks/useAlbumCatalogYearBounds';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { resolveAlbumYearBounds } from '@/utils/library/albumYearFilter';
|
||||
import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
import { resolveAlbumYearBounds } from '@/lib/library/albumYearFilter';
|
||||
import {
|
||||
filterAlbumsByCompilation,
|
||||
filterAlbumsByGenres,
|
||||
filterAlbumsByStarred,
|
||||
filterAlbumsByYearBounds,
|
||||
} from '@/utils/library/albumBrowseFilters';
|
||||
} from '@/lib/library/albumBrowseFilters';
|
||||
import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore';
|
||||
|
||||
type SortType = AlbumBrowseSort;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AlbumCard from '@/features/album/components/AlbumCard';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
import { ndListLosslessAlbumsPage } from '@/lib/api/navidromeBrowse';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
runLocalAlbumBrowsePage,
|
||||
sortSubsonicAlbums,
|
||||
type AlbumBrowseSort,
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
} from '@/lib/library/browseTextSearch';
|
||||
|
||||
/** Local index page size — SQLite is cheap; larger pages than the network walk. */
|
||||
const LOCAL_PAGE_SIZE = 30;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter';
|
||||
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '@/utils/library/browseTextSearch';
|
||||
import { runLocalRandomAlbums, runLocalAlbumsByGenres } from '@/lib/library/browseTextSearch';
|
||||
import { useOfflineStore } from '@/features/offline';
|
||||
import { useDownloadModalStore } from '@/features/offline';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand';
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import type { AlbumBrowseSort } from '@/utils/library/browseTextSearch';
|
||||
import type { AlbumBrowseSort } from '@/lib/library/browseTextSearch';
|
||||
|
||||
export const DEFAULT_ALBUM_BROWSE_SORT: AlbumBrowseSort = 'alphabeticalByName';
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { isOfflineBrowseActive } from '@/features/offline';
|
||||
import { loadOfflineAlbumCatalogChunk } from '@/features/offline';
|
||||
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||
import { fetchLocalAlbumCatalogChunk } from '@/utils/library/albumBrowseLoad';
|
||||
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
|
||||
import { fetchLocalAlbumCatalogChunk } from '@/lib/library/albumBrowseLoad';
|
||||
|
||||
export type AlbumCatalogChunk = {
|
||||
albums: SubsonicAlbum[];
|
||||
|
||||
@@ -11,8 +11,8 @@ import { loadArtistFromLibraryIndex } from '@/features/offline';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
import { runLocalArtistLosslessBrowse } from '@/utils/library/browseTextSearch';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { runLocalArtistLosslessBrowse } from '@/lib/library/browseTextSearch';
|
||||
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
|
||||
|
||||
export interface UseArtistDetailDataOptions {
|
||||
/** When true, albums and top tracks are limited to lossless containers (local index preferred). */
|
||||
|
||||
@@ -5,7 +5,7 @@ import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import {
|
||||
fetchLocalArtistCatalogChunk,
|
||||
fetchNetworkStarredArtists,
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
} from '@/lib/library/browseTextSearch';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { useOfflineBrowseReloadToken } from '@/features/offline';
|
||||
import {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
runLocalBrowseArtists,
|
||||
runNetworkBrowseArtists,
|
||||
type LibrarySearchSurface,
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
} from '@/lib/library/browseTextSearch';
|
||||
import { useOfflineBrowseContext } from '@/features/offline';
|
||||
import { offlineLocalBrowseEnabled, searchOfflineLocalArtists } from '@/features/offline';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* `pages/*` paths, not re-exported), artist cards/rows/avatars, the detail hero +
|
||||
* similar-artists + top-tracks UI, the artist Subsonic API, browse catalog/
|
||||
* filter/scroll hooks, per-artist offline status, and the open-artist-ref nav
|
||||
* cluster. `sortArtistAlbums` moved in from `utils/library/` (artist-only sort,
|
||||
* cluster. `sortArtistAlbums` moved in from `lib/library/` (artist-only sort,
|
||||
* not the shared index query engine).
|
||||
*
|
||||
* Stays OUT (other owners): `deriveAlbumHeaderArtistRefs` (album header),
|
||||
|
||||
@@ -36,7 +36,7 @@ import { artistDetailCoverWarmAlbums } from '@/features/artist/components/topSon
|
||||
import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch';
|
||||
import { useWarmGridCovers } from '@/hooks/useWarmGridCovers';
|
||||
import { VirtualCardGrid } from '@/components/VirtualCardGrid';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
import { sortArtistAlbumsByYear } from '@/features/artist/utils/sortArtistAlbums';
|
||||
import { readDetailServerId } from '@/utils/navigation/detailServerScope';
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary';
|
||||
import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
|
||||
const TOP_SONGS_LIMIT = 5;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
|
||||
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
|
||||
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||
import {
|
||||
fetchOfflineLocalAlbumCatalogChunk,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '@/store/localPl
|
||||
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 { resolveTrackCoverArtId, trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
|
||||
import type { Track } from '@/features/playback/store/playerStoreTypes';
|
||||
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
albumToAlbum,
|
||||
artistToArtist,
|
||||
trackToSong,
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
} from '@/lib/library/advancedSearchLocal';
|
||||
|
||||
export async function loadAlbumFromLibraryIndex(
|
||||
serverId: string,
|
||||
|
||||
@@ -9,17 +9,17 @@ import {
|
||||
artistToArtist,
|
||||
resolveTrackCoverArtId,
|
||||
trackToSong,
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
import { albumIsCompilationFromTrackDtos } from '@/utils/library/albumCompilation';
|
||||
} from '@/lib/library/advancedSearchLocal';
|
||||
import { albumIsCompilationFromTrackDtos } from '@/lib/library/albumCompilation';
|
||||
import {
|
||||
filterAlbumsByCompilation,
|
||||
filterAlbumsByGenres,
|
||||
filterAlbumsByStarred,
|
||||
filterAlbumsByYearBounds,
|
||||
} from '@/utils/library/albumBrowseFilters';
|
||||
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes';
|
||||
import { sortSubsonicAlbums } from '@/utils/library/albumBrowseSort';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
} from '@/lib/library/albumBrowseFilters';
|
||||
import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
|
||||
import { sortSubsonicAlbums } from '@/lib/library/albumBrowseSort';
|
||||
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
|
||||
import { entryBelongsToServer } from '@/store/localPlaybackResolve';
|
||||
|
||||
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
|
||||
|
||||
@@ -62,7 +62,7 @@ vi.mock('@/lib/api/subsonicPlaylists', () => ({
|
||||
getPlaylistForServerMock(serverId, playlistId),
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/library/libraryReady', () => ({
|
||||
vi.mock('@/lib/library/libraryReady', () => ({
|
||||
libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId),
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
import { registerMediaResolver } from '@/store/mediaResolver';
|
||||
import {
|
||||
loadAlbumFromLibraryIndex,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import type { PinnedGroup } from '@/store/localPlaybackStore';
|
||||
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { isManualOfflinePlaylist } from '@/features/offline/utils/pinnedOfflineSync';
|
||||
import {
|
||||
hasLocalLibraryBytes,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isActiveServerReachable } from '@/utils/network/activeServerReachabilit
|
||||
import {
|
||||
albumToAlbum,
|
||||
trackToSong,
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
} from '@/lib/library/advancedSearchLocal';
|
||||
import { dedupeById } from '@/lib/util/dedupeById';
|
||||
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
|
||||
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
|
||||
import { useOfflineStore } from '@/features/offline/store/offlineStore';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability';
|
||||
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '@/features/playback/store/playListenSession';
|
||||
import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
|
||||
|
||||
vi.mock('@/utils/library/libraryReady', () => ({
|
||||
vi.mock('@/lib/library/libraryReady', () => ({
|
||||
libraryIsReady: vi.fn(async () => true),
|
||||
}));
|
||||
|
||||
@@ -178,7 +178,7 @@ describe('playListenSession', () => {
|
||||
});
|
||||
|
||||
it('skips when library is not ready', async () => {
|
||||
const { libraryIsReady } = await import('@/utils/library/libraryReady');
|
||||
const { libraryIsReady } = await import('@/lib/library/libraryReady');
|
||||
vi.mocked(libraryIsReady).mockResolvedValueOnce(false);
|
||||
vi.useFakeTimers();
|
||||
await playListenSessionOpen(testTrack, 'server-1');
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
libraryRecordPlaySession,
|
||||
type PlaySessionEndReason,
|
||||
} from '@/lib/api/library';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
|
||||
import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { QueueItemRef, Track } from '@/features/playback/store/playerStoreT
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
|
||||
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
|
||||
/**
|
||||
* Queue track resolver (thin-state phase 2). Resolves `QueueItemRef`s to full
|
||||
|
||||
@@ -22,23 +22,23 @@ vi.mock('@/lib/api/subsonicClient', () => ({
|
||||
libraryScopeForServer: vi.fn(() => 'music'),
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/library/libraryReady', () => ({
|
||||
vi.mock('@/lib/library/libraryReady', () => ({
|
||||
libraryIsReady: vi.fn(),
|
||||
}));
|
||||
|
||||
// Spread the real leaf module so other consumers pulled in transitively (the
|
||||
// album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs
|
||||
// GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here.
|
||||
vi.mock('@/utils/library/genreAlbumBrowse', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('@/utils/library/genreAlbumBrowse')>()),
|
||||
vi.mock('@/lib/library/genreAlbumBrowse', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('@/lib/library/genreAlbumBrowse')>()),
|
||||
fetchGenreAlbumTotal: vi.fn(),
|
||||
}));
|
||||
|
||||
import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library';
|
||||
import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
|
||||
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
|
||||
import { resetGenreCatalogCountsCacheForTests } from '@/utils/library/genreCatalogCountsCache';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
|
||||
import { resetGenreCatalogCountsCacheForTests } from '@/lib/library/genreCatalogCountsCache';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
|
||||
describe('genreBrowsePlayback', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { libraryScopeForServer } from '@/lib/api/subsonicClient';
|
||||
import type { Track } from '@/features/playback/store/playerStoreTypes';
|
||||
import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
|
||||
import { shuffleArray } from '@/lib/util/shuffleArray';
|
||||
import { trackToSong } from '@/utils/library/advancedSearchLocal';
|
||||
import { type AlbumBrowseSort } from '@/utils/library/albumBrowseSort';
|
||||
import { trackToSong } from '@/lib/library/advancedSearchLocal';
|
||||
import { type AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
|
||||
import {
|
||||
genreCatalogCacheKey,
|
||||
getInflightGenreCatalog,
|
||||
@@ -17,9 +17,9 @@ import {
|
||||
peekGenreCatalogCache,
|
||||
trackInflightGenreCatalog,
|
||||
writeGenreCatalogCache,
|
||||
} from '@/utils/library/genreCatalogCountsCache';
|
||||
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
} from '@/lib/library/genreCatalogCountsCache';
|
||||
import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
|
||||
/** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */
|
||||
export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '@/utils/componentHelpers/queuePanelHelpers';
|
||||
import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder';
|
||||
import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider';
|
||||
import { formatQueueBpmTech, formatQueueMoodLabels } from '@/utils/library/trackEnrichment';
|
||||
import { formatQueueBpmTech, formatQueueMoodLabels } from '@/lib/library/trackEnrichment';
|
||||
import { useQueueTrackEnrichment } from '@/features/queue/hooks/useQueueTrackEnrichment';
|
||||
import { QueueLufsTargetMenu } from '@/features/queue/components/QueueLufsTargetMenu';
|
||||
import { PlaybackBufferingOverlay } from '@/components/playback/PlaybackBufferingOverlay';
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
OXIMEDIA_MOOD_UI_ENABLED,
|
||||
parseTrackEnrichmentFacts,
|
||||
type ParsedTrackEnrichment,
|
||||
} from '@/utils/library/trackEnrichment';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
} from '@/lib/library/trackEnrichment';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity';
|
||||
|
||||
const EMPTY: ParsedTrackEnrichment = {
|
||||
|
||||
@@ -9,17 +9,17 @@ import {
|
||||
mergeLiveSearchResults,
|
||||
runLocalLiveSearch,
|
||||
runNetworkLiveSearch,
|
||||
} from '@/utils/library/liveSearchLocal';
|
||||
import { raceLiveSearch } from '@/utils/library/searchRace';
|
||||
import { libraryIsReady } from '@/utils/library/libraryReady';
|
||||
} from '@/lib/library/liveSearchLocal';
|
||||
import { raceLiveSearch } from '@/lib/library/searchRace';
|
||||
import { libraryIsReady } from '@/lib/library/libraryReady';
|
||||
import {
|
||||
emitLiveSearchDebug,
|
||||
searchHitCounts,
|
||||
searchResultSamples,
|
||||
} from '@/utils/library/liveSearchDebug';
|
||||
} from '@/lib/library/liveSearchDebug';
|
||||
import {
|
||||
logLibrarySearch,
|
||||
} from '@/utils/library/libraryDevLog';
|
||||
} from '@/lib/library/libraryDevLog';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useNavigateToAlbum } from '@/features/album';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* search/browse page (lazy via the deep path `pages/SearchBrowsePage`), live
|
||||
* search scope UI/state, and the share-link search/queue-preview surfaces.
|
||||
*
|
||||
* The local-index query engine (`utils/library/*search*`), the shared Subsonic
|
||||
* The local-index query engine (`lib/library/*search*`), the shared Subsonic
|
||||
* search API, the cross-cutting `liveSearchScopeStore`, and the
|
||||
* `advancedSearch*` session/scroll state live outside this feature
|
||||
* (library-core / cross-cutting) — this feature consumes them.
|
||||
|
||||
@@ -42,19 +42,19 @@ import {
|
||||
runNetworkAdvancedTextSearch,
|
||||
runNetworkAdvancedYearAlbums,
|
||||
tryRunLocalAdvancedSearch,
|
||||
} from '@/utils/library/advancedSearchLocal';
|
||||
import { isLosslessSuffix } from '@/utils/library/losslessFormats';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode';
|
||||
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/utils/library/trackEnrichment';
|
||||
import { raceSearchSources } from '@/utils/library/searchRace';
|
||||
import { logLibrarySearch } from '@/utils/library/libraryDevLog';
|
||||
} from '@/lib/library/advancedSearchLocal';
|
||||
import { isLosslessSuffix } from '@/lib/library/losslessFormats';
|
||||
import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
|
||||
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/lib/library/trackEnrichment';
|
||||
import { raceSearchSources } from '@/lib/library/searchRace';
|
||||
import { logLibrarySearch } from '@/lib/library/libraryDevLog';
|
||||
import {
|
||||
browseRaceCountsFullSearch,
|
||||
loadMoreLocalBrowseSongs,
|
||||
raceBrowseWithLocalFallback,
|
||||
runLocalBrowseFullSearch,
|
||||
runNetworkBrowseFullSearch,
|
||||
} from '@/utils/library/browseTextSearch';
|
||||
} from '@/lib/library/browseTextSearch';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { MOOD_GROUP_IDS } from '@/config/moodGroups';
|
||||
import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
ADVANCED_PARALLELISM_MAX,
|
||||
ADVANCED_PARALLELISM_MIN,
|
||||
type AnalyticsStrategy,
|
||||
} from '@/utils/library/analysisStrategy';
|
||||
} from '@/lib/library/analysisStrategy';
|
||||
|
||||
type ClearTarget = {
|
||||
serverId: string;
|
||||
|
||||
@@ -19,11 +19,11 @@ import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
|
||||
import { showToast } from '@/utils/ui/toast';
|
||||
import { formatBytes } from '@/lib/format/formatBytes';
|
||||
import { clearImageCache, getImageCacheSize } from '@/utils/imageCache';
|
||||
import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake';
|
||||
import { wakeLibraryCoverBackfill } from '@/lib/library/coverBackfillWake';
|
||||
import {
|
||||
COVER_CACHE_STRATEGIES,
|
||||
type CoverCacheStrategy,
|
||||
} from '@/utils/library/coverStrategy';
|
||||
} from '@/lib/library/coverStrategy';
|
||||
|
||||
type ClearTarget =
|
||||
| { kind: 'image' }
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { SyncStateDto } from '@/lib/api/library';
|
||||
import {
|
||||
libraryStatusDisplayTrackCount,
|
||||
libraryStatusIsReady,
|
||||
} from '@/utils/library/libraryReady';
|
||||
} from '@/lib/library/libraryReady';
|
||||
import type { LibraryServerConnection } from '@/hooks/useLibraryIndexSync';
|
||||
|
||||
interface ServerLibraryIndexControlsProps {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type Instan
|
||||
import { buildCapabilityContext } from '@/serverCapabilities/context';
|
||||
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
||||
import { libraryDeleteServerData, librarySyncClearSession } from '@/lib/api/library';
|
||||
import { bootstrapIndexedServer } from '@/utils/library/librarySession';
|
||||
import { bootstrapIndexedServer } from '@/lib/library/librarySession';
|
||||
import { useLibraryIndexSync } from '@/hooks/useLibraryIndexSync';
|
||||
import ServerLibraryIndexControls from '@/features/settings/components/ServerLibraryIndexControls';
|
||||
import type { ServerProfile } from '@/store/authStoreTypes';
|
||||
|
||||
Reference in New Issue
Block a user