refactor(lib): extract clean server + navigation infra to lib/ (decouple-knot files stay in utils/ pending seam)

This commit is contained in:
Psychotoxical
2026-06-30 20:44:34 +02:00
parent 7d98e649da
commit 13142e6ae3
123 changed files with 191 additions and 191 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
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 { serverIndexKeyFromUrl } from '../utils/server/serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
export interface AnalysisBackfillQueueStatsDto { export interface AnalysisBackfillQueueStatsDto {
queued: number; queued: number;
+2 -2
View File
@@ -2,8 +2,8 @@ import { invoke } from '@tauri-apps/api/core';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useThemeStore } from '../store/themeStore'; import { useThemeStore } from '../store/themeStore';
import { coverIndexKeyFromRef, coverStorageKeyFromRef } from '../cover/storageKeys'; import { coverIndexKeyFromRef, coverStorageKeyFromRef } from '../cover/storageKeys';
import { connectBaseUrlForServer } from '../utils/server/serverEndpoint'; import { connectBaseUrlForServer } from '@/lib/server/serverEndpoint';
import { serverIndexKeyForProfile } from '../utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { restBaseFromUrl } from '@/lib/api/subsonicClient'; import { restBaseFromUrl } from '@/lib/api/subsonicClient';
import type { CoverArtRef, CoverArtTier } from '../cover/types'; import type { CoverArtRef, CoverArtTier } from '../cover/types';
+1 -1
View File
@@ -10,7 +10,7 @@ import type { ServerProfile } from '@/store/authStoreTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { switchActiveServer } from '@/utils/server/switchActiveServer'; import { switchActiveServer } from '@/utils/server/switchActiveServer';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
interface Props { interface Props {
status: ConnectionStatus; status: ConnectionStatus;
+1 -1
View File
@@ -4,7 +4,7 @@ import { libraryGenreTagsInspect, libraryGenreTagsRun } from '@/lib/api/library'
import { migrationInspect, migrationRun, type ServerIndexMapping } from '@/api/migration'; import { migrationInspect, migrationRun, type ServerIndexMapping } from '@/api/migration';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { useMigrationStore } from '@/store/migrationStore'; import { useMigrationStore } from '@/store/migrationStore';
import { serverIndexKeyFromUrl } from '@/utils/server/serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
import { rewriteFrontendStoreKeys } from '@/utils/server/rewriteFrontendStoreKeys'; import { rewriteFrontendStoreKeys } from '@/utils/server/rewriteFrontendStoreKeys';
const MIGRATION_DONE_FLAG = 'psysonic-server-key-migration-v1'; const MIGRATION_DONE_FLAG = 'psysonic-server-key-migration-v1';
+1 -1
View File
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import type { EntitySharePayloadV1 } from '../utils/share/shareLink'; import type { EntitySharePayloadV1 } from '../utils/share/shareLink';
import { decodeSharePayloadFromText } from '../utils/share/shareLink'; import { decodeSharePayloadFromText } from '../utils/share/shareLink';
import { decodeServerMagicStringFromText } from '../utils/server/serverMagicString'; import { decodeServerMagicStringFromText } from '@/lib/server/serverMagicString';
import { applySharePastePayload, applySharePasteQueue } from '../utils/share/applySharePaste'; import { applySharePastePayload, applySharePasteQueue } from '../utils/share/applySharePaste';
import { shareQueueServerContext } from '../utils/share/shareServerOriginLabel'; import { shareQueueServerContext } from '../utils/share/shareServerOriginLabel';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
+1 -1
View File
@@ -10,7 +10,7 @@ import { ndGetSongPath } from '@/lib/api/navidromeAdmin';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { useLibraryIndexStore } from '../store/libraryIndexStore'; import { useLibraryIndexStore } from '../store/libraryIndexStore';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { copyTextToClipboard } from '../utils/server/serverMagicString'; import { copyTextToClipboard } from '@/lib/server/serverMagicString';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/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';
+1 -1
View File
@@ -4,7 +4,7 @@ import {
} from '@/lib/api/subsonicStreamUrl'; } from '@/lib/api/subsonicStreamUrl';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { connectBaseUrlForServer } from '../utils/server/serverEndpoint'; import { connectBaseUrlForServer } from '@/lib/server/serverEndpoint';
import type { CoverArtRef, CoverArtTier } from './types'; import type { CoverArtRef, CoverArtTier } from './types';
/** Builds ephemeral getCoverArt URL — NOT a cache key */ /** Builds ephemeral getCoverArt URL — NOT a cache key */
+1 -1
View File
@@ -1,5 +1,5 @@
import { buildCoverArtUrlForServer } from '@/lib/api/subsonicStreamUrl'; import { buildCoverArtUrlForServer } from '@/lib/api/subsonicStreamUrl';
import { serverShareBaseUrl } from '../../utils/server/serverEndpoint'; import { serverShareBaseUrl } from '@/lib/server/serverEndpoint';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '../../store/authStore';
import type { CoverArtRef } from '../types'; import type { CoverArtRef } from '../types';
+1 -1
View File
@@ -1,4 +1,4 @@
import { findServerByIdOrIndexKey } from '../utils/server/serverLookup'; import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { COVER_SCOPE_ACTIVE, type CoverServerScope } from './types'; import { COVER_SCOPE_ACTIVE, type CoverServerScope } from './types';
/** Explicit server bucket for cover disk/IDB — use when entity carries `serverId` (e.g. cross-server favorites). */ /** Explicit server bucket for cover disk/IDB — use when entity carries `serverId` (e.g. cross-server favorites). */
+2 -2
View File
@@ -1,11 +1,11 @@
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { findServerByIdOrIndexKey } from '../utils/server/serverLookup'; import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { import {
resolveIndexKey, resolveIndexKey,
serverIndexKeyForProfile, serverIndexKeyForProfile,
serverIndexKeyFromUrl, serverIndexKeyFromUrl,
} from '../utils/server/serverIndexKey'; } from '@/lib/server/serverIndexKey';
import type { CoverArtRef, CoverArtTier, CoverServerScope } from './types'; import type { CoverArtRef, CoverArtTier, CoverServerScope } from './types';
/** /**
+2 -2
View File
@@ -11,8 +11,8 @@ 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 '@/lib/library/coverBackfillWake'; import { subscribeLibraryCoverBackfillWake } from '@/lib/library/coverBackfillWake';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { subscribeConnectCache } from '@/utils/server/serverEndpoint'; import { subscribeConnectCache } from '@/lib/server/serverEndpoint';
/** /**
* Library cover warm-up — configure session in Rust; full pass runs natively. * Library cover warm-up — configure session in Rust; full pass runs natively.
+1 -1
View File
@@ -11,7 +11,7 @@ import {
resolvePlaybackCoverScope, resolvePlaybackCoverScope,
} from './ref'; } from './ref';
import { coverServerScopeForServerId } from './serverScope'; import { coverServerScopeForServerId } from './serverScope';
import { resolveServerIdForIndexKey } from '../utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import { import {
resolveAlbumCoverRefFromLibrary, resolveAlbumCoverRefFromLibrary,
+1 -1
View File
@@ -23,7 +23,7 @@ import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency'; import { isAlbumRecentlyAdded } from '@/features/album/utils/albumRecency';
import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album/utils/deriveAlbumHeaderArtistRefs'; import { albumArtistDisplayName, deriveAlbumArtistRefs } from '@/features/album/utils/deriveAlbumHeaderArtistRefs';
import { coverServerScopeForServerId } from '@/cover/serverScope'; import { coverServerScopeForServerId } from '@/cover/serverScope';
import { appendServerQuery } from '@/utils/navigation/detailServerScope'; import { appendServerQuery } from '@/lib/navigation/detailServerScope';
interface AlbumCardProps { interface AlbumCardProps {
album: SubsonicAlbum; album: SubsonicAlbum;
@@ -16,7 +16,7 @@ import {
loadArtistFromLocalPlayback, loadArtistFromLocalPlayback,
offlineLocalBrowseEnabled, offlineLocalBrowseEnabled,
} from '@/features/offline'; } from '@/features/offline';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; import { readDetailServerId } from '@/lib/navigation/detailServerScope';
import { libraryIsReady } from '@/lib/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
import { import {
shouldAttemptSubsonicForActiveServer, shouldAttemptSubsonicForActiveServer,
+1 -1
View File
@@ -45,7 +45,7 @@ import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import LosslessModeBanner from '@/ui/LosslessModeBanner'; import LosslessModeBanner from '@/ui/LosslessModeBanner';
import { isLosslessSuffix } from '@/lib/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
import { isLosslessMode } from '@/lib/library/losslessMode'; import { isLosslessMode } from '@/lib/library/losslessMode';
import { readDetailServerId } from '@/utils/navigation/detailServerScope'; import { readDetailServerId } from '@/lib/navigation/detailServerScope';
import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseContext } from '@/features/offline';
import { offlineActionPolicy } from '@/features/offline'; import { offlineActionPolicy } from '@/features/offline';
@@ -7,7 +7,7 @@ import { useArtistCoverRef } from '@/cover/useLibraryCoverRef';
import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes'; import { COVER_DENSE_GRID_MIN_CELL_CSS_PX } from '@/cover/layoutSizes';
import { useNavigateToArtist } from '@/features/artist/hooks/useNavigateToArtist'; import { useNavigateToArtist } from '@/features/artist/hooks/useNavigateToArtist';
import { coverServerScopeForServerId } from '@/cover/serverScope'; import { coverServerScopeForServerId } from '@/cover/serverScope';
import { appendServerQuery } from '@/utils/navigation/detailServerScope'; import { appendServerQuery } from '@/lib/navigation/detailServerScope';
interface Props { interface Props {
artist: SubsonicArtist; artist: SubsonicArtist;
@@ -10,7 +10,7 @@ import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { loadArtistFromLibraryIndex } from '@/features/offline'; 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 '@/lib/navigation/detailServerScope';
import { runLocalArtistLosslessBrowse } from '@/lib/library/browseTextSearch'; import { runLocalArtistLosslessBrowse } from '@/lib/library/browseTextSearch';
import { isLosslessSuffix } from '@/lib/library/losslessFormats'; import { isLosslessSuffix } from '@/lib/library/losslessFormats';
+1 -1
View File
@@ -38,7 +38,7 @@ import { useWarmGridCovers } from '@/cover/useWarmGridCovers';
import { VirtualCardGrid } from '@/ui/VirtualCardGrid'; import { VirtualCardGrid } from '@/ui/VirtualCardGrid';
import { LOSSLESS_MODE_QUERY } from '@/lib/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 '@/lib/navigation/detailServerScope';
export default function ArtistDetail() { export default function ArtistDetail() {
+2 -2
View File
@@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '@/test/helpers/renderWithProviders'; import { renderWithProviders } from '@/test/helpers/renderWithProviders';
import { resetAuthStore } from '@/test/helpers/storeReset'; import { resetAuthStore } from '@/test/helpers/storeReset';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { encodeServerMagicString } from '@/utils/server/serverMagicString'; import { encodeServerMagicString } from '@/lib/server/serverMagicString';
vi.mock('@/lib/api/subsonic', () => ({ vi.mock('@/lib/api/subsonic', () => ({
pingWithCredentialsForProfile: vi.fn(async () => ({ pingWithCredentialsForProfile: vi.fn(async () => ({
@@ -16,7 +16,7 @@ vi.mock('@/lib/api/subsonic', () => ({
scheduleInstantMixProbeForServer: vi.fn(), scheduleInstantMixProbeForServer: vi.fn(),
})); }));
vi.mock('@/utils/server/syncServerHttpContext', () => ({ vi.mock('@/lib/server/syncServerHttpContext', () => ({
syncServerHttpContextForProfile: vi.fn(async () => undefined), syncServerHttpContextForProfile: vi.fn(async () => undefined),
})); }));
+4 -4
View File
@@ -9,8 +9,8 @@ import {
DEFAULT_CUSTOM_HEADERS_APPLY_TO, DEFAULT_CUSTOM_HEADERS_APPLY_TO,
serverCustomHeadersFromForm, serverCustomHeadersFromForm,
validateCustomHeaders, validateCustomHeaders,
} from '@/utils/server/serverHttpHeaders'; } from '@/lib/server/serverHttpHeaders';
import { syncServerHttpContextForProfile } from '@/utils/server/syncServerHttpContext'; import { syncServerHttpContextForProfile } from '@/lib/server/syncServerHttpContext';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import i18n from '@/lib/i18n'; import i18n from '@/lib/i18n';
import CustomSelect from '@/ui/CustomSelect'; import CustomSelect from '@/ui/CustomSelect';
@@ -19,8 +19,8 @@ import {
DECODED_PASSWORD_VISUAL_MASK, DECODED_PASSWORD_VISUAL_MASK,
encodeServerMagicString, encodeServerMagicString,
type ServerMagicPayload, type ServerMagicPayload,
} from '@/utils/server/serverMagicString'; } from '@/lib/server/serverMagicString';
import { shortHostFromServerUrl, serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { shortHostFromServerUrl, serverListDisplayLabel } from '@/lib/server/serverDisplayName';
const PsysonicLogo = () => ( const PsysonicLogo = () => (
<img src="/logo-psysonic.png" width="64" height="64" alt="Psysonic" style={{ borderRadius: 18 }} /> <img src="/logo-psysonic.png" width="64" height="64" alt="Psysonic" style={{ borderRadius: 18 }} />
@@ -13,7 +13,7 @@ import { useThemeStore } from '@/store/themeStore';
import { useDragDrop } from '@/lib/dnd/DragDropContext'; import { useDragDrop } from '@/lib/dnd/DragDropContext';
import { useOrbitSongRowBehavior } from '@/features/orbit'; import { useOrbitSongRowBehavior } from '@/features/orbit';
import { songToTrack } from '@/lib/media/songToTrack'; import { songToTrack } from '@/lib/media/songToTrack';
import { appendServerQuery } from '@/utils/navigation/detailServerScope'; import { appendServerQuery } from '@/lib/navigation/detailServerScope';
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight'; import { useElementClientHeightById } from '@/lib/hooks/useResizeClientHeight';
import { SORTABLE_COLUMNS } from '@/features/favorites/hooks/useFavoritesSongFiltering'; import { SORTABLE_COLUMNS } from '@/features/favorites/hooks/useFavoritesSongFiltering';
+1 -1
View File
@@ -10,7 +10,7 @@ import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { fetchGenreCatalog, filterGenresWithContent } from '@/features/playback/utils/playback/genreBrowsePlayback'; import { fetchGenreCatalog, filterGenresWithContent } from '@/features/playback/utils/playback/genreBrowsePlayback';
import { libraryScopeForServer } from '@/lib/api/subsonicClient'; import { libraryScopeForServer } from '@/lib/api/subsonicClient';
import { peekGenreCatalogCache } from '@/lib/library/genreCatalogCountsCache'; import { peekGenreCatalogCache } from '@/lib/library/genreCatalogCountsCache';
import { resolveIndexKey } from '@/utils/server/serverIndexKey'; import { resolveIndexKey } from '@/lib/server/serverIndexKey';
import { genreColor } from '@/lib/library/genreColor'; import { genreColor } from '@/lib/library/genreColor';
const SCROLL_KEY = 'genres-scroll'; const SCROLL_KEY = 'genres-scroll';
@@ -35,7 +35,7 @@ import {
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { shuffleArray } from '@/lib/util/shuffleArray'; import { shuffleArray } from '@/lib/util/shuffleArray';
import { getMediaDir } from '@/lib/media/mediaDir'; import { getMediaDir } from '@/lib/media/mediaDir';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey, resolveIndexKey } from '@/lib/server/serverIndexKey';
import { reconcileAllLibraryTiersFromDisk } from '@/features/offline/utils/libraryTierReconcile'; import { reconcileAllLibraryTiersFromDisk } from '@/features/offline/utils/libraryTierReconcile';
import { import {
inferPinSourcesFromLibraryIndex, inferPinSourcesFromLibraryIndex,
+1 -1
View File
@@ -17,7 +17,7 @@ import {
pendingOfflinePinSongs, pendingOfflinePinSongs,
} from '@/features/offline/utils/offlineLibraryHelpers'; } from '@/features/offline/utils/offlineLibraryHelpers';
import { librarySqlServerId } from '@/api/coverCache'; import { librarySqlServerId } from '@/api/coverCache';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { isSmartPlaylistName } from '@/utils/componentHelpers/playlistDetailHelpers'; import { isSmartPlaylistName } from '@/utils/componentHelpers/playlistDetailHelpers';
import { import {
enqueueOfflinePin, enqueueOfflinePin,
@@ -12,7 +12,7 @@ import { cancelledDownloads, useOfflineJobStore } from '@/features/offline/store
import { useFavoritesOfflineSyncStore } from '@/features/offline/store/favoritesOfflineSyncStore'; import { useFavoritesOfflineSyncStore } from '@/features/offline/store/favoritesOfflineSyncStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { getMediaDir } from '@/lib/media/mediaDir'; import { getMediaDir } from '@/lib/media/mediaDir';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants'; import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants';
import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; import { isActiveServerReachable } from '@/utils/network/activeServerReachability';
import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse'; import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse';
@@ -6,8 +6,8 @@ import { useLocalPlaybackStore, type LocalPlaybackEntry, type PinSource } from '
import { localPlaybackEntryKey } from '@/store/localPlaybackKeys'; import { localPlaybackEntryKey } from '@/store/localPlaybackKeys';
import { importLegacyLocalPlayback } from '@/store/localPlaybackMigration'; import { importLegacyLocalPlayback } from '@/store/localPlaybackMigration';
import { getMediaDir } from '@/lib/media/mediaDir'; import { getMediaDir } from '@/lib/media/mediaDir';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { resolveIndexKey } from '@/utils/server/serverIndexKey'; import { resolveIndexKey } from '@/lib/server/serverIndexKey';
interface LegacyOfflineMigrationResult { interface LegacyOfflineMigrationResult {
trackId: string; trackId: string;
@@ -6,7 +6,7 @@ import { useAuthStore } from '@/store/authStore';
import type { LocalPlaybackEntry, PinSource } from '@/store/localPlaybackStore'; import type { LocalPlaybackEntry, PinSource } from '@/store/localPlaybackStore';
import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
import { getMediaDir } from '@/lib/media/mediaDir'; import { getMediaDir } from '@/lib/media/mediaDir';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { import {
entryBelongsToServer, entryBelongsToServer,
findLocalPlaybackEntry, findLocalPlaybackEntry,
@@ -8,10 +8,10 @@ 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 '@/lib/library/advancedSearchLocal'; import { resolveTrackCoverArtId, trackToSong } from '@/lib/library/advancedSearchLocal';
import { canonicalQueueServerKey, resolveIndexKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey, resolveIndexKey } from '@/lib/server/serverIndexKey';
import type { Track } from '@/lib/media/trackTypes'; import type { Track } from '@/lib/media/trackTypes';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
export interface OfflineLibraryCard { export interface OfflineLibraryCard {
serverIndexKey: string; serverIndexKey: string;
@@ -15,8 +15,8 @@ import {
isActiveServerReachable, isActiveServerReachable,
onActiveServerBecameReachable, onActiveServerBecameReachable,
} from '@/utils/network/activeServerReachability'; } from '@/utils/network/activeServerReachability';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { findLocalPlaybackEntry } from '@/store/localPlaybackResolve'; import { findLocalPlaybackEntry } from '@/store/localPlaybackResolve';
import { enqueueOfflinePin } from '@/features/offline/utils/offlinePinQueue'; import { enqueueOfflinePin } from '@/features/offline/utils/offlinePinQueue';
@@ -7,7 +7,7 @@ import { useOfflineStore } from '@/features/offline/store/offlineStore';
import { trackToSong } from '@/lib/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 '@/lib/server/serverLookup';
import { isOfflinePinComplete } from '@/features/offline/utils/offlineLibraryHelpers'; import { isOfflinePinComplete } from '@/features/offline/utils/offlineLibraryHelpers';
import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve'; import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve';
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useOrbitStore } from '@/features/orbit/store/orbitStore'; import { useOrbitStore } from '@/features/orbit/store/orbitStore';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { buildOrbitShareLink } from '@/features/orbit/utils/orbit'; import { buildOrbitShareLink } from '@/features/orbit/utils/orbit';
import { serverShareBaseUrl } from '@/utils/server/serverEndpoint'; import { serverShareBaseUrl } from '@/lib/server/serverEndpoint';
interface Props { interface Props {
anchorRef: React.RefObject<HTMLElement | null>; anchorRef: React.RefObject<HTMLElement | null>;
@@ -13,7 +13,7 @@ import {
import { randomOrbitSessionName } from '@/features/orbit/utils/orbitNames'; import { randomOrbitSessionName } from '@/features/orbit/utils/orbitNames';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { isLanUrl, serverShareBaseUrl } from '@/utils/server/serverEndpoint'; import { isLanUrl, serverShareBaseUrl } from '@/lib/server/serverEndpoint';
import { ORBIT_DEFAULT_MAX_USERS } from '@/features/orbit/api/orbit'; import { ORBIT_DEFAULT_MAX_USERS } from '@/features/orbit/api/orbit';
interface Props { onClose: () => void; } interface Props { onClose: () => void; }
@@ -13,7 +13,7 @@ import type { QueueItemRef } from '@/lib/media/trackTypes';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { resolveQueueTrack } from '@/features/playback/store/queueTrackView'; import { resolveQueueTrack } from '@/features/playback/store/queueTrackView';
import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore'; import { queueUndoRestoreAudioEngine } from '@/features/playback/store/queueUndoAudioRestore';
import { import {
@@ -7,7 +7,7 @@ vi.mock('@/lib/api/subsonicPlayQueue', () => ({
getPlayQueueForServer: (...args: unknown[]) => getPlayQueueForServerMock(...args), getPlayQueueForServer: (...args: unknown[]) => getPlayQueueForServerMock(...args),
})); }));
vi.mock('@/utils/server/serverLookup', () => ({ vi.mock('@/lib/server/serverLookup', () => ({
resolveServerIdForIndexKey: (id: string) => id, resolveServerIdForIndexKey: (id: string) => id,
})); }));
@@ -1,7 +1,7 @@
import { getPlayQueueForServer, type PlayQueueResult } from '@/lib/api/subsonicPlayQueue'; import { getPlayQueueForServer, type PlayQueueResult } from '@/lib/api/subsonicPlayQueue';
import { songToTrack } from '@/lib/media/songToTrack'; import { songToTrack } from '@/lib/media/songToTrack';
import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer'; import { bindQueueServerId } from '@/features/playback/utils/playback/playbackServer';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { toQueueItemRefs } from '@/features/playback/store/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver'; import { seedQueueResolver } from '@/features/playback/store/queueTrackResolver';
import type { Track } from '@/lib/media/trackTypes'; import type { Track } from '@/lib/media/trackTypes';
@@ -39,7 +39,7 @@ const hoisted = vi.hoisted(() => {
vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock })); vi.mock('@tauri-apps/api/core', () => ({ invoke: hoisted.invokeMock }));
vi.mock('@/lib/api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock })); vi.mock('@/lib/api/subsonicStreamUrl', () => ({ buildStreamUrl: hoisted.buildStreamUrlMock }));
vi.mock('@/utils/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock })); vi.mock('@/lib/server/redactSubsonicUrl', () => ({ redactSubsonicUrlForLog: hoisted.redactMock }));
vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } })); vi.mock('@/store/authStore', () => ({ useAuthStore: { getState: () => hoisted.auth } }));
vi.mock('@/features/playback/store/playerStore', () => ({ vi.mock('@/features/playback/store/playerStore', () => ({
usePlayerStore: { usePlayerStore: {
@@ -1,7 +1,7 @@
import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl'; import { buildStreamUrl } from '@/lib/api/subsonicStreamUrl';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { getPlaybackIndexKey } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackIndexKey } from '@/features/playback/utils/playback/playbackServer';
import { redactSubsonicUrlForLog } from '@/utils/server/redactSubsonicUrl'; import { redactSubsonicUrlForLog } from '@/lib/server/redactSubsonicUrl';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug'; import { emitNormalizationDebug } from '@/features/playback/store/normalizationDebug';
@@ -1,6 +1,6 @@
import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve';
import { resolvePlaybackUrl, type PlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl'; import { resolvePlaybackUrl, type PlaybackSourceKind } from '@/features/playback/utils/playback/resolvePlaybackUrl';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
/** /**
+1 -1
View File
@@ -7,7 +7,7 @@ import { emitPlaybackProgress } from '@/features/playback/store/playbackProgress
import type { QueueItemRef, Track } from '@/lib/media/trackTypes'; import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
import type { PlayerState } from '@/features/playback/store/playerStoreTypes'; import type { PlayerState } from '@/features/playback/store/playerStoreTypes';
import { toQueueItemRefs } from '@/features/playback/store/queueItemRef'; import { toQueueItemRefs } from '@/features/playback/store/queueItemRef';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
import { readInitialQueueVisibility } from '@/features/playback/store/queueVisibilityStorage'; import { readInitialQueueVisibility } from '@/features/playback/store/queueVisibilityStorage';
import { createNetworkLoveActions } from '@/features/playback/store/networkLoveActions'; import { createNetworkLoveActions } from '@/features/playback/store/networkLoveActions';
import { createMiscActions } from '@/features/playback/store/miscActions'; import { createMiscActions } from '@/features/playback/store/miscActions';
+1 -1
View File
@@ -1,6 +1,6 @@
import type { QueueItemRef, Track } from '@/lib/media/trackTypes'; import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
import { stampTrackServerId } from '@/lib/media/trackServerScope'; import { stampTrackServerId } from '@/lib/media/trackServerScope';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
/** /**
* Derive thin `QueueItemRef`s from a `Track[]` queue (thin-state). Per-item * Derive thin `QueueItemRef`s from a `Track[]` queue (thin-state). Per-item
+1 -1
View File
@@ -1,7 +1,7 @@
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef } from '@/lib/media/trackTypes'; import type { QueueItemRef } from '@/lib/media/trackTypes';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
import { resolveBatch } from './queueTrackResolver'; import { resolveBatch } from './queueTrackResolver';
/** /**
@@ -3,8 +3,8 @@ import { getSongForServer } from '@/lib/api/subsonicLibrary';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import type { QueueItemRef, Track } from '@/lib/media/trackTypes'; import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
import { songToTrack } from '@/lib/media/songToTrack'; import { songToTrack } from '@/lib/media/songToTrack';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
import { trackToSong } from '@/lib/library/advancedSearchLocal'; import { trackToSong } from '@/lib/library/advancedSearchLocal';
import { libraryIsReady } from '@/lib/library/libraryReady'; import { libraryIsReady } from '@/lib/library/libraryReady';
@@ -9,13 +9,13 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { switchActiveServer } from '@/utils/server/switchActiveServer'; import { switchActiveServer } from '@/utils/server/switchActiveServer';
import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity'; import { sameQueueTrackId } from '@/features/playback/utils/playback/queueIdentity';
import type { QueueItemRef, Track } from '@/lib/media/trackTypes'; import type { QueueItemRef, Track } from '@/lib/media/trackTypes';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { import {
canonicalQueueServerKey, canonicalQueueServerKey,
resolveIndexKey, resolveIndexKey,
serverIndexKeyForProfile, serverIndexKeyForProfile,
serverIndexKeyFromUrl, serverIndexKeyFromUrl,
} from '@/utils/server/serverIndexKey'; } from '@/lib/server/serverIndexKey';
import { import {
activeServerProfileId, activeServerProfileId,
isMultiServerQueue, isMultiServerQueue,
@@ -1,6 +1,6 @@
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve'; import { findLocalPlaybackUrl } from '@/store/localPlaybackResolve';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { getPlaybackCacheServerKey, getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackCacheServerKey, getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
/** Same resolution order as {@link resolvePlaybackUrl} — for UI hints only. */ /** Same resolution order as {@link resolvePlaybackUrl} — for UI hints only. */
@@ -1,6 +1,6 @@
import type { QueueItemRef } from '@/lib/media/trackTypes'; import type { QueueItemRef } from '@/lib/media/trackTypes';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
import { activeServerProfileId, profileIdFromQueueRef } from '@/lib/media/trackServerScope'; import { activeServerProfileId, profileIdFromQueueRef } from '@/lib/media/trackServerScope';
/** /**
+2 -2
View File
@@ -13,8 +13,8 @@ import { useTranslation } from 'react-i18next';
import { usePlaybackLibraryNavigate } from '@/features/playback/hooks/usePlaybackLibraryNavigate'; import { usePlaybackLibraryNavigate } from '@/features/playback/hooks/usePlaybackLibraryNavigate';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { encodeSharePayload } from '@/utils/share/shareLink'; import { encodeSharePayload } from '@/utils/share/shareLink';
import { serverShareBaseUrl } from '@/utils/server/serverEndpoint'; import { serverShareBaseUrl } from '@/lib/server/serverEndpoint';
import { copyTextToClipboard } from '@/utils/server/serverMagicString'; import { copyTextToClipboard } from '@/lib/server/serverMagicString';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { useThemeStore } from '@/store/themeStore'; import { useThemeStore } from '@/store/themeStore';
import { useLyricsStore } from '@/store/lyricsStore'; import { useLyricsStore } from '@/store/lyricsStore';
@@ -54,7 +54,7 @@ import {
resolveLiveSearchScopeGhost, resolveLiveSearchScopeGhost,
} from '@/features/search/components/liveSearchScope'; } from '@/features/search/components/liveSearchScope';
import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore'; import { useLiveSearchScopeStore } from '@/store/liveSearchScopeStore';
import { resolveIndexKey } from '@/utils/server/serverIndexKey'; import { resolveIndexKey } from '@/lib/server/serverIndexKey';
type LiveSearchSource = 'local' | 'network'; type LiveSearchSource = 'local' | 'network';
+1 -1
View File
@@ -11,7 +11,7 @@ import type { ServerProfile } from '@/store/authStoreTypes';
import { findServerIdForShareUrl } from '@/utils/share/shareLink'; import { findServerIdForShareUrl } from '@/utils/share/shareLink';
import { shareServerOriginLabel } from '@/utils/share/shareServerOriginLabel'; import { shareServerOriginLabel } from '@/utils/share/shareServerOriginLabel';
import { parseShareSearchText } from '@/utils/share/shareSearch'; import { parseShareSearchText } from '@/utils/share/shareSearch';
import { serverIndexKeyFromUrl } from '@/utils/server/serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
import { useShareSearchPreview } from '@/features/search/hooks/useShareSearchPreview'; import { useShareSearchPreview } from '@/features/search/hooks/useShareSearchPreview';
export function useShareSearch(query: string, onSuccess?: () => void) { export function useShareSearch(query: string, onSuccess?: () => void) {
@@ -35,8 +35,8 @@ import {
registerAdvancedSearchSessionProvider, registerAdvancedSearchSessionProvider,
resolveAdvancedSearchLeaveSnapshot, resolveAdvancedSearchLeaveSnapshot,
type AdvancedSearchLeaveSnapshot, type AdvancedSearchLeaveSnapshot,
} from '@/utils/navigation/advancedSearchScrollSnapshot'; } from '@/lib/navigation/advancedSearchScrollSnapshot';
import { restoreMainViewportScroll } from '@/utils/navigation/restoreMainViewportScroll'; import { restoreMainViewportScroll } from '@/lib/navigation/restoreMainViewportScroll';
import { import {
loadMoreLocalSongs, loadMoreLocalSongs,
runNetworkAdvancedTextSearch, runNetworkAdvancedTextSearch,
@@ -3,7 +3,7 @@ import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '@/test/helpers/renderWithProviders'; import { renderWithProviders } from '@/test/helpers/renderWithProviders';
import { AddServerForm } from '@/features/settings/components/AddServerForm'; import { AddServerForm } from '@/features/settings/components/AddServerForm';
import { encodeServerMagicString } from '@/utils/server/serverMagicString'; import { encodeServerMagicString } from '@/lib/server/serverMagicString';
// resolve_host_addresses Tauri command — hint-only, must not block save. // resolve_host_addresses Tauri command — hint-only, must not block save.
vi.mock('@/api/network', () => ({ vi.mock('@/api/network', () => ({
@@ -6,16 +6,16 @@ import {
DEFAULT_CUSTOM_HEADERS_APPLY_TO, DEFAULT_CUSTOM_HEADERS_APPLY_TO,
serverCustomHeadersFromForm, serverCustomHeadersFromForm,
validateCustomHeaders, validateCustomHeaders,
} from '@/utils/server/serverHttpHeaders'; } from '@/lib/server/serverHttpHeaders';
import { CustomHttpHeadersEditor } from '@/features/settings/components/CustomHttpHeadersEditor'; import { CustomHttpHeadersEditor } from '@/features/settings/components/CustomHttpHeadersEditor';
import { import {
decodeServerMagicString, decodeServerMagicString,
encodeServerMagicString, encodeServerMagicString,
DECODED_PASSWORD_VISUAL_MASK, DECODED_PASSWORD_VISUAL_MASK,
type ServerMagicPayload, type ServerMagicPayload,
} from '@/utils/server/serverMagicString'; } from '@/lib/server/serverMagicString';
import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; import { shortHostFromServerUrl } from '@/lib/server/serverDisplayName';
import { isLanUrl } from '@/utils/server/serverEndpoint'; import { isLanUrl } from '@/lib/server/serverEndpoint';
import { resolveHostAddresses } from '@/api/network'; import { resolveHostAddresses } from '@/api/network';
type FormState = { type FormState = {
@@ -21,8 +21,8 @@ import {
} from '@/api/analysis'; } from '@/api/analysis';
import { libraryGetTracksBatch, type LibraryTrackDto, type TrackRefDto } from '@/lib/api/library'; import { libraryGetTracksBatch, type LibraryTrackDto, type TrackRefDto } from '@/lib/api/library';
import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl'; import { buildStreamUrlForServer } from '@/lib/api/subsonicStreamUrl';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { import {
ANALYTICS_STRATEGIES, ANALYTICS_STRATEGIES,
@@ -14,8 +14,8 @@ import {
libraryCoverProgress, libraryCoverProgress,
} from '@/api/coverCache'; } from '@/api/coverCache';
import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache'; import { clearDiskSrcCacheForServer } from '@/cover/diskSrcCache';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/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';
@@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom';
import { open as openUrl } from '@tauri-apps/plugin-shell'; import { open as openUrl } from '@tauri-apps/plugin-shell';
import { AlertTriangle, CheckCircle2, Info, Lock, LogOut, Pencil, Plus, Power, Server, Sparkles, Wifi, WifiOff } from 'lucide-react'; import { AlertTriangle, CheckCircle2, Info, Lock, LogOut, Pencil, Plus, Power, Server, Sparkles, Wifi, WifiOff } from 'lucide-react';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity } from '@/utils/server/subsonicServerIdentity'; import { formatServerSoftware, isNavidromeAudiomuseSoftwareEligible, type InstantMixProbeResult, type SubsonicServerIdentity } from '@/lib/server/subsonicServerIdentity';
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';
@@ -16,24 +16,24 @@ import { pingWithCredentialsForProfile, scheduleInstantMixProbeForServer } from
import { import {
clearServerHttpContext, clearServerHttpContext,
syncServerHttpContextForProfile, syncServerHttpContextForProfile,
} from '@/utils/server/syncServerHttpContext'; } from '@/lib/server/syncServerHttpContext';
import { type ServerMagicPayload } from '@/utils/server/serverMagicString'; import { type ServerMagicPayload } from '@/lib/server/serverMagicString';
import { ensureConnectUrlResolved, invalidateReachableEndpointCache } from '@/utils/server/serverEndpoint'; import { ensureConnectUrlResolved, invalidateReachableEndpointCache } from '@/lib/server/serverEndpoint';
import { import {
verifySameServerEndpoints, verifySameServerEndpoints,
type VerifySameServerResult, type VerifySameServerResult,
} from '@/utils/server/serverFingerprint'; } from '@/lib/server/serverFingerprint';
import { import {
indexKeyRemapForUrlChange, indexKeyRemapForUrlChange,
runIndexKeyRemigration, runIndexKeyRemigration,
} from '@/utils/server/serverUrlRemigration'; } from '@/lib/server/serverUrlRemigration';
import { useConfirmModalStore } from '@/store/confirmModalStore'; import { useConfirmModalStore } from '@/store/confirmModalStore';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '@/serverCapabilities/catalog'; import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '@/serverCapabilities/catalog';
import { isFeatureActiveForServer, resolveFeatureForServer } from '@/serverCapabilities/storeView'; import { isFeatureActiveForServer, resolveFeatureForServer } from '@/serverCapabilities/storeView';
import type { ResolvedCapability } from '@/serverCapabilities/types'; import type { ResolvedCapability } from '@/serverCapabilities/types';
import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle } from '@/utils/server/serverDisplayName'; import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle } from '@/lib/server/serverDisplayName';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { switchActiveServer } from '@/utils/server/switchActiveServer'; import { switchActiveServer } from '@/utils/server/switchActiveServer';
import { AddServerForm } from '@/features/settings/components/AddServerForm'; import { AddServerForm } from '@/features/settings/components/AddServerForm';
import { ServerCapabilityHeaderBadge } from '@/features/settings/components/ServerCapabilityHeaderBadge'; import { ServerCapabilityHeaderBadge } from '@/features/settings/components/ServerCapabilityHeaderBadge';
@@ -7,8 +7,8 @@ import {
copyTextToClipboard, copyTextToClipboard,
encodeServerMagicString, encodeServerMagicString,
magicPayloadAddressFields, magicPayloadAddressFields,
} from '@/utils/server/serverMagicString'; } from '@/lib/server/serverMagicString';
import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; import { shortHostFromServerUrl } from '@/lib/server/serverDisplayName';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
export interface UserFormState { export interface UserFormState {
@@ -8,8 +8,8 @@ import {
copyTextToClipboard, copyTextToClipboard,
encodeServerMagicString, encodeServerMagicString,
magicPayloadAddressFields, magicPayloadAddressFields,
} from '@/utils/server/serverMagicString'; } from '@/lib/server/serverMagicString';
import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; import { shortHostFromServerUrl } from '@/lib/server/serverDisplayName';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
interface Props { interface Props {
@@ -12,8 +12,8 @@ import { showToast } from '@/lib/dom/toast';
import { import {
copyTextToClipboard, copyTextToClipboard,
encodeServerMagicString, encodeServerMagicString,
} from '@/utils/server/serverMagicString'; } from '@/lib/server/serverMagicString';
import { shortHostFromServerUrl } from '@/utils/server/serverDisplayName'; import { shortHostFromServerUrl } from '@/lib/server/serverDisplayName';
import type { UserFormState } from '@/features/settings/components/UserForm'; import type { UserFormState } from '@/features/settings/components/UserForm';
interface UseUserMgmtActionsArgs { interface UseUserMgmtActionsArgs {
+1 -1
View File
@@ -21,7 +21,7 @@ import { searchSettings, type SettingsSearchHit } from '@/features/settings/comp
import { type Tab, resolveTab } from '@/features/settings/components/settingsTabs'; import { type Tab, resolveTab } from '@/features/settings/components/settingsTabs';
import { UserManagementSection } from '@/features/settings/components/UserManagementSection'; import { UserManagementSection } from '@/features/settings/components/UserManagementSection';
import { ndLogin } from '@/lib/api/navidromeAdmin'; import { ndLogin } from '@/lib/api/navidromeAdmin';
import { type ServerMagicPayload } from '@/utils/server/serverMagicString'; import { type ServerMagicPayload } from '@/lib/server/serverMagicString';
export default function Settings() { export default function Settings() {
@@ -2,8 +2,8 @@ import { useAuthStore } from '@/store/authStore';
import { usePlayerStore } from '@/features/playback/store/playerStore'; import { usePlayerStore } from '@/features/playback/store/playerStore';
import { useConnectionStatus } from '@/hooks/useConnectionStatus'; import { useConnectionStatus } from '@/hooks/useConnectionStatus';
import { useNavidromeAdminRole } from '@/hooks/useNavidromeAdminRole'; import { useNavidromeAdminRole } from '@/hooks/useNavidromeAdminRole';
import { serverListDisplayLabel } from '@/utils/server/serverDisplayName'; import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import { findServerByIdOrIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { PerfProbeMetricSection } from '@/features/sidebar/components/perfProbe/PerfProbeMetricCard'; import { PerfProbeMetricSection } from '@/features/sidebar/components/perfProbe/PerfProbeMetricCard';
import PerfProbeDetailList from '@/features/sidebar/components/perfProbe/PerfProbeDetailList'; import PerfProbeDetailList from '@/features/sidebar/components/perfProbe/PerfProbeDetailList';
import PerfProbeStatusBadge, { type PerfProbeBadgeTone } from '@/features/sidebar/components/perfProbe/PerfProbeStatusBadge'; import PerfProbeStatusBadge, { type PerfProbeBadgeTone } from '@/features/sidebar/components/perfProbe/PerfProbeStatusBadge';
@@ -1,6 +1,6 @@
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { NavidromeAdminRole } from '@/hooks/useNavidromeAdminRole'; import type { NavidromeAdminRole } from '@/hooks/useNavidromeAdminRole';
import { isNavidromeServer, formatServerSoftware } from '@/utils/server/subsonicServerIdentity'; import { isNavidromeServer, formatServerSoftware } from '@/lib/server/subsonicServerIdentity';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS } from '@/serverCapabilities/catalog'; import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS } from '@/serverCapabilities/catalog';
import { import {
isFeatureActiveForServer, isFeatureActiveForServer,
+1 -1
View File
@@ -5,7 +5,7 @@ import { useAuthStore } from '@/store/authStore';
import { import {
invalidateReachableEndpointCache, invalidateReachableEndpointCache,
type PickReachableResult, type PickReachableResult,
} from '@/utils/server/serverEndpoint'; } from '@/lib/server/serverEndpoint';
vi.mock('@/lib/api/subsonic', () => ({ vi.mock('@/lib/api/subsonic', () => ({
pingWithCredentials: vi.fn(), pingWithCredentials: vi.fn(),
+2 -2
View File
@@ -1,13 +1,13 @@
import { useState, useEffect, useCallback, useRef, useMemo, useSyncExternalStore } from 'react'; import { useState, useEffect, useCallback, useRef, useMemo, useSyncExternalStore } from 'react';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { scheduleInstantMixProbeForServer } from '@/lib/api/subsonic'; import { scheduleInstantMixProbeForServer } from '@/lib/api/subsonic';
import { serverListDisplayLabel } from '../utils/server/serverDisplayName'; import { serverListDisplayLabel } from '@/lib/server/serverDisplayName';
import { import {
ensureConnectUrlResolved, ensureConnectUrlResolved,
invalidateReachableEndpointCache, invalidateReachableEndpointCache,
isLanUrl, isLanUrl,
type ServerEndpointKind, type ServerEndpointKind,
} from '../utils/server/serverEndpoint'; } from '@/lib/server/serverEndpoint';
import { import {
getConnectionStatus, getConnectionStatus,
setActiveServerReachable, setActiveServerReachable,
+1 -1
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ndLogin } from '@/lib/api/navidromeAdmin'; import { ndLogin } from '@/lib/api/navidromeAdmin';
import { useAuthStore } from '../store/authStore'; import { useAuthStore } from '../store/authStore';
import { isNavidromeServer } from '../utils/server/subsonicServerIdentity'; import { isNavidromeServer } from '@/lib/server/subsonicServerIdentity';
export type NavidromeAdminRole = 'idle' | 'checking' | 'admin' | 'user' | 'na' | 'error'; export type NavidromeAdminRole = 'idle' | 'checking' | 'admin' | 'user' | 'na' | 'error';
+1 -1
View File
@@ -4,7 +4,7 @@ import { usePlayerStore } from '@/features/playback/store/playerStore';
import { collectPlaybackMiddlePriorityTrackIds } from '@/features/playback/store/loudnessBackfillWindow'; import { collectPlaybackMiddlePriorityTrackIds } from '@/features/playback/store/loudnessBackfillWindow';
import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer'; import { getPlaybackServerId } from '@/features/playback/utils/playback/playbackServer';
import { analysisSetPlaybackPriorityHints } from '../api/analysis'; import { analysisSetPlaybackPriorityHints } from '../api/analysis';
import { serverIndexKeyFromUrl } from '../utils/server/serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
import { hotCacheFrontendDebug } from './helpers'; import { hotCacheFrontendDebug } from './helpers';
let analysisPruneTimer: ReturnType<typeof setTimeout> | null = null; let analysisPruneTimer: ReturnType<typeof setTimeout> | null = null;
+2 -2
View File
@@ -9,8 +9,8 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { listen, type UnlistenFn } from '@tauri-apps/api/event'; import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { serverIndexKeyFromUrl } from '@/utils/server/serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
// ── DTO mirrors (camelCase, matching the Rust `#[serde(rename_all = "camelCase")]`) ─ // ── DTO mirrors (camelCase, matching the Rust `#[serde(rename_all = "camelCase")]`) ─
+1 -1
View File
@@ -7,7 +7,7 @@ vi.mock('@/lib/api/subsonicOpenSubsonic', () => ({
import { fetchOpenSubsonicExtensionsWithCredentials } from '@/lib/api/subsonicOpenSubsonic'; import { fetchOpenSubsonicExtensionsWithCredentials } from '@/lib/api/subsonicOpenSubsonic';
import { scheduleInstantMixProbeForServer } from '@/lib/api/subsonic'; import { scheduleInstantMixProbeForServer } from '@/lib/api/subsonic';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { SubsonicServerIdentity } from '@/utils/server/subsonicServerIdentity'; import type { SubsonicServerIdentity } from '@/lib/server/subsonicServerIdentity';
const fetchMock = vi.mocked(fetchOpenSubsonicExtensionsWithCredentials); const fetchMock = vi.mocked(fetchOpenSubsonicExtensionsWithCredentials);
const SID = 'srv-probe'; const SID = 'srv-probe';
+3 -3
View File
@@ -2,12 +2,12 @@ import axios from 'axios';
import md5 from 'md5'; import md5 from 'md5';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { ServerProfile } from '@/store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { headersForServerRequest } from '@/utils/server/serverHttpHeaders'; import { headersForServerRequest } from '@/lib/server/serverHttpHeaders';
import { findServerByIdOrIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { import {
type InstantMixProbeResult, type InstantMixProbeResult,
type SubsonicServerIdentity, type SubsonicServerIdentity,
} from '@/utils/server/subsonicServerIdentity'; } from '@/lib/server/subsonicServerIdentity';
import { fetchOpenSubsonicExtensionsWithCredentials } from '@/lib/api/subsonicOpenSubsonic'; import { fetchOpenSubsonicExtensionsWithCredentials } from '@/lib/api/subsonicOpenSubsonic';
import { buildCapabilityContext } from '@/serverCapabilities/context'; import { buildCapabilityContext } from '@/serverCapabilities/context';
import { import {
+3 -3
View File
@@ -3,9 +3,9 @@ import md5 from 'md5';
import { version } from '@/../package.json'; import { version } from '@/../package.json';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { ServerProfile } from '@/store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { connectBaseUrlForServer } from '@/utils/server/serverEndpoint'; import { connectBaseUrlForServer } from '@/lib/server/serverEndpoint';
import { headersForServerRequest } from '@/utils/server/serverHttpHeaders'; import { headersForServerRequest } from '@/lib/server/serverHttpHeaders';
import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey, resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
export const SUBSONIC_CLIENT = `psysonic/${version}`; export const SUBSONIC_CLIENT = `psysonic/${version}`;
+2 -2
View File
@@ -3,8 +3,8 @@ import { coverStorageKeyFromRef } from '@/cover/storageKeys';
import { coverEntryToRef, resolveAlbumCoverEntry } from '@/cover/resolveEntry'; import { coverEntryToRef, resolveAlbumCoverEntry } from '@/cover/resolveEntry';
import type { CoverArtTier } from '@/cover/types'; import type { CoverArtTier } from '@/cover/types';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { connectBaseUrlForServer } from '@/utils/server/serverEndpoint'; import { connectBaseUrlForServer } from '@/lib/server/serverEndpoint';
import { findServerByIdOrIndexKey } from '@/utils/server/serverLookup'; import { findServerByIdOrIndexKey } from '@/lib/server/serverLookup';
import { restBaseFromUrl, SUBSONIC_CLIENT, secureRandomSalt } from '@/lib/api/subsonicClient'; import { restBaseFromUrl, SUBSONIC_CLIENT, secureRandomSalt } from '@/lib/api/subsonicClient';
function coverArtQueryParams(username: string, password: string, id: string, size: number): URLSearchParams { function coverArtQueryParams(username: string, password: string, id: string, size: number): URLSearchParams {
+1 -1
View File
@@ -1,4 +1,4 @@
import type { SubsonicServerIdentity } from '@/utils/server/subsonicServerIdentity'; import type { SubsonicServerIdentity } from '@/lib/server/subsonicServerIdentity';
/** OpenSubsonic `ItemGenre` on songs/albums (atomic genres from the server). */ /** OpenSubsonic `ItemGenre` on songs/albums (atomic genres from the server). */
export interface SubsonicItemGenre { export interface SubsonicItemGenre {
+1 -1
View File
@@ -1,5 +1,5 @@
import type { SubsonicGenre } from '@/lib/api/subsonicTypes'; import type { SubsonicGenre } from '@/lib/api/subsonicTypes';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/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;
@@ -7,7 +7,7 @@ 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 '@/lib/library/analysisStrategy'; import { DEFAULT_ADVANCED_PARALLELISM } from '@/lib/library/analysisStrategy';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
const DISABLED_CONFIGURE = { const DISABLED_CONFIGURE = {
enabled: false, enabled: false,
+1 -1
View File
@@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { showToast } from '@/lib/dom/toast'; import { showToast } from '@/lib/dom/toast';
import { resolveIndexKey, serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { import {
libraryGetStatus, libraryGetStatus,
librarySyncCancel, librarySyncCancel,
+3 -3
View File
@@ -6,9 +6,9 @@ import { enqueueLibrarySync, queueInitialSyncIfNeeded } from './librarySyncQueue
import type { ServerProfile } from '@/store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { useLibraryIndexStore } from '@/store/libraryIndexStore'; import { useLibraryIndexStore } from '@/store/libraryIndexStore';
import { ensureConnectUrlResolved } from '@/utils/server/serverEndpoint'; import { ensureConnectUrlResolved } from '@/lib/server/serverEndpoint';
import { serverIndexKeyForProfile } from '@/utils/server/serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { syncServerHttpContextForProfile } from '@/utils/server/syncServerHttpContext'; import { syncServerHttpContextForProfile } from '@/lib/server/syncServerHttpContext';
import { libraryDevEnabled, logLibraryStatus, logLibrarySync, timed } from './libraryDevLog'; import { libraryDevEnabled, logLibraryStatus, logLibrarySync, timed } from './libraryDevLog';
export type BindServerResult = 'bound' | 'offline' | 'error'; export type BindServerResult = 'bound' | 'offline' | 'error';
+2 -2
View File
@@ -1,7 +1,7 @@
import type { Track, QueueItemRef } from '@/lib/media/trackTypes'; import type { Track, QueueItemRef } from '@/lib/media/trackTypes';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { canonicalQueueServerKey } from '@/utils/server/serverIndexKey'; import { canonicalQueueServerKey } from '@/lib/server/serverIndexKey';
import { resolveServerIdForIndexKey } from '@/utils/server/serverLookup'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup';
/** /**
* Pure server-scope helpers for the shared media model: stamp the owning server * Pure server-scope helpers for the shared media model: stamp the owning server
@@ -1,6 +1,6 @@
// @vitest-environment jsdom // @vitest-environment jsdom
import { afterEach, describe, expect, it } from 'vitest'; import { afterEach, describe, expect, it } from 'vitest';
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { import {
clearAdvancedSearchLeaveSnapshots, clearAdvancedSearchLeaveSnapshots,
peekPersistedAdvancedSearchLeaveSnapshot, peekPersistedAdvancedSearchLeaveSnapshot,
@@ -9,8 +9,8 @@ import {
registerAdvancedSearchSessionProvider, registerAdvancedSearchSessionProvider,
resolveAdvancedSearchLeaveSnapshot, resolveAdvancedSearchLeaveSnapshot,
saveAdvancedSearchLeaveSnapshot, saveAdvancedSearchLeaveSnapshot,
} from './advancedSearchScrollSnapshot'; } from '@/lib/navigation/advancedSearchScrollSnapshot';
import { useAdvancedSearchSessionStore } from '../../store/advancedSearchSessionStore'; import { useAdvancedSearchSessionStore } from '@/store/advancedSearchSessionStore';
describe('advancedSearchScrollSnapshot', () => { describe('advancedSearchScrollSnapshot', () => {
afterEach(() => { afterEach(() => {
@@ -1,8 +1,8 @@
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
import { import {
useAdvancedSearchSessionStore, useAdvancedSearchSessionStore,
type AdvancedSearchSessionStash, type AdvancedSearchSessionStash,
} from '../../store/advancedSearchSessionStore'; } from '@/store/advancedSearchSessionStore';
export type AdvancedSearchLeaveSnapshot = { export type AdvancedSearchLeaveSnapshot = {
scrollTop: number; scrollTop: number;
@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
import { appendServerQuery, readDetailServerId } from './detailServerScope'; import { appendServerQuery, readDetailServerId } from '@/lib/navigation/detailServerScope';
describe('detailServerScope', () => { describe('detailServerScope', () => {
beforeEach(() => { beforeEach(() => {
@@ -1,4 +1,4 @@
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
/** Resolve `?server=` on album/artist detail routes; falls back when absent or unknown. */ /** Resolve `?server=` on album/artist detail routes; falls back when absent or unknown. */
export function readDetailServerId( export function readDetailServerId(
@@ -1,4 +1,4 @@
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../../constants/appScroll'; import { APP_MAIN_SCROLL_VIEWPORT_ID } from '@/constants/appScroll';
const SAFETY_TIMEOUT_MS = 3000; const SAFETY_TIMEOUT_MS = 3000;
@@ -1,4 +1,4 @@
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
/** Normalized Subsonic root URL for a server profile (same shape as `getBaseUrl`). */ /** Normalized Subsonic root URL for a server profile (same shape as `getBaseUrl`). */
export function serverProfileBaseUrl(server: Pick<ServerProfile, 'url'>): string { export function serverProfileBaseUrl(server: Pick<ServerProfile, 'url'>): string {
@@ -1,6 +1,6 @@
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle, shortHostFromServerUrl } from './serverDisplayName'; import { serverIdentityLabel, serverListDisplayLabel, serverSettingsEntryTitle, shortHostFromServerUrl } from '@/lib/server/serverDisplayName';
function srv(p: Partial<ServerProfile> & Pick<ServerProfile, 'id'>): ServerProfile { function srv(p: Partial<ServerProfile> & Pick<ServerProfile, 'id'>): ServerProfile {
return { return {
@@ -1,4 +1,4 @@
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
/** Host (+ port) from a server base URL, e.g. `https://music.one.com/foo` → `music.one.com`. */ /** Host (+ port) from a server base URL, e.g. `https://music.one.com/foo` → `music.one.com`. */
export function shortHostFromServerUrl(urlRaw?: string | null): string { export function shortHostFromServerUrl(urlRaw?: string | null): string {
const t = typeof urlRaw === 'string' ? urlRaw.trim() : ''; const t = typeof urlRaw === 'string' ? urlRaw.trim() : '';
@@ -17,8 +17,8 @@ import {
serverAddressEndpoints, serverAddressEndpoints,
serverShareBaseUrl, serverShareBaseUrl,
subscribeConnectCache, subscribeConnectCache,
} from './serverEndpoint'; } from '@/lib/server/serverEndpoint';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
function makeProfile(overrides: Partial<ServerProfile> = {}): ServerProfile { function makeProfile(overrides: Partial<ServerProfile> = {}): ServerProfile {
return { return {
@@ -1,7 +1,7 @@
import { pingWithCredentialsForProfile } from '@/lib/api/subsonic'; import { pingWithCredentialsForProfile } from '@/lib/api/subsonic';
import type { PingWithCredentialsResult } from '@/lib/api/subsonicTypes'; import type { PingWithCredentialsResult } from '@/lib/api/subsonicTypes';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { serverProfileBaseUrl } from './serverBaseUrl'; import { serverProfileBaseUrl } from '@/lib/server/serverBaseUrl';
export type ServerEndpointKind = 'local' | 'public'; export type ServerEndpointKind = 'local' | 'public';
@@ -16,7 +16,7 @@ import {
fetchServerFingerprint, fetchServerFingerprint,
verifySameServerEndpoints, verifySameServerEndpoints,
type ServerFingerprint, type ServerFingerprint,
} from './serverFingerprint'; } from '@/lib/server/serverFingerprint';
function makeFingerprint(overrides: Partial<ServerFingerprint> = {}): ServerFingerprint { function makeFingerprint(overrides: Partial<ServerFingerprint> = {}): ServerFingerprint {
return { return {
@@ -22,9 +22,9 @@ import {
SUBSONIC_CLIENT, SUBSONIC_CLIENT,
type ServerHttpHeaderProfile, type ServerHttpHeaderProfile,
} from '@/lib/api/subsonicClient'; } from '@/lib/api/subsonicClient';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { allNormalizedAddresses } from './serverEndpoint'; import { allNormalizedAddresses } from '@/lib/server/serverEndpoint';
import { headersForServerRequest } from './serverHttpHeaders'; import { headersForServerRequest } from '@/lib/server/serverHttpHeaders';
export type ServerFingerprint = { export type ServerFingerprint = {
ping: { ping: {
@@ -4,7 +4,7 @@ import {
requestBaseUrlFromHttpUrl, requestBaseUrlFromHttpUrl,
serverCustomHeadersFromForm, serverCustomHeadersFromForm,
validateCustomHeaders, validateCustomHeaders,
} from './serverHttpHeaders'; } from '@/lib/server/serverHttpHeaders';
describe('requestBaseUrlFromHttpUrl', () => { describe('requestBaseUrlFromHttpUrl', () => {
it('strips /rest/ path and query from stream URLs', () => { it('strips /rest/ path and query from stream URLs', () => {
@@ -4,9 +4,9 @@ import type {
CustomHeadersFieldError, CustomHeadersFieldError,
CustomHeadersValidationResult, CustomHeadersValidationResult,
ServerProfile, ServerProfile,
} from '../../store/authStoreTypes'; } from '@/store/authStoreTypes';
import { serverIndexKeyForProfile } from './serverIndexKey'; import { serverIndexKeyForProfile } from '@/lib/server/serverIndexKey';
import { normalizeServerBaseUrl, serverAddressEndpoints, type ServerEndpointKind } from './serverEndpoint'; import { normalizeServerBaseUrl, serverAddressEndpoints, type ServerEndpointKind } from '@/lib/server/serverEndpoint';
export const DEFAULT_CUSTOM_HEADERS_APPLY_TO: CustomHeadersApplyTo = 'public'; export const DEFAULT_CUSTOM_HEADERS_APPLY_TO: CustomHeadersApplyTo = 'public';
@@ -1,6 +1,6 @@
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
import { serverProfileBaseUrl } from './serverBaseUrl'; import { serverProfileBaseUrl } from '@/lib/server/serverBaseUrl';
/** Stable index key derived from a server URL (host + optional path, no scheme). */ /** Stable index key derived from a server URL (host + optional path, no scheme). */
export function serverIndexKeyFromUrl(urlRaw: string): string { export function serverIndexKeyFromUrl(urlRaw: string): string {
@@ -1,5 +1,5 @@
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
import { rewriteFrontendStoreKeys } from './rewriteFrontendStoreKeys'; import { rewriteFrontendStoreKeys } from '@/utils/server/rewriteFrontendStoreKeys';
/** /**
* Legacy compatibility shim. The migration lifecycle now runs through * Legacy compatibility shim. The migration lifecycle now runs through
@@ -1,6 +1,6 @@
import { useAuthStore } from '../../store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { serverIndexKeyForProfile, serverIndexKeyFromUrl } from './serverIndexKey'; import { serverIndexKeyForProfile, serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
export function findServerByIdOrIndexKey(serverIdOrKey: string): ServerProfile | undefined { export function findServerByIdOrIndexKey(serverIdOrKey: string): ServerProfile | undefined {
const servers = useAuthStore.getState().servers; const servers = useAuthStore.getState().servers;
@@ -6,7 +6,7 @@ import {
decodeServerMagicString, decodeServerMagicString,
decodeServerMagicStringFromText, decodeServerMagicStringFromText,
encodeServerMagicString, encodeServerMagicString,
} from './serverMagicString'; } from '@/lib/server/serverMagicString';
describe('DECODED_PASSWORD_VISUAL_MASK', () => { describe('DECODED_PASSWORD_VISUAL_MASK', () => {
it('has fixed length independent of real passwords', () => { it('has fixed length independent of real passwords', () => {
@@ -1,5 +1,5 @@
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { normalizeServerBaseUrl, serverShareBaseUrl } from './serverEndpoint'; import { normalizeServerBaseUrl, serverShareBaseUrl } from '@/lib/server/serverEndpoint';
/** /**
* Prefix for server invite strings (Subsonic credentials). Same family as library * Prefix for server invite strings (Subsonic credentials). Same family as library
@@ -11,7 +11,7 @@ import { invoke } from '@tauri-apps/api/core';
import { import {
indexKeyRemapForUrlChange, indexKeyRemapForUrlChange,
runIndexKeyRemigration, runIndexKeyRemigration,
} from './serverUrlRemigration'; } from '@/lib/server/serverUrlRemigration';
function inspectStub(needs = true) { function inspectStub(needs = true) {
return { return {
@@ -28,15 +28,15 @@
*/ */
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import type { ServerProfile } from '../../store/authStoreTypes'; import type { ServerProfile } from '@/store/authStoreTypes';
import { import {
migrationInspect, migrationInspect,
migrationRun, migrationRun,
type MigrationInspectReport, type MigrationInspectReport,
type MigrationRunResult, type MigrationRunResult,
} from '../../api/migration'; } from '@/api/migration';
import { rewriteFrontendStoreKeysForRemap } from './rewriteFrontendStoreKeys'; import { rewriteFrontendStoreKeysForRemap } from '@/utils/server/rewriteFrontendStoreKeys';
import { serverIndexKeyFromUrl } from './serverIndexKey'; import { serverIndexKeyFromUrl } from '@/lib/server/serverIndexKey';
export type IndexKeyRemap = { oldKey: string; newKey: string }; export type IndexKeyRemap = { oldKey: string; newKey: string };
@@ -4,7 +4,7 @@ import {
isNavidromeAudiomuseSoftwareEligible, isNavidromeAudiomuseSoftwareEligible,
isNavidromeServer, isNavidromeServer,
parseLeadingSemver, parseLeadingSemver,
} from './subsonicServerIdentity'; } from '@/lib/server/subsonicServerIdentity';
describe('parseLeadingSemver', () => { describe('parseLeadingSemver', () => {
it('parses Navidrome-style version strings', () => { it('parses Navidrome-style version strings', () => {
@@ -4,7 +4,7 @@ const invokeMock = vi.hoisted(() => vi.fn(async () => undefined));
vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock })); vi.mock('@tauri-apps/api/core', () => ({ invoke: invokeMock }));
import { syncServerHttpContextForProfile } from './syncServerHttpContext'; import { syncServerHttpContextForProfile } from '@/lib/server/syncServerHttpContext';
const server = { const server = {
id: 'app-uuid-1', id: 'app-uuid-1',

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