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:
Psychotoxical
2026-06-30 17:06:03 +02:00
parent e5705f853e
commit 2756cb7698
129 changed files with 139 additions and 139 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ import { runLegacyOfflineFileMigration } from '@/features/offline';
import { reconcileLibraryTierForServer } from '@/features/offline'; import { reconcileLibraryTierForServer } from '@/features/offline';
import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer'; import { initMiniPlayerBridgeOnMain } from '@/features/miniPlayer';
import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration'; import { runAdvancedModeMigration } from '../utils/migrations/advancedModeMigration';
import { bootstrapAllIndexedServers } from '../utils/library/librarySession'; import { bootstrapAllIndexedServers } from '@/lib/library/librarySession';
import { hydrateQueueFromIndex } from '@/features/playback/store/queueRestore'; import { hydrateQueueFromIndex } from '@/features/playback/store/queueRestore';
import { useLibraryAnalysisBackfill } from '../hooks/useLibraryAnalysisBackfill'; import { useLibraryAnalysisBackfill } from '../hooks/useLibraryAnalysisBackfill';
import { useCoverArtPrefetch } from '../cover/useCoverArtPrefetch'; import { useCoverArtPrefetch } from '../cover/useCoverArtPrefetch';
+1 -1
View File
@@ -1,5 +1,5 @@
import { getGenres } from '@/lib/api/subsonicGenres'; import { getGenres } from '@/lib/api/subsonicGenres';
import type { GenreFilterOption } from '../utils/library/albumBrowseLoad'; import type { GenreFilterOption } from '@/lib/library/albumBrowseLoad';
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Check, Filter, X } from 'lucide-react'; import { Check, Filter, X } from 'lucide-react';
+2 -2
View File
@@ -14,13 +14,13 @@ import { copyTextToClipboard } from '../utils/server/serverMagicString';
import { showToast } from '../utils/ui/toast'; import { showToast } from '../utils/ui/toast';
import { formatTrackTime } from '@/lib/format/formatDuration'; import { formatTrackTime } from '@/lib/format/formatDuration';
import { formatLastSeen } from '../utils/componentHelpers/userMgmtHelpers'; import { formatLastSeen } from '../utils/componentHelpers/userMgmtHelpers';
import { libraryIsReady } from '../utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
import { import {
formatQueueMoodLabels, formatQueueMoodLabels,
parseTrackEnrichmentFacts, parseTrackEnrichmentFacts,
resolveQueueBpm, resolveQueueBpm,
type ParsedTrackEnrichment, type ParsedTrackEnrichment,
} from '../utils/library/trackEnrichment'; } from '@/lib/library/trackEnrichment';
import i18n from '@/lib/i18n'; import i18n from '@/lib/i18n';
function formatSize(bytes?: number): string | null { function formatSize(bytes?: number): string | null {
+1 -1
View File
@@ -12,7 +12,7 @@ import {
normalizeAlbumYearToFieldChange, normalizeAlbumYearToFieldChange,
resolveAlbumYearBounds, resolveAlbumYearBounds,
stepAlbumYearField, stepAlbumYearField,
} from '../utils/library/albumYearFilter'; } from '@/lib/library/albumYearFilter';
interface Props { interface Props {
from: string; from: string;
+2 -2
View File
@@ -1,8 +1,8 @@
import type { CoverCacheStrategy } from '../utils/library/coverStrategy'; import type { CoverCacheStrategy } from '@/lib/library/coverStrategy';
import { import {
coverStrategyAllowsLibraryBackfill, coverStrategyAllowsLibraryBackfill,
coverStrategyAllowsRoutePrefetch, coverStrategyAllowsRoutePrefetch,
} from '../utils/library/coverStrategy'; } from '@/lib/library/coverStrategy';
/** @deprecated Use `coverStrategyAllowsRoutePrefetch` */ /** @deprecated Use `coverStrategyAllowsRoutePrefetch` */
export function coverPrefetchStrategyAllowsRoutePrefetch( export function coverPrefetchStrategyAllowsRoutePrefetch(
+1 -1
View File
@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { coverCacheStats } from '../api/coverCache'; import { coverCacheStats } from '../api/coverCache';
import { coverStrategyAllowsRoutePrefetch } from '../utils/library/coverStrategy'; import { coverStrategyAllowsRoutePrefetch } from '@/lib/library/coverStrategy';
import { useCoverStrategyStore } from '../store/coverStrategyStore'; import { useCoverStrategyStore } from '../store/coverStrategyStore';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { coverPrefetchDrainBatch } from './prefetchRegistry'; import { coverPrefetchDrainBatch } from './prefetchRegistry';
@@ -20,8 +20,8 @@ import { sanitizeHtml } from '@/lib/util/sanitizeHtml';
import { OpenArtistRefInline } from '@/features/artist'; import { OpenArtistRefInline } from '@/features/artist';
import { tooltipAttrs } from '@/ui/tooltipAttrs'; import { tooltipAttrs } from '@/ui/tooltipAttrs';
import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline'; import { offlineActionPolicy, type OfflineActionPolicy } from '@/features/offline';
import { deriveAlbumGenreTags } from '@/utils/library/genreTags'; import { deriveAlbumGenreTags } from '@/lib/library/genreTags';
import { genreColor } from '@/utils/library/genreColor'; import { genreColor } from '@/lib/library/genreColor';
/** True when the album artist label means "no single artist" — `getArtistInfo` /** 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. * 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 AlbumRow from '@/features/album/components/AlbumRow';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { runLocalLosslessAlbums } from '@/utils/library/browseTextSearch'; import { runLocalLosslessAlbums } from '@/lib/library/browseTextSearch';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode'; import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
interface Props { interface Props {
disableArtwork?: boolean; disableArtwork?: boolean;
@@ -13,8 +13,8 @@ import {
} from '@/cover/coverTraffic'; } from '@/cover/coverTraffic';
import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue'; import { coverEnsureQueueBacklog, coverEnsureResumePump, coverEnsureSubscribeBacklogDrain } from '@/cover/ensureQueue';
import { dedupeById } from '@/lib/util/dedupeById'; import { dedupeById } from '@/lib/util/dedupeById';
import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/utils/library/albumCompilation'; import { albumBrowseCompScanComplete, albumBrowseCompFilterClientOnly } from '@/lib/library/albumCompilation';
import type { AlbumCompFilter } from '@/utils/library/albumCompilation'; import type { AlbumCompFilter } from '@/lib/library/albumCompilation';
import { import {
albumBrowseHasGenreFilter, albumBrowseHasGenreFilter,
albumBrowseHasServerFilters, albumBrowseHasServerFilters,
@@ -24,12 +24,12 @@ import {
fetchLocalAlbumCatalogChunk, fetchLocalAlbumCatalogChunk,
type AlbumBrowseQuery, type AlbumBrowseQuery,
type GenreFilterOption, type GenreFilterOption,
} from '@/utils/library/albumBrowseLoad'; } from '@/lib/library/albumBrowseLoad';
import { libraryScopeForServer } from '@/lib/api/subsonicClient'; import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import { import {
ALBUM_YEAR_FILTER_DEBOUNCE_MS, ALBUM_YEAR_FILTER_DEBOUNCE_MS,
resolveAlbumYearBounds, resolveAlbumYearBounds,
} from '@/utils/library/albumYearFilter'; } from '@/lib/library/albumYearFilter';
import { loadOfflineAlbumBrowseInitial } from '@/features/offline'; import { loadOfflineAlbumBrowseInitial } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline'; import { useOfflineBrowseReloadToken } from '@/features/offline';
import { import {
@@ -14,7 +14,7 @@ import {
isAlbumDetailPath, isAlbumDetailPath,
useAlbumBrowseSessionStore, useAlbumBrowseSessionStore,
} from '@/features/album/store/albumBrowseSessionStore'; } 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 { shouldRestoreAlbumBrowseSession } from '@/utils/navigation/albumDetailNavigation';
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore'; import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
@@ -3,8 +3,8 @@ import {
ALBUM_YEAR_MAX, ALBUM_YEAR_MAX,
ALBUM_YEAR_MIN, ALBUM_YEAR_MIN,
type AlbumCatalogYearRange, type AlbumCatalogYearRange,
} from '@/utils/library/albumYearFilter'; } from '@/lib/library/albumYearFilter';
import { fetchAlbumCatalogYearBounds } from '@/utils/library/albumCatalogYearBounds'; import { fetchAlbumCatalogYearBounds } from '@/lib/library/albumCatalogYearBounds';
const DEFAULT: AlbumCatalogYearRange = { min: ALBUM_YEAR_MIN, max: ALBUM_YEAR_MAX }; const DEFAULT: AlbumCatalogYearRange = { min: ALBUM_YEAR_MIN, max: ALBUM_YEAR_MAX };
@@ -17,7 +17,7 @@ import {
offlineLocalBrowseEnabled, offlineLocalBrowseEnabled,
} from '@/features/offline'; } from '@/features/offline';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
import { import {
shouldAttemptSubsonicForActiveServer, shouldAttemptSubsonicForActiveServer,
shouldAttemptSubsonicForServer, shouldAttemptSubsonicForServer,
@@ -7,7 +7,7 @@ import {
raceBrowseWithLocalFallback, raceBrowseWithLocalFallback,
runLocalBrowseAlbums, runLocalBrowseAlbums,
runNetworkBrowseAlbums, runNetworkBrowseAlbums,
} from '@/utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline'; import { offlineLocalBrowseEnabled, searchOfflineLocalAlbums } from '@/features/offline';
@@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { dedupeById } from '@/lib/util/dedupeById'; import { dedupeById } from '@/lib/util/dedupeById';
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort'; import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import { import {
fetchGenreAlbumPage, fetchGenreAlbumPage,
GENRE_ALBUM_CATALOG_CHUNK, GENRE_ALBUM_CATALOG_CHUNK,
GENRE_ALBUM_FIRST_PAGE, GENRE_ALBUM_FIRST_PAGE,
} from '@/utils/library/genreAlbumBrowse'; } from '@/lib/library/genreAlbumBrowse';
import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll'; import { useClientSliceInfiniteScroll } from '@/hooks/useClientSliceInfiniteScroll';
import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel'; import { useInpageScrollSentinel } from '@/hooks/useInpageScrollSentinel';
+1 -1
View File
@@ -5,7 +5,7 @@
* the album session store, per-album offline status, and album export helpers. * the album session store, per-album offline status, and album export helpers.
* *
* Stays OUT (library-core / cross-cutting, consumed by this feature, not owned): * 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), * browse surfaces → M4), `albumDetailNavigation` (cross-cutting nav → M4),
* `playAlbum` (playback action → playback), `starredAlbumIndexSync` (index * `playAlbum` (playback action → playback), `starredAlbumIndexSync` (index
* sync, core), the album context-menu items (context-menu subsystem), the * sync, core), the album context-menu items (context-menu subsystem), the
+2 -2
View File
@@ -43,8 +43,8 @@ import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
import { albumGridWarmCovers } from '@/cover/layoutSizes'; import { albumGridWarmCovers } from '@/cover/layoutSizes';
import { VirtualCardGrid } from '@/components/VirtualCardGrid'; import { VirtualCardGrid } from '@/components/VirtualCardGrid';
import LosslessModeBanner from '@/components/LosslessModeBanner'; import LosslessModeBanner from '@/components/LosslessModeBanner';
import { isLosslessSuffix } from '@/utils/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { isLosslessMode } from '@/utils/library/losslessMode'; import { isLosslessMode } from '@/lib/library/losslessMode';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { offlineActionPolicy } from '@/features/offline'; import { offlineActionPolicy } from '@/features/offline';
+4 -4
View File
@@ -43,15 +43,15 @@ import { peekAlbumBrowseScrollRestore } from '@/features/album/store/albumBrowse
import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation'; import { readAlbumBrowseRestore } from '@/utils/navigation/albumDetailNavigation';
import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs'; import { albumArtistDisplayName } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
import { useAlbumCatalogYearBounds } from '@/features/album/hooks/useAlbumCatalogYearBounds'; import { useAlbumCatalogYearBounds } from '@/features/album/hooks/useAlbumCatalogYearBounds';
import type { AlbumBrowseSort } from '@/utils/library/albumBrowseSort'; import type { AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode'; import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { resolveAlbumYearBounds } from '@/utils/library/albumYearFilter'; import { resolveAlbumYearBounds } from '@/lib/library/albumYearFilter';
import { import {
filterAlbumsByCompilation, filterAlbumsByCompilation,
filterAlbumsByGenres, filterAlbumsByGenres,
filterAlbumsByStarred, filterAlbumsByStarred,
filterAlbumsByYearBounds, filterAlbumsByYearBounds,
} from '@/utils/library/albumBrowseFilters'; } from '@/lib/library/albumBrowseFilters';
import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore'; import { useScopedBrowseSearchQuery } from '@/store/liveSearchScopeStore';
type SortType = AlbumBrowseSort; type SortType = AlbumBrowseSort;
+2 -2
View File
@@ -4,7 +4,7 @@ import type { SubsonicAlbum } from '@/lib/api/subsonicTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import AlbumCard from '@/features/album/components/AlbumCard'; 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 { ndListLosslessAlbumsPage } from '@/lib/api/navidromeBrowse';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
@@ -38,7 +38,7 @@ import {
runLocalAlbumBrowsePage, runLocalAlbumBrowsePage,
sortSubsonicAlbums, sortSubsonicAlbums,
type AlbumBrowseSort, type AlbumBrowseSort,
} from '@/utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
/** Local index page size — SQLite is cheap; larger pages than the network walk. */ /** Local index page size — SQLite is cheap; larger pages than the network walk. */
const LOCAL_PAGE_SIZE = 30; const LOCAL_PAGE_SIZE = 30;
+1 -1
View File
@@ -15,7 +15,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { filterAlbumsByMixRatings, getMixMinRatingsConfigFromAuth } from '@/utils/mix/mixRatingFilter'; 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 { useOfflineStore } from '@/features/offline';
import { useDownloadModalStore } from '@/features/offline'; import { useDownloadModalStore } from '@/features/offline';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
@@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; 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'; 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 { dedupeById } from '@/lib/util/dedupeById';
import { isOfflineBrowseActive } from '@/features/offline'; import { isOfflineBrowseActive } from '@/features/offline';
import { loadOfflineAlbumCatalogChunk } from '@/features/offline'; import { loadOfflineAlbumCatalogChunk } from '@/features/offline';
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes'; import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
import { fetchLocalAlbumCatalogChunk } from '@/utils/library/albumBrowseLoad'; import { fetchLocalAlbumCatalogChunk } from '@/lib/library/albumBrowseLoad';
export type AlbumCatalogChunk = { export type AlbumCatalogChunk = {
albums: SubsonicAlbum[]; albums: SubsonicAlbum[];
@@ -11,8 +11,8 @@ import { loadArtistFromLibraryIndex } from '@/features/offline';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline'; import { loadArtistFromLocalPlayback, offlineLocalBrowseEnabled } from '@/features/offline';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; import { readDetailServerId } from '@/utils/navigation/detailServerScope';
import { runLocalArtistLosslessBrowse } from '@/utils/library/browseTextSearch'; import { runLocalArtistLosslessBrowse } from '@/lib/library/browseTextSearch';
import { isLosslessSuffix } from '@/utils/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
export interface UseArtistDetailDataOptions { export interface UseArtistDetailDataOptions {
/** When true, albums and top tracks are limited to lossless containers (local index preferred). */ /** 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 { import {
fetchLocalArtistCatalogChunk, fetchLocalArtistCatalogChunk,
fetchNetworkStarredArtists, fetchNetworkStarredArtists,
} from '@/utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { useOfflineBrowseReloadToken } from '@/features/offline'; import { useOfflineBrowseReloadToken } from '@/features/offline';
import { import {
@@ -8,7 +8,7 @@ import {
runLocalBrowseArtists, runLocalBrowseArtists,
runNetworkBrowseArtists, runNetworkBrowseArtists,
type LibrarySearchSurface, type LibrarySearchSurface,
} from '@/utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { offlineLocalBrowseEnabled, searchOfflineLocalArtists } from '@/features/offline'; import { offlineLocalBrowseEnabled, searchOfflineLocalArtists } from '@/features/offline';
+1 -1
View File
@@ -3,7 +3,7 @@
* `pages/*` paths, not re-exported), artist cards/rows/avatars, the detail hero + * `pages/*` paths, not re-exported), artist cards/rows/avatars, the detail hero +
* similar-artists + top-tracks UI, the artist Subsonic API, browse catalog/ * similar-artists + top-tracks UI, the artist Subsonic API, browse catalog/
* filter/scroll hooks, per-artist offline status, and the open-artist-ref nav * 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). * not the shared index query engine).
* *
* Stays OUT (other owners): `deriveAlbumHeaderArtistRefs` (album header), * Stays OUT (other owners): `deriveAlbumHeaderArtistRefs` (album header),
+1 -1
View File
@@ -36,7 +36,7 @@ import { artistDetailCoverWarmAlbums } from '@/features/artist/components/topSon
import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch'; import { useLibraryCoverPrefetch } from '@/cover/useLibraryCoverPrefetch';
import { useWarmGridCovers } from '@/hooks/useWarmGridCovers'; import { useWarmGridCovers } from '@/hooks/useWarmGridCovers';
import { VirtualCardGrid } from '@/components/VirtualCardGrid'; 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 { sortArtistAlbumsByYear } from '@/features/artist/utils/sortArtistAlbums';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; 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 type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline'; import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline';
import { trackToSong } from '@/utils/library/advancedSearchLocal'; import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
const TOP_SONGS_LIMIT = 5; const TOP_SONGS_LIMIT = 5;
@@ -1,5 +1,5 @@
import type { SubsonicAlbum } from '@/lib/api/subsonicTypes'; 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 { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import { import {
fetchOfflineLocalAlbumCatalogChunk, fetchOfflineLocalAlbumCatalogChunk,
@@ -7,7 +7,7 @@ import type { LocalPlaybackEntry, PinnedGroup, PinSource } from '@/store/localPl
import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlaybackResolve'; import { findLocalPlaybackEntry, hasLocalLibraryBytes } from '@/store/localPlaybackResolve';
import { useOfflineStore, type OfflineAlbumMeta } from '@/features/offline/store/offlineStore'; 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 { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
@@ -8,7 +8,7 @@ import {
albumToAlbum, albumToAlbum,
artistToArtist, artistToArtist,
trackToSong, trackToSong,
} from '@/utils/library/advancedSearchLocal'; } from '@/lib/library/advancedSearchLocal';
export async function loadAlbumFromLibraryIndex( export async function loadAlbumFromLibraryIndex(
serverId: string, serverId: string,
@@ -9,17 +9,17 @@ import {
artistToArtist, artistToArtist,
resolveTrackCoverArtId, resolveTrackCoverArtId,
trackToSong, trackToSong,
} from '@/utils/library/advancedSearchLocal'; } from '@/lib/library/advancedSearchLocal';
import { albumIsCompilationFromTrackDtos } from '@/utils/library/albumCompilation'; import { albumIsCompilationFromTrackDtos } from '@/lib/library/albumCompilation';
import { import {
filterAlbumsByCompilation, filterAlbumsByCompilation,
filterAlbumsByGenres, filterAlbumsByGenres,
filterAlbumsByStarred, filterAlbumsByStarred,
filterAlbumsByYearBounds, filterAlbumsByYearBounds,
} from '@/utils/library/albumBrowseFilters'; } from '@/lib/library/albumBrowseFilters';
import type { AlbumBrowseQuery } from '@/utils/library/albumBrowseTypes'; import type { AlbumBrowseQuery } from '@/lib/library/albumBrowseTypes';
import { sortSubsonicAlbums } from '@/utils/library/albumBrowseSort'; import { sortSubsonicAlbums } from '@/lib/library/albumBrowseSort';
import { isLosslessSuffix } from '@/utils/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { entryBelongsToServer } from '@/store/localPlaybackResolve'; import { entryBelongsToServer } from '@/store/localPlaybackResolve';
function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] { function sortBrowsableSongs(songs: SubsonicSong[]): SubsonicSong[] {
@@ -62,7 +62,7 @@ vi.mock('@/lib/api/subsonicPlaylists', () => ({
getPlaylistForServerMock(serverId, playlistId), getPlaylistForServerMock(serverId, playlistId),
})); }));
vi.mock('@/utils/library/libraryReady', () => ({ vi.mock('@/lib/library/libraryReady', () => ({
libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId), libraryIsReady: (serverId: string) => libraryIsReadyMock(serverId),
})); }));
@@ -10,7 +10,7 @@ import type {
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode'; 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 { registerMediaResolver } from '@/store/mediaResolver';
import { import {
loadAlbumFromLibraryIndex, loadAlbumFromLibraryIndex,
@@ -3,7 +3,7 @@ import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import type { PinnedGroup } from '@/store/localPlaybackStore'; import type { PinnedGroup } from '@/store/localPlaybackStore';
import { useLocalPlaybackStore } 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 { isManualOfflinePlaylist } from '@/features/offline/utils/pinnedOfflineSync';
import { import {
hasLocalLibraryBytes, hasLocalLibraryBytes,
@@ -10,7 +10,7 @@ import { isActiveServerReachable } from '@/utils/network/activeServerReachabilit
import { import {
albumToAlbum, albumToAlbum,
trackToSong, trackToSong,
} from '@/utils/library/advancedSearchLocal'; } from '@/lib/library/advancedSearchLocal';
import { dedupeById } from '@/lib/util/dedupeById'; import { dedupeById } from '@/lib/util/dedupeById';
import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode'; import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode';
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse'; import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
@@ -4,7 +4,7 @@ import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore'; import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore';
import { useOfflineStore } 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 { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
@@ -14,7 +14,7 @@ import {
} from '@/features/playback/store/playListenSession'; } from '@/features/playback/store/playListenSession';
import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; import { onPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
vi.mock('@/utils/library/libraryReady', () => ({ vi.mock('@/lib/library/libraryReady', () => ({
libraryIsReady: vi.fn(async () => true), libraryIsReady: vi.fn(async () => true),
})); }));
@@ -178,7 +178,7 @@ describe('playListenSession', () => {
}); });
it('skips when library is not ready', async () => { 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.mocked(libraryIsReady).mockResolvedValueOnce(false);
vi.useFakeTimers(); vi.useFakeTimers();
await playListenSessionOpen(testTrack, 'server-1'); await playListenSessionOpen(testTrack, 'server-1');
@@ -5,7 +5,7 @@ import {
libraryRecordPlaySession, libraryRecordPlaySession,
type PlaySessionEndReason, type PlaySessionEndReason,
} from '@/lib/api/library'; } 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 { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded'; import { emitPlaySessionRecorded } from '@/features/playback/store/playSessionRecorded';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; 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 { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey';
import { trackToSong } from '@/utils/library/advancedSearchLocal'; import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
/** /**
* Queue track resolver (thin-state phase 2). Resolves `QueueItemRef`s to full * 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'), libraryScopeForServer: vi.fn(() => 'music'),
})); }));
vi.mock('@/utils/library/libraryReady', () => ({ vi.mock('@/lib/library/libraryReady', () => ({
libraryIsReady: vi.fn(), libraryIsReady: vi.fn(),
})); }));
// Spread the real leaf module so other consumers pulled in transitively (the // Spread the real leaf module so other consumers pulled in transitively (the
// album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs // album barrel reaches this via the artist↔album edge → useGenreAlbumBrowse needs
// GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here. // GENRE_ALBUM_FIRST_PAGE); only fetchGenreAlbumTotal is stubbed here.
vi.mock('@/utils/library/genreAlbumBrowse', async (importOriginal) => ({ vi.mock('@/lib/library/genreAlbumBrowse', async (importOriginal) => ({
...(await importOriginal<typeof import('@/utils/library/genreAlbumBrowse')>()), ...(await importOriginal<typeof import('@/lib/library/genreAlbumBrowse')>()),
fetchGenreAlbumTotal: vi.fn(), fetchGenreAlbumTotal: vi.fn(),
})); }));
import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library'; import { libraryAdvancedSearch, libraryGetGenreAlbumCounts } from '@/lib/api/library';
import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres'; import { fetchAllSongsByGenre, getGenres } from '@/lib/api/subsonicGenres';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse'; import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
import { resetGenreCatalogCountsCacheForTests } from '@/utils/library/genreCatalogCountsCache'; import { resetGenreCatalogCountsCacheForTests } from '@/lib/library/genreCatalogCountsCache';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
describe('genreBrowsePlayback', () => { describe('genreBrowsePlayback', () => {
beforeEach(() => { beforeEach(() => {
@@ -8,8 +8,8 @@ import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import type { Track } from '@/features/playback/store/playerStoreTypes'; import type { Track } from '@/features/playback/store/playerStoreTypes';
import { songToTrack } from '@/features/playback/utils/playback/songToTrack'; import { songToTrack } from '@/features/playback/utils/playback/songToTrack';
import { shuffleArray } from '@/lib/util/shuffleArray'; import { shuffleArray } from '@/lib/util/shuffleArray';
import { trackToSong } from '@/utils/library/advancedSearchLocal'; import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { type AlbumBrowseSort } from '@/utils/library/albumBrowseSort'; import { type AlbumBrowseSort } from '@/lib/library/albumBrowseSort';
import { import {
genreCatalogCacheKey, genreCatalogCacheKey,
getInflightGenreCatalog, getInflightGenreCatalog,
@@ -17,9 +17,9 @@ import {
peekGenreCatalogCache, peekGenreCatalogCache,
trackInflightGenreCatalog, trackInflightGenreCatalog,
writeGenreCatalogCache, writeGenreCatalogCache,
} from '@/utils/library/genreCatalogCountsCache'; } from '@/lib/library/genreCatalogCountsCache';
import { fetchGenreAlbumTotal } from '@/utils/library/genreAlbumBrowse'; import { fetchGenreAlbumTotal } from '@/lib/library/genreAlbumBrowse';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
/** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */ /** Drop genres with no indexed albums/tracks (stale server list or orphan rows). */
export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] { export function filterGenresWithContent(genres: SubsonicGenre[]): SubsonicGenre[] {
@@ -10,7 +10,7 @@ import {
} from '@/utils/componentHelpers/queuePanelHelpers'; } from '@/utils/componentHelpers/queuePanelHelpers';
import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder'; import { loudnessGainPlaceholderUntilCacheDb } from '@/features/playback/utils/audio/loudnessPlaceholder';
import { effectiveLoudnessPreAnalysisAttenuationDb } from '@/utils/audio/loudnessPreAnalysisSlider'; 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 { useQueueTrackEnrichment } from '@/features/queue/hooks/useQueueTrackEnrichment';
import { QueueLufsTargetMenu } from '@/features/queue/components/QueueLufsTargetMenu'; import { QueueLufsTargetMenu } from '@/features/queue/components/QueueLufsTargetMenu';
import { PlaybackBufferingOverlay } from '@/components/playback/PlaybackBufferingOverlay'; import { PlaybackBufferingOverlay } from '@/components/playback/PlaybackBufferingOverlay';
@@ -8,8 +8,8 @@ import {
OXIMEDIA_MOOD_UI_ENABLED, OXIMEDIA_MOOD_UI_ENABLED,
parseTrackEnrichmentFacts, parseTrackEnrichmentFacts,
type ParsedTrackEnrichment, type ParsedTrackEnrichment,
} from '@/utils/library/trackEnrichment'; } from '@/lib/library/trackEnrichment';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { normalizeAnalysisTrackId } from '@/features/playback/utils/playback/queueIdentity';
const EMPTY: ParsedTrackEnrichment = { const EMPTY: ParsedTrackEnrichment = {
@@ -9,17 +9,17 @@ import {
mergeLiveSearchResults, mergeLiveSearchResults,
runLocalLiveSearch, runLocalLiveSearch,
runNetworkLiveSearch, runNetworkLiveSearch,
} from '@/utils/library/liveSearchLocal'; } from '@/lib/library/liveSearchLocal';
import { raceLiveSearch } from '@/utils/library/searchRace'; import { raceLiveSearch } from '@/lib/library/searchRace';
import { libraryIsReady } from '@/utils/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
import { import {
emitLiveSearchDebug, emitLiveSearchDebug,
searchHitCounts, searchHitCounts,
searchResultSamples, searchResultSamples,
} from '@/utils/library/liveSearchDebug'; } from '@/lib/library/liveSearchDebug';
import { import {
logLibrarySearch, logLibrarySearch,
} from '@/utils/library/libraryDevLog'; } from '@/lib/library/libraryDevLog';
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { useNavigateToAlbum } from '@/features/album'; import { useNavigateToAlbum } from '@/features/album';
+1 -1
View File
@@ -3,7 +3,7 @@
* search/browse page (lazy via the deep path `pages/SearchBrowsePage`), live * search/browse page (lazy via the deep path `pages/SearchBrowsePage`), live
* search scope UI/state, and the share-link search/queue-preview surfaces. * 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 * search API, the cross-cutting `liveSearchScopeStore`, and the
* `advancedSearch*` session/scroll state live outside this feature * `advancedSearch*` session/scroll state live outside this feature
* (library-core / cross-cutting) this feature consumes them. * (library-core / cross-cutting) this feature consumes them.
@@ -42,19 +42,19 @@ import {
runNetworkAdvancedTextSearch, runNetworkAdvancedTextSearch,
runNetworkAdvancedYearAlbums, runNetworkAdvancedYearAlbums,
tryRunLocalAdvancedSearch, tryRunLocalAdvancedSearch,
} from '@/utils/library/advancedSearchLocal'; } from '@/lib/library/advancedSearchLocal';
import { isLosslessSuffix } from '@/utils/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { LOSSLESS_MODE_QUERY } from '@/utils/library/losslessMode'; import { LOSSLESS_MODE_QUERY } from '@/lib/library/losslessMode';
import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/utils/library/trackEnrichment'; import { OXIMEDIA_MOOD_SEARCH_ENABLED } from '@/lib/library/trackEnrichment';
import { raceSearchSources } from '@/utils/library/searchRace'; import { raceSearchSources } from '@/lib/library/searchRace';
import { logLibrarySearch } from '@/utils/library/libraryDevLog'; import { logLibrarySearch } from '@/lib/library/libraryDevLog';
import { import {
browseRaceCountsFullSearch, browseRaceCountsFullSearch,
loadMoreLocalBrowseSongs, loadMoreLocalBrowseSongs,
raceBrowseWithLocalFallback, raceBrowseWithLocalFallback,
runLocalBrowseFullSearch, runLocalBrowseFullSearch,
runNetworkBrowseFullSearch, runNetworkBrowseFullSearch,
} from '@/utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { MOOD_GROUP_IDS } from '@/config/moodGroups'; import { MOOD_GROUP_IDS } from '@/config/moodGroups';
import { usePerfProbeFlags } from '@/utils/perf/perfFlags'; import { usePerfProbeFlags } from '@/utils/perf/perfFlags';
@@ -29,7 +29,7 @@ import {
ADVANCED_PARALLELISM_MAX, ADVANCED_PARALLELISM_MAX,
ADVANCED_PARALLELISM_MIN, ADVANCED_PARALLELISM_MIN,
type AnalyticsStrategy, type AnalyticsStrategy,
} from '@/utils/library/analysisStrategy'; } from '@/lib/library/analysisStrategy';
type ClearTarget = { type ClearTarget = {
serverId: string; serverId: string;
@@ -19,11 +19,11 @@ import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey';
import { showToast } from '@/utils/ui/toast'; import { showToast } from '@/utils/ui/toast';
import { formatBytes } from '@/lib/format/formatBytes'; import { formatBytes } from '@/lib/format/formatBytes';
import { clearImageCache, getImageCacheSize } from '@/utils/imageCache'; import { clearImageCache, getImageCacheSize } from '@/utils/imageCache';
import { wakeLibraryCoverBackfill } from '@/utils/library/coverBackfillWake'; import { wakeLibraryCoverBackfill } from '@/lib/library/coverBackfillWake';
import { import {
COVER_CACHE_STRATEGIES, COVER_CACHE_STRATEGIES,
type CoverCacheStrategy, type CoverCacheStrategy,
} from '@/utils/library/coverStrategy'; } from '@/lib/library/coverStrategy';
type ClearTarget = type ClearTarget =
| { kind: 'image' } | { kind: 'image' }
@@ -4,7 +4,7 @@ import type { SyncStateDto } from '@/lib/api/library';
import { import {
libraryStatusDisplayTrackCount, libraryStatusDisplayTrackCount,
libraryStatusIsReady, libraryStatusIsReady,
} from '@/utils/library/libraryReady'; } from '@/lib/library/libraryReady';
import type { LibraryServerConnection } from '@/hooks/useLibraryIndexSync'; import type { LibraryServerConnection } from '@/hooks/useLibraryIndexSync';
interface ServerLibraryIndexControlsProps { interface ServerLibraryIndexControlsProps {
@@ -8,7 +8,7 @@ import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type Instan
import { buildCapabilityContext } from '@/serverCapabilities/context'; import { buildCapabilityContext } from '@/serverCapabilities/context';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { libraryDeleteServerData, librarySyncClearSession } from '@/lib/api/library'; 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 { useLibraryIndexSync } from '@/hooks/useLibraryIndexSync';
import ServerLibraryIndexControls from '@/features/settings/components/ServerLibraryIndexControls'; import ServerLibraryIndexControls from '@/features/settings/components/ServerLibraryIndexControls';
import type { ServerProfile } from '@/store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
@@ -15,7 +15,7 @@ import {
logLibrarySync, logLibrarySync,
normalizeIngestMetrics, normalizeIngestMetrics,
timed, timed,
} from '../../utils/library/libraryDevLog'; } from '@/lib/library/libraryDevLog';
/** /**
* DevTools: log library sync progress + idle with ingest strategy from status. * DevTools: log library sync progress + idle with ingest strategy from status.
+1 -1
View File
@@ -6,7 +6,7 @@ import {
import { librarySqlServerId } from '../api/coverCache'; import { librarySqlServerId } from '../api/coverCache';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useAnalysisStrategyStore } from '../store/analysisStrategyStore'; import { useAnalysisStrategyStore } from '../store/analysisStrategyStore';
import { DEFAULT_ADVANCED_PARALLELISM } from '../utils/library/analysisStrategy'; import { DEFAULT_ADVANCED_PARALLELISM } from '@/lib/library/analysisStrategy';
import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey';
const DISABLED_CONFIGURE = { const DISABLED_CONFIGURE = {
+2 -2
View File
@@ -7,10 +7,10 @@ import {
libraryCoverBackfillSetBaseUrl, libraryCoverBackfillSetBaseUrl,
librarySqlServerId, librarySqlServerId,
} from '../api/coverCache'; } from '../api/coverCache';
import { coverStrategyAllowsLibraryBackfill } from '../utils/library/coverStrategy'; import { coverStrategyAllowsLibraryBackfill } from '@/lib/library/coverStrategy';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useCoverStrategyStore } from '../store/coverStrategyStore'; import { useCoverStrategyStore } from '../store/coverStrategyStore';
import { subscribeLibraryCoverBackfillWake } from '../utils/library/coverBackfillWake'; import { subscribeLibraryCoverBackfillWake } from '@/lib/library/coverBackfillWake';
import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey';
import { subscribeConnectCache } from '../utils/server/serverEndpoint'; import { subscribeConnectCache } from '../utils/server/serverEndpoint';
+3 -3
View File
@@ -15,9 +15,9 @@ import {
bootstrapAllIndexedServers, bootstrapAllIndexedServers,
bootstrapIndexedServer, bootstrapIndexedServer,
type BindServerResult, type BindServerResult,
} from '../utils/library/librarySession'; } from '@/lib/library/librarySession';
import { enqueueLibrarySync } from '../utils/library/librarySyncQueue'; import { enqueueLibrarySync } from '@/lib/library/librarySyncQueue';
import { syncIngestDisplayCount } from '../utils/library/libraryReady'; import { syncIngestDisplayCount } from '@/lib/library/libraryReady';
export type LibraryServerConnection = 'online' | 'offline' | 'unknown'; export type LibraryServerConnection = 'online' | 'offline' | 'unknown';
+2 -2
View File
@@ -14,7 +14,7 @@ vi.mock('@/lib/api/navidromeBrowse', () => ({
ndListSongs: vi.fn(async () => []), ndListSongs: vi.fn(async () => []),
})); }));
vi.mock('../utils/library/advancedSearchLocal', () => ({ vi.mock('@/lib/library/advancedSearchLocal', () => ({
runLocalSongBrowse: vi.fn(async () => []), runLocalSongBrowse: vi.fn(async () => []),
})); }));
@@ -25,7 +25,7 @@ vi.mock('@/features/offline/hooks/useOfflineBrowseReloadToken', () => ({
useOfflineBrowseReloadToken: () => undefined, useOfflineBrowseReloadToken: () => undefined,
})); }));
vi.mock('../utils/library/browseTextSearch', () => ({ vi.mock('@/lib/library/browseTextSearch', () => ({
BROWSE_TEXT_DEBOUNCE_NETWORK_MS: 10, BROWSE_TEXT_DEBOUNCE_NETWORK_MS: 10,
BROWSE_TEXT_DEBOUNCE_RACE_MS: 10, BROWSE_TEXT_DEBOUNCE_RACE_MS: 10,
browseRaceCountsSongs: vi.fn(), browseRaceCountsSongs: vi.fn(),
+2 -2
View File
@@ -2,7 +2,7 @@ import { searchSongsPaged } from '@/lib/api/subsonicSearch';
import type { SubsonicSong } from '@/lib/api/subsonicTypes'; import type { SubsonicSong } from '@/lib/api/subsonicTypes';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { ndListSongs } from '@/lib/api/navidromeBrowse'; import { ndListSongs } from '@/lib/api/navidromeBrowse';
import { runLocalSongBrowse } from '../utils/library/advancedSearchLocal'; import { runLocalSongBrowse } from '@/lib/library/advancedSearchLocal';
import { import {
BROWSE_TEXT_DEBOUNCE_NETWORK_MS, BROWSE_TEXT_DEBOUNCE_NETWORK_MS,
BROWSE_TEXT_DEBOUNCE_RACE_MS, BROWSE_TEXT_DEBOUNCE_RACE_MS,
@@ -11,7 +11,7 @@ import {
raceBrowseWithLocalFallback, raceBrowseWithLocalFallback,
runLocalBrowseSongPage, runLocalBrowseSongPage,
runNetworkBrowseSongPage, runNetworkBrowseSongPage,
} from '../utils/library/browseTextSearch'; } from '@/lib/library/browseTextSearch';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useLibraryIndexStore } from '../store/libraryIndexStore'; import { useLibraryIndexStore } from '../store/libraryIndexStore';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
+1 -1
View File
@@ -1,5 +1,5 @@
import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes'; import type { SubsonicAlbum, SubsonicArtist, SubsonicSong } from '@/lib/api/subsonicTypes';
import { parseItemGenres } from '@/utils/library/genreTags'; import { parseItemGenres } from '@/lib/library/genreTags';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { ndLogin } from '@/lib/api/navidromeAdmin'; import { ndLogin } from '@/lib/api/navidromeAdmin';
+1 -1
View File
@@ -1,6 +1,6 @@
import { api, apiForServer } from '@/lib/api/subsonicClient'; import { api, apiForServer } from '@/lib/api/subsonicClient';
import type { PlaybackReportState, SubsonicNowPlaying } from '@/lib/api/subsonicTypes'; import type { PlaybackReportState, SubsonicNowPlaying } from '@/lib/api/subsonicTypes';
import { patchLibraryTrackOnUse } from '@/utils/library/patchOnUse'; import { patchLibraryTrackOnUse } from '@/lib/library/patchOnUse';
import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard';
async function scrobbleOnServer( async function scrobbleOnServer(
+1 -1
View File
@@ -1,5 +1,5 @@
import { api, libraryFilterParams } from '@/lib/api/subsonicClient'; import { api, libraryFilterParams } from '@/lib/api/subsonicClient';
import { searchQueryIsFtsSafe } from '@/utils/library/searchQueryFtsSafe'; import { searchQueryIsFtsSafe } from '@/lib/library/searchQueryFtsSafe';
import type { import type {
SearchResults, SearchResults,
SubsonicAlbum, SubsonicAlbum,
+2 -2
View File
@@ -1,12 +1,12 @@
import { api, apiForServer, libraryFilterParams, libraryFilterParamsForServer } from '@/lib/api/subsonicClient'; import { api, apiForServer, libraryFilterParams, libraryFilterParamsForServer } from '@/lib/api/subsonicClient';
import { invalidateEntityUserRatingCaches } from '@/lib/api/subsonicRatings'; import { invalidateEntityUserRatingCaches } from '@/lib/api/subsonicRatings';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { patchLibraryTrackOnUse, type StarPatchMeta } from '@/utils/library/patchOnUse'; import { patchLibraryTrackOnUse, type StarPatchMeta } from '@/lib/library/patchOnUse';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { import {
invalidateStarredAlbumBrowse, invalidateStarredAlbumBrowse,
refreshStarredAlbumIndexFromServer, refreshStarredAlbumIndexFromServer,
} from '@/utils/library/starredAlbumIndexSync'; } from '@/lib/library/starredAlbumIndexSync';
import type { import type {
EntityRatingSupportLevel, EntityRatingSupportLevel,
StarredResults, StarredResults,
+1 -1
View File
@@ -1,5 +1,5 @@
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { genreTagsFor } from '@/utils/library/genreTags'; import { genreTagsFor } from '@/lib/library/genreTags';
import { getArtists } from '@/lib/api/subsonicArtists'; import { getArtists } from '@/lib/api/subsonicArtists';
import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary'; import { getAlbumList, getRandomSongs } from '@/lib/api/subsonicLibrary';
import type { import type {
@@ -1,5 +1,5 @@
import type { SubsonicGenre } from '@/lib/api/subsonicTypes'; import type { SubsonicGenre } from '@/lib/api/subsonicTypes';
import { resolveServerIdForIndexKey } from '../server/serverLookup'; import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup';
/** Fresh hits skip SQLite entirely. */ /** Fresh hits skip SQLite entirely. */
const FRESH_TTL_MS = 60 * 60 * 1000; const FRESH_TTL_MS = 60 * 60 * 1000;
@@ -1,4 +1,4 @@
import { coverEnsureQueueBacklog } from '../../cover/ensureQueue'; import { coverEnsureQueueBacklog } from '@/cover/ensureQueue';
/** Target in-flight + queued ensures ≈ workers × multiplier (mirror analysis backfill). */ /** Target in-flight + queued ensures ≈ workers × multiplier (mirror analysis backfill). */
export const LIBRARY_COVER_BACKLOG_DEPTH_MULTIPLIER = 3; export const LIBRARY_COVER_BACKLOG_DEPTH_MULTIPLIER = 3;
@@ -5,7 +5,7 @@
* index when it's enabled and synced enough for trustworthy results. * index when it's enabled and synced enough for trustworthy results.
*/ */
import { libraryGetStatus, type SyncStateDto } from '@/lib/api/library'; import { libraryGetStatus, type SyncStateDto } from '@/lib/api/library';
import { useLibraryIndexStore } from '../../store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
/** Spec §9.3 — shared by Live Search, Advanced Search, browse, … */ /** Spec §9.3 — shared by Live Search, Advanced Search, browse, … */
export function libraryStatusIsReady(status: SyncStateDto): boolean { export function libraryStatusIsReady(status: SyncStateDto): boolean {

Some files were not shown because too many files have changed in this diff Show More