diff --git a/src/features/album/hooks/useAlbumDetailData.ts b/src/features/album/hooks/useAlbumDetailData.ts index 0146182d..1b8046a5 100644 --- a/src/features/album/hooks/useAlbumDetailData.ts +++ b/src/features/album/hooks/useAlbumDetailData.ts @@ -21,7 +21,7 @@ import { libraryIsReady } from '@/lib/library/libraryReady'; import { shouldAttemptSubsonicForActiveServer, shouldAttemptSubsonicForServer, -} from '@/utils/network/subsonicNetworkGuard'; +} from '@/lib/network/subsonicNetworkGuard'; type AlbumPayload = ResolvedAlbum; diff --git a/src/features/album/pages/AlbumDetail.tsx b/src/features/album/pages/AlbumDetail.tsx index 96aa9aed..894ebf01 100644 --- a/src/features/album/pages/AlbumDetail.tsx +++ b/src/features/album/pages/AlbumDetail.tsx @@ -21,7 +21,7 @@ import { useOfflineJobStore } from '@/features/offline'; import { isOfflinePinComplete } from '@/features/offline'; import { dequeueOfflinePin } from '@/features/offline'; import { reconcileLibraryTierForAlbum } from '@/features/offline'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { join } from '@tauri-apps/api/path'; import { useZipDownloadStore } from '@/features/offline'; import AlbumCard from '@/features/album/components/AlbumCard'; diff --git a/src/features/favorites/hooks/useFavoritesData.ts b/src/features/favorites/hooks/useFavoritesData.ts index d2572937..359d11bc 100644 --- a/src/features/favorites/hooks/useFavoritesData.ts +++ b/src/features/favorites/hooks/useFavoritesData.ts @@ -8,7 +8,7 @@ import { useAuthStore } from '@/store/authStore'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import type { TopFavoriteArtist } from '@/features/favorites/components/TopFavoriteArtists'; import { useConnectionStatus } from '@/hooks/useConnectionStatus'; -import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; import { useOfflineBrowseContext } from '@/features/offline'; import { useOfflineBrowseReloadToken } from '@/features/offline'; import { diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts index a37471f9..d0e92293 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.test.ts @@ -16,11 +16,11 @@ import type { SubsonicArtistInfo, SubsonicSong, SubsonicAlbum, SubsonicArtist } vi.mock('@/lib/api/subsonicArtists'); vi.mock('@/lib/api/subsonicLibrary'); vi.mock('@/api/bandsintown'); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForServer: vi.fn(() => true), })); -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { getArtistForServer, getArtistInfoForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists'; import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary'; import { fetchBandsintownEvents } from '@/api/bandsintown'; diff --git a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts index 952212f1..492d8b60 100644 --- a/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts +++ b/src/features/nowPlaying/hooks/useNowPlayingFetchers.ts @@ -6,7 +6,7 @@ import { fetchBandsintownEvents, type BandsintownEvent } from '@/api/bandsintown import type { ArtistStats, TrackStats } from '@/music-network'; import { getMusicNetworkRuntimeOrNull } from '@/music-network'; import { makeCache } from '@/lib/cache/nowPlayingCache'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { useConnectionStatus } from '@/hooks/useConnectionStatus'; // Module-level TTL caches (shared across mounts) diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts index 39f3031e..6446d8ed 100644 --- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts +++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.test.ts @@ -13,10 +13,10 @@ import * as subsonicArtists from '@/lib/api/subsonicArtists'; import * as subsonicLibrary from '@/lib/api/subsonicLibrary'; // Network reachability is decided by the guard; mock it so we can test both arms. -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForServer: vi.fn(() => true), })); -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { resolveNpAlbum, resolveNpDiscography, diff --git a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts index bbb6d4bf..1176ec11 100644 --- a/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts +++ b/src/features/nowPlaying/utils/nowPlayingMetadataResolve.ts @@ -20,7 +20,7 @@ import { libraryGetTrack, libraryGetTracksByAlbum } from '@/lib/api/library'; import { getArtistForServer, getTopSongsForServer } from '@/lib/api/subsonicArtists'; import { getAlbumForServer, getSongForServer } from '@/lib/api/subsonicLibrary'; import type { SubsonicAlbum, SubsonicSong } from '@/lib/api/subsonicTypes'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { loadAlbumFromLibraryIndex, loadArtistFromLibraryIndex } from '@/features/offline'; import { trackToSong } from '@/lib/library/advancedSearchLocal'; import { libraryIsReady } from '@/lib/library/libraryReady'; diff --git a/src/features/offline/index.ts b/src/features/offline/index.ts index f647af7a..00d109af 100644 --- a/src/features/offline/index.ts +++ b/src/features/offline/index.ts @@ -19,7 +19,7 @@ export * from './hooks/useOfflineBrowseContext'; export * from './hooks/useOfflineBrowseReloadToken'; export * from './hooks/useOfflineLibraryFilterSuspend'; export * from './hooks/useZipDownloadBridge'; -export * from './store/devOfflineBrowseStore'; +export * from '@/store/devOfflineBrowseStore'; export * from './store/downloadModalStore'; export * from './store/favoritesOfflineSyncStore'; export * from './store/offlineJobStore'; diff --git a/src/features/offline/utils/favoritesOfflineBrowse.test.ts b/src/features/offline/utils/favoritesOfflineBrowse.test.ts index 74f33bc7..d5ce1805 100644 --- a/src/features/offline/utils/favoritesOfflineBrowse.test.ts +++ b/src/features/offline/utils/favoritesOfflineBrowse.test.ts @@ -19,10 +19,10 @@ import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaReso const isActiveServerReachableMock = vi.fn(() => true); const shouldAttemptSubsonicForServerMock = vi.fn((_serverId: string, _trackId?: string) => true); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => isActiveServerReachableMock(), })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) => shouldAttemptSubsonicForServerMock(serverId, trackId), })); diff --git a/src/features/offline/utils/favoritesOfflineSync.test.ts b/src/features/offline/utils/favoritesOfflineSync.test.ts index 298f5dbf..0d315010 100644 --- a/src/features/offline/utils/favoritesOfflineSync.test.ts +++ b/src/features/offline/utils/favoritesOfflineSync.test.ts @@ -16,7 +16,7 @@ const getStarredForServerMock = vi.fn(async (_serverId: string) => ({ const isActiveServerReachableMock = vi.fn(() => true); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => isActiveServerReachableMock(), })); diff --git a/src/features/offline/utils/favoritesOfflineSync.ts b/src/features/offline/utils/favoritesOfflineSync.ts index ca0223b9..23c7de7f 100644 --- a/src/features/offline/utils/favoritesOfflineSync.ts +++ b/src/features/offline/utils/favoritesOfflineSync.ts @@ -14,7 +14,7 @@ import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { getMediaDir } from '@/lib/media/mediaDir'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey'; import { FAVORITES_OFFLINE_JOB_ID } from '@/features/offline/utils/favoritesOfflineConstants'; -import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; import { favoritesServerIds } from '@/features/offline/utils/favoritesOfflineBrowse'; import { loadAlbumFromLibraryIndex } from '@/features/offline/utils/offlineLibraryIndexLoad'; import { diff --git a/src/features/offline/utils/offlineBrowseMode.test.ts b/src/features/offline/utils/offlineBrowseMode.test.ts index 4700ba88..decd8040 100644 --- a/src/features/offline/utils/offlineBrowseMode.test.ts +++ b/src/features/offline/utils/offlineBrowseMode.test.ts @@ -1,7 +1,7 @@ import { renderHook, act } from '@testing-library/react'; import { describe, expect, it, beforeEach } from 'vitest'; -import { useDevOfflineBrowseStore } from '@/features/offline/store/devOfflineBrowseStore'; -import { resetActiveServerConnectionSnapshot } from '@/utils/network/activeServerReachability'; +import { useDevOfflineBrowseStore } from '@/store/devOfflineBrowseStore'; +import { resetActiveServerConnectionSnapshot } from '@/lib/network/activeServerReachability'; import { useOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode'; describe('useOfflineBrowseActive', () => { diff --git a/src/features/offline/utils/offlineBrowseMode.ts b/src/features/offline/utils/offlineBrowseMode.ts index babe822a..a784b313 100644 --- a/src/features/offline/utils/offlineBrowseMode.ts +++ b/src/features/offline/utils/offlineBrowseMode.ts @@ -1,9 +1,9 @@ import { isDevOfflineBrowseForced, useDevOfflineBrowseStore, -} from '@/features/offline/store/devOfflineBrowseStore'; +} from '@/store/devOfflineBrowseStore'; import { useConnectionStatus } from '@/hooks/useConnectionStatus'; -import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; /** True when browse/detail pages should use local-bytes-only data sources. */ export function isOfflineBrowseActive(): boolean { diff --git a/src/features/offline/utils/offlineMediaResolve.test.ts b/src/features/offline/utils/offlineMediaResolve.test.ts index 10841ae8..beeda330 100644 --- a/src/features/offline/utils/offlineMediaResolve.test.ts +++ b/src/features/offline/utils/offlineMediaResolve.test.ts @@ -44,7 +44,7 @@ vi.mock('@/features/offline/utils/offlinePlaylistBrowse', () => ({ loadOfflineBrowsablePlaylistMock(playlistId, serverId), })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForServer: (serverId: string, trackId?: string) => shouldAttemptSubsonicForServerMock(serverId, trackId), })); diff --git a/src/features/offline/utils/offlineMediaResolve.ts b/src/features/offline/utils/offlineMediaResolve.ts index 76205c12..9841a67a 100644 --- a/src/features/offline/utils/offlineMediaResolve.ts +++ b/src/features/offline/utils/offlineMediaResolve.ts @@ -8,7 +8,7 @@ import type { SubsonicSong, } from '@/lib/api/subsonicTypes'; import { useAuthStore } from '@/store/authStore'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { isOfflineBrowseActive } from '@/features/offline/utils/offlineBrowseMode'; import { libraryIsReady } from '@/lib/library/libraryReady'; import { registerMediaResolver } from '@/store/mediaResolver'; diff --git a/src/features/offline/utils/offlineStarredLoad.ts b/src/features/offline/utils/offlineStarredLoad.ts index d2f006b1..71e8078f 100644 --- a/src/features/offline/utils/offlineStarredLoad.ts +++ b/src/features/offline/utils/offlineStarredLoad.ts @@ -6,7 +6,7 @@ import type { SubsonicArtist, SubsonicSong, } from '@/lib/api/subsonicTypes'; -import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; import { albumToAlbum, trackToSong, diff --git a/src/features/offline/utils/pinnedOfflineSync.test.ts b/src/features/offline/utils/pinnedOfflineSync.test.ts index 530ee58d..6a1957d2 100644 --- a/src/features/offline/utils/pinnedOfflineSync.test.ts +++ b/src/features/offline/utils/pinnedOfflineSync.test.ts @@ -24,7 +24,7 @@ const isReachableMock = vi.fn(() => true); const enqueueMock = vi.fn((_task: unknown) => true); const invokeMock = vi.fn(async (_cmd: string, _args?: unknown) => ({})); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => isReachableMock(), onActiveServerBecameReachable: () => () => {}, })); diff --git a/src/features/offline/utils/pinnedOfflineSync.ts b/src/features/offline/utils/pinnedOfflineSync.ts index 1635c226..fb722c28 100644 --- a/src/features/offline/utils/pinnedOfflineSync.ts +++ b/src/features/offline/utils/pinnedOfflineSync.ts @@ -14,7 +14,7 @@ import { getMediaDir } from '@/lib/media/mediaDir'; import { isActiveServerReachable, onActiveServerBecameReachable, -} from '@/utils/network/activeServerReachability'; +} from '@/lib/network/activeServerReachability'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup'; import { findLocalPlaybackEntry } from '@/store/localPlaybackResolve'; diff --git a/src/features/offline/utils/resumeIncompleteOfflinePins.test.ts b/src/features/offline/utils/resumeIncompleteOfflinePins.test.ts index 7e3aaa25..6532eced 100644 --- a/src/features/offline/utils/resumeIncompleteOfflinePins.test.ts +++ b/src/features/offline/utils/resumeIncompleteOfflinePins.test.ts @@ -8,7 +8,7 @@ const isOfflinePinCompleteMock = vi.fn((_albumId: string, _serverId: string) => const resolveAlbumForServerMock = vi.fn(); const downloadAlbumMock = vi.fn(); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => isActiveServerReachableMock(), onActiveServerBecameReachable: () => () => {}, })); diff --git a/src/features/offline/utils/resumeIncompleteOfflinePins.ts b/src/features/offline/utils/resumeIncompleteOfflinePins.ts index e553f185..6982a135 100644 --- a/src/features/offline/utils/resumeIncompleteOfflinePins.ts +++ b/src/features/offline/utils/resumeIncompleteOfflinePins.ts @@ -5,8 +5,8 @@ import { useAuthStore } from '@/store/authStore'; import type { OfflineAlbumMeta } from '@/features/offline/store/offlineStore'; import { useOfflineStore } from '@/features/offline/store/offlineStore'; import { trackToSong } from '@/lib/library/advancedSearchLocal'; -import { isActiveServerReachable, onActiveServerBecameReachable } from '@/utils/network/activeServerReachability'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { isActiveServerReachable, onActiveServerBecameReachable } from '@/lib/network/activeServerReachability'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup'; import { isOfflinePinComplete } from '@/features/offline/utils/offlineLibraryHelpers'; import { resolveAlbumForServer } from '@/features/offline/utils/offlineMediaResolve'; diff --git a/src/features/playback/store/b1QueueServerIdentity.test.ts b/src/features/playback/store/b1QueueServerIdentity.test.ts index 72ee9f59..b4a74d6c 100644 --- a/src/features/playback/store/b1QueueServerIdentity.test.ts +++ b/src/features/playback/store/b1QueueServerIdentity.test.ts @@ -37,7 +37,7 @@ vi.mock('@/lib/api/subsonicPlayQueue', () => ({ savePlayQueue: vi.fn(async () => undefined), getPlayQueue: vi.fn(async () => ({ songs: [], current: undefined, position: 0 })), })); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => true, })); diff --git a/src/features/playback/store/playerStore.persistence.test.ts b/src/features/playback/store/playerStore.persistence.test.ts index 50980226..b355f3e3 100644 --- a/src/features/playback/store/playerStore.persistence.test.ts +++ b/src/features/playback/store/playerStore.persistence.test.ts @@ -30,7 +30,7 @@ vi.mock('@/lib/api/subsonicPlayQueue', () => ({ savePlayQueue: vi.fn(async () => undefined), getPlayQueue: vi.fn(async () => ({ songs: [], current: undefined, position: 0 })), })); -vi.mock('@/utils/network/activeServerReachability', () => ({ +vi.mock('@/lib/network/activeServerReachability', () => ({ isActiveServerReachable: () => true, })); vi.mock('@/lib/api/subsonicStreamUrl', () => ({ diff --git a/src/features/playback/store/queueSync.test.ts b/src/features/playback/store/queueSync.test.ts index 652d9064..7c6b67b0 100644 --- a/src/features/playback/store/queueSync.test.ts +++ b/src/features/playback/store/queueSync.test.ts @@ -13,7 +13,7 @@ const { savePlayQueueMock, playerState, progressSnapshot, isSubsonicServerReacha })); vi.mock('@/lib/api/subsonicPlayQueue', () => ({ savePlayQueue: savePlayQueueMock })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ isSubsonicServerReachable: (serverId: string) => isSubsonicServerReachableMock(serverId), })); vi.mock('@/features/playback/utils/playback/playbackServer', () => ({ diff --git a/src/features/playback/store/queueSync.ts b/src/features/playback/store/queueSync.ts index 145f51e9..cf13b93c 100644 --- a/src/features/playback/store/queueSync.ts +++ b/src/features/playback/store/queueSync.ts @@ -1,6 +1,6 @@ import { savePlayQueue } from '@/lib/api/subsonicPlayQueue'; import type { QueueItemRef, Track } from '@/lib/media/trackTypes'; -import { isSubsonicServerReachable } from '@/utils/network/subsonicNetworkGuard'; +import { isSubsonicServerReachable } from '@/lib/network/subsonicNetworkGuard'; import { filterQueueRefsForPlaybackServer, getPlaybackServerId, diff --git a/src/features/sidebar/hooks/useSidebarNewReleasesUnread.ts b/src/features/sidebar/hooks/useSidebarNewReleasesUnread.ts index 1ef19360..93852d9f 100644 --- a/src/features/sidebar/hooks/useSidebarNewReleasesUnread.ts +++ b/src/features/sidebar/hooks/useSidebarNewReleasesUnread.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { getAlbumList } from '@/lib/api/subsonicLibrary'; -import { isActiveServerReachable } from '@/utils/network/activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; import { NEW_RELEASES_RESET_DELAY_MS, NEW_RELEASES_SEEN_MAX_IDS, diff --git a/src/hooks/useConnectionStatus.test.ts b/src/hooks/useConnectionStatus.test.ts index 3fe31f29..09f89df4 100644 --- a/src/hooks/useConnectionStatus.test.ts +++ b/src/hooks/useConnectionStatus.test.ts @@ -19,7 +19,7 @@ vi.mock('@/lib/perf/perfFlags', () => ({ import { pingWithCredentialsForProfile } from '@/lib/api/subsonic'; import { useDevOfflineBrowseStore } from '@/features/offline'; -import { resetActiveServerConnectionSnapshot, setConnectionStatus } from '@/utils/network/activeServerReachability'; +import { resetActiveServerConnectionSnapshot, setConnectionStatus } from '@/lib/network/activeServerReachability'; import { useConnectionStatus } from './useConnectionStatus'; beforeEach(() => { diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts index b6662c25..a0ea2dc4 100644 --- a/src/hooks/useConnectionStatus.ts +++ b/src/hooks/useConnectionStatus.ts @@ -14,12 +14,12 @@ import { setConnectionStatus, subscribeConnectionStatus, type ConnectionStatus, -} from '../utils/network/activeServerReachability'; +} from '@/lib/network/activeServerReachability'; import { usePerfProbeFlags } from '@/lib/perf/perfFlags'; import { isDevOfflineBrowseForced, useDevOfflineBrowseStore, -} from '@/features/offline'; +} from '@/store/devOfflineBrowseStore'; // Backward-compatible re-export for call sites that still import from the hook. export { isLanUrl }; diff --git a/src/lib/api/subsonic.async.test.ts b/src/lib/api/subsonic.async.test.ts index a804b417..0241bf89 100644 --- a/src/lib/api/subsonic.async.test.ts +++ b/src/lib/api/subsonic.async.test.ts @@ -20,7 +20,7 @@ vi.mock('axios', () => ({ default: { get: vi.fn() }, })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForActiveServer: () => true, shouldAttemptSubsonicForServer: () => true, })); diff --git a/src/lib/api/subsonicLibrary.ts b/src/lib/api/subsonicLibrary.ts index 3b594a3c..c1efb6e3 100644 --- a/src/lib/api/subsonicLibrary.ts +++ b/src/lib/api/subsonicLibrary.ts @@ -2,7 +2,7 @@ import { useAuthStore } from '@/store/authStore'; import { shouldAttemptSubsonicForActiveServer, shouldAttemptSubsonicForServer, -} from '@/utils/network/subsonicNetworkGuard'; +} from '@/lib/network/subsonicNetworkGuard'; import { api, apiForServer, libraryFilterParams, libraryFilterParamsForServer } from '@/lib/api/subsonicClient'; import type { RandomSongsFilters, diff --git a/src/lib/api/subsonicPlaylists.ts b/src/lib/api/subsonicPlaylists.ts index c155d344..ca860591 100644 --- a/src/lib/api/subsonicPlaylists.ts +++ b/src/lib/api/subsonicPlaylists.ts @@ -1,6 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { useAuthStore } from '@/store/authStore'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; import { api, apiForServer } from '@/lib/api/subsonicClient'; import type { SubsonicPlaylist, SubsonicSong } from '@/lib/api/subsonicTypes'; diff --git a/src/lib/api/subsonicRatings.test.ts b/src/lib/api/subsonicRatings.test.ts index e3c29c81..051990a8 100644 --- a/src/lib/api/subsonicRatings.test.ts +++ b/src/lib/api/subsonicRatings.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; vi.mock('@/lib/api/subsonicArtists', () => ({ getArtist: vi.fn() })); vi.mock('@/lib/api/subsonicLibrary', () => ({ getAlbum: vi.fn() })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForActiveServer: vi.fn(() => true), })); diff --git a/src/lib/api/subsonicRatings.ts b/src/lib/api/subsonicRatings.ts index 943a4e6e..4ff92af6 100644 --- a/src/lib/api/subsonicRatings.ts +++ b/src/lib/api/subsonicRatings.ts @@ -1,6 +1,6 @@ import { getArtist } from '@/lib/api/subsonicArtists'; import { getAlbum } from '@/lib/api/subsonicLibrary'; -import { shouldAttemptSubsonicForActiveServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForActiveServer } from '@/lib/network/subsonicNetworkGuard'; const MIX_RATING_PREFETCH_CONCURRENCY = 8; const RATING_CACHE_TTL = 7 * 60 * 1000; // 7 minutes diff --git a/src/lib/api/subsonicScrobble.test.ts b/src/lib/api/subsonicScrobble.test.ts index e7e0f4ca..f1fd1b6e 100644 --- a/src/lib/api/subsonicScrobble.test.ts +++ b/src/lib/api/subsonicScrobble.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useAuthStore } from '@/store/authStore'; import { usePlayerStore } from '@/features/playback/store/playerStore'; import { reportNowPlaying, scrobbleSong } from '@/lib/api/subsonicScrobble'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; const { apiForServerMock } = vi.hoisted(() => ({ apiForServerMock: vi.fn(async () => ({})), @@ -12,7 +12,7 @@ vi.mock('@/lib/api/subsonicClient', () => ({ api: vi.fn(), apiForServer: apiForServerMock, })); -vi.mock('@/utils/network/subsonicNetworkGuard', () => ({ +vi.mock('@/lib/network/subsonicNetworkGuard', () => ({ shouldAttemptSubsonicForServer: vi.fn(() => true), })); diff --git a/src/lib/api/subsonicScrobble.ts b/src/lib/api/subsonicScrobble.ts index 897ed8e8..59aae881 100644 --- a/src/lib/api/subsonicScrobble.ts +++ b/src/lib/api/subsonicScrobble.ts @@ -1,7 +1,7 @@ import { api, apiForServer } from '@/lib/api/subsonicClient'; import type { PlaybackReportState, SubsonicNowPlaying } from '@/lib/api/subsonicTypes'; import { patchLibraryTrackOnUse } from '@/lib/library/patchOnUse'; -import { shouldAttemptSubsonicForServer } from '@/utils/network/subsonicNetworkGuard'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; async function scrobbleOnServer( serverId: string, diff --git a/src/utils/network/activeServerReachability.test.ts b/src/lib/network/activeServerReachability.test.ts similarity index 92% rename from src/utils/network/activeServerReachability.test.ts rename to src/lib/network/activeServerReachability.test.ts index e712da47..5ff04a8c 100644 --- a/src/utils/network/activeServerReachability.test.ts +++ b/src/lib/network/activeServerReachability.test.ts @@ -1,12 +1,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { useDevOfflineBrowseStore } from '@/features/offline'; +import { useDevOfflineBrowseStore } from '@/store/devOfflineBrowseStore'; import { getActiveServerReachable, isActiveServerReachable, onActiveServerBecameReachable, resetActiveServerConnectionSnapshot, setActiveServerReachable, -} from './activeServerReachability'; +} from '@/lib/network/activeServerReachability'; describe('activeServerReachability', () => { beforeEach(() => { diff --git a/src/utils/network/activeServerReachability.ts b/src/lib/network/activeServerReachability.ts similarity index 96% rename from src/utils/network/activeServerReachability.ts rename to src/lib/network/activeServerReachability.ts index e2da6440..7bbb5fc0 100644 --- a/src/utils/network/activeServerReachability.ts +++ b/src/lib/network/activeServerReachability.ts @@ -1,4 +1,4 @@ -import { isDevOfflineBrowseForced } from '@/features/offline'; +import { isDevOfflineBrowseForced } from '@/store/devOfflineBrowseStore'; export type ConnectionStatus = 'connected' | 'disconnected' | 'checking'; diff --git a/src/utils/network/subsonicNetworkGuard.test.ts b/src/lib/network/subsonicNetworkGuard.test.ts similarity index 74% rename from src/utils/network/subsonicNetworkGuard.test.ts rename to src/lib/network/subsonicNetworkGuard.test.ts index d07b15ec..5d540ab2 100644 --- a/src/utils/network/subsonicNetworkGuard.test.ts +++ b/src/lib/network/subsonicNetworkGuard.test.ts @@ -1,23 +1,21 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { useAuthStore } from '../../store/authStore'; -import { resetAuthStore } from '../../test/helpers/storeReset'; -import { setActiveServerReachable } from './activeServerReachability'; -import { shouldAttemptSubsonicForServer } from './subsonicNetworkGuard'; +import { useAuthStore } from '@/store/authStore'; +import { resetAuthStore } from '@/test/helpers/storeReset'; +import { setActiveServerReachable } from '@/lib/network/activeServerReachability'; +import { shouldAttemptSubsonicForServer } from '@/lib/network/subsonicNetworkGuard'; -const resolvePlaybackUrlMock = vi.fn((_trackId: string, _serverId?: string) => - 'https://music.test/stream', -); +const hasLocalPlaybackUrlMock = vi.fn((_trackId: string, _serverId: string) => false); -vi.mock('@/features/playback/utils/playback/resolvePlaybackUrl', () => ({ - resolvePlaybackUrl: (trackId: string, serverId?: string) => - resolvePlaybackUrlMock(trackId, serverId), +vi.mock('@/store/localPlaybackResolve', () => ({ + hasLocalPlaybackUrl: (trackId: string, serverId: string) => + hasLocalPlaybackUrlMock(trackId, serverId), })); describe('shouldAttemptSubsonicForServer', () => { beforeEach(() => { resetAuthStore(); setActiveServerReachable(null); - resolvePlaybackUrlMock.mockReturnValue('https://music.test/stream'); + hasLocalPlaybackUrlMock.mockReturnValue(false); }); it('returns false without a server id', () => { @@ -58,9 +56,9 @@ describe('shouldAttemptSubsonicForServer', () => { it('returns false when the track resolves to a local playback url', () => { setActiveServerReachable(true); - resolvePlaybackUrlMock.mockReturnValue('psysonic-local:///favorites/t1.flac'); + hasLocalPlaybackUrlMock.mockReturnValue(true); expect(shouldAttemptSubsonicForServer('srv-1', 't1')).toBe(false); - expect(resolvePlaybackUrlMock).toHaveBeenCalledWith('t1', 'srv-1'); + expect(hasLocalPlaybackUrlMock).toHaveBeenCalledWith('t1', 'srv-1'); }); it('returns true for stream playback when the active server is reachable', () => { @@ -84,11 +82,11 @@ describe('shouldAttemptSubsonicForServer', () => { }); useAuthStore.getState().setActiveServer(activeId); setActiveServerReachable(true); - resolvePlaybackUrlMock.mockReturnValue('psysonic-local:///favorites/t1.flac'); + hasLocalPlaybackUrlMock.mockReturnValue(true); // Byte-style call (with the track id) is blocked because the bytes are local… expect(shouldAttemptSubsonicForServer(activeId, 't1')).toBe(false); // …but the metadata gate omits the track id, so it never consults - // resolvePlaybackUrl and stays allowed while the server is reachable. + // hasLocalPlaybackUrl and stays allowed while the server is reachable. expect(shouldAttemptSubsonicForServer(activeId)).toBe(true); }); }); diff --git a/src/utils/network/subsonicNetworkGuard.ts b/src/lib/network/subsonicNetworkGuard.ts similarity index 80% rename from src/utils/network/subsonicNetworkGuard.ts rename to src/lib/network/subsonicNetworkGuard.ts index dcc4ae55..bd1bcb02 100644 --- a/src/utils/network/subsonicNetworkGuard.ts +++ b/src/lib/network/subsonicNetworkGuard.ts @@ -1,8 +1,8 @@ -import { useAuthStore } from '../../store/authStore'; -import { resolvePlaybackUrl } from '@/features/playback/utils/playback/resolvePlaybackUrl'; -import { isDevOfflineBrowseForced } from '@/features/offline'; +import { useAuthStore } from '@/store/authStore'; +import { hasLocalPlaybackUrl } from '@/store/localPlaybackResolve'; +import { isDevOfflineBrowseForced } from '@/store/devOfflineBrowseStore'; import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup'; -import { isActiveServerReachable } from './activeServerReachability'; +import { isActiveServerReachable } from '@/lib/network/activeServerReachability'; function isSameServerProfile(a: string, b: string): boolean { if (!a || !b) return false; @@ -35,10 +35,7 @@ export function shouldAttemptSubsonicForServer(serverId: string, trackId?: strin if (!serverId) return false; if (isDevOfflineBrowseForced()) return false; if (typeof navigator !== 'undefined' && !navigator.onLine) return false; - if (trackId) { - const url = resolvePlaybackUrl(trackId, serverId); - if (url.startsWith('psysonic-local://')) return false; - } + if (trackId && hasLocalPlaybackUrl(trackId, serverId)) return false; return isSubsonicServerReachable(serverId); } diff --git a/src/features/offline/store/devOfflineBrowseStore.ts b/src/store/devOfflineBrowseStore.ts similarity index 100% rename from src/features/offline/store/devOfflineBrowseStore.ts rename to src/store/devOfflineBrowseStore.ts diff --git a/src/store/localPlaybackResolve.ts b/src/store/localPlaybackResolve.ts index 95781d3b..befb5517 100644 --- a/src/store/localPlaybackResolve.ts +++ b/src/store/localPlaybackResolve.ts @@ -6,6 +6,7 @@ import { useAuthStore } from '@/store/authStore'; import type { LocalPlaybackEntry } from '@/store/localPlaybackStore'; import { useLocalPlaybackStore } from '@/store/localPlaybackStore'; import { resolveIndexKey, serverIndexKeyForProfile } from '@/lib/server/serverIndexKey'; +import { resolveServerIdForIndexKey } from '@/lib/server/serverLookup'; function serverIndexKeysForServerId(serverId: string): string[] { const servers = useAuthStore.getState().servers; @@ -100,3 +101,20 @@ export function findLocalPlaybackUrl( } return null; } + +/** + * True when the track resolves to local `psysonic-local://` bytes (library, + * favorite-auto, or ephemeral tier). Mirrors `resolvePlaybackUrl`'s local-source + * branch exactly (same `resolveServerIdForIndexKey(serverId) || serverId` profile + * resolution, minus the empty-serverId playback-store fallback that callers here + * never hit) so the network-guard skip check stays bit-identical without an + * @/features/playback import. + */ +export function hasLocalPlaybackUrl(trackId: string, serverId: string): boolean { + const profileId = resolveServerIdForIndexKey(serverId) || serverId; + return !!( + findLocalPlaybackUrl(trackId, profileId, 'library') || + findLocalPlaybackUrl(trackId, profileId, 'favorite-auto') || + findLocalPlaybackUrl(trackId, profileId, 'ephemeral') + ); +}