refactor(stores): rename store to offlineCacheState
- Create offlineCacheState.ts with compliant terminology - Update all component imports and type references - Add compatibility aliases for gradual migration - Remove deprecated downloadState.ts cc
This commit is contained in:
1 parent
393589862d
commit
84a2164079
10 files changed
+358
-303
No files matched your search
@@ -6,10 +6,11 @@
|
|||||||
isOffline as checkIsOffline
|
isOffline as checkIsOffline
|
||||||
} from '$lib/stores/offlineStore';
|
} from '$lib/stores/offlineStore';
|
||||||
|
|
||||||
type DownloadStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
// Offline cache status for tracks
|
||||||
|
type OfflineCacheStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
status?: DownloadStatus;
|
status?: OfflineCacheStatus;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
onDownload?: () => void;
|
onDownload?: () => void;
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
} from '$lib/stores/favoritesStore';
|
} from '$lib/stores/favoritesStore';
|
||||||
import { togglePlay } from '$lib/stores/playerStore';
|
import { togglePlay } from '$lib/stores/playerStore';
|
||||||
|
|
||||||
type DownloadStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
// Offline cache status for tracks
|
||||||
|
type OfflineCacheStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trackId?: number; // Optional - required for favorites functionality unless hideFavorite=true
|
trackId?: number; // Optional - required for favorites functionality unless hideFavorite=true
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
isPlaying?: boolean;
|
isPlaying?: boolean;
|
||||||
isLocal?: boolean; // Whether this is a local library track
|
isLocal?: boolean; // Whether this is a local library track
|
||||||
isFavoriteOverride?: boolean; // Optional override for favorite state
|
isFavoriteOverride?: boolean; // Optional override for favorite state
|
||||||
downloadStatus?: DownloadStatus;
|
downloadStatus?: OfflineCacheStatus;
|
||||||
downloadProgress?: number;
|
downloadProgress?: number;
|
||||||
hideDownload?: boolean;
|
hideDownload?: boolean;
|
||||||
hideFavorite?: boolean;
|
hideFavorite?: boolean;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import TrackRow from './TrackRow.svelte';
|
import TrackRow from './TrackRow.svelte';
|
||||||
import type { DownloadStatus } from '$lib/stores/downloadState';
|
import type { OfflineCacheStatus } from '$lib/stores/offlineCacheState';
|
||||||
|
|
||||||
// Use generic types to match whatever caller passes
|
// Use generic types to match whatever caller passes
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
hideFavorite?: boolean;
|
hideFavorite?: boolean;
|
||||||
isFavoriteOverride?: boolean;
|
isFavoriteOverride?: boolean;
|
||||||
// Download status support for Favorites/Search
|
// Download status support for Favorites/Search
|
||||||
getDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
getOfflineCacheStatus?: (trackId: number) => { status: OfflineCacheStatus; progress: number };
|
||||||
onDownload?: (track: Track) => void;
|
onDownload?: (track: Track) => void;
|
||||||
onRemoveDownload?: (trackId: number) => void;
|
onRemoveDownload?: (trackId: number) => void;
|
||||||
// Additional menu actions for Favorites
|
// Additional menu actions for Favorites
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
hideDownload = true,
|
hideDownload = true,
|
||||||
hideFavorite = true,
|
hideFavorite = true,
|
||||||
isFavoriteOverride,
|
isFavoriteOverride,
|
||||||
getDownloadStatus,
|
getOfflineCacheStatus,
|
||||||
onDownload,
|
onDownload,
|
||||||
onRemoveDownload,
|
onRemoveDownload,
|
||||||
onShareQobuz,
|
onShareQobuz,
|
||||||
@@ -305,7 +305,7 @@
|
|||||||
{@const albumKey = getTrackAlbumKey(item.track)}
|
{@const albumKey = getTrackAlbumKey(item.track)}
|
||||||
{@const artistId = getArtistId?.(item.track)}
|
{@const artistId = getArtistId?.(item.track)}
|
||||||
{@const albumId = getAlbumId?.(item.track)}
|
{@const albumId = getAlbumId?.(item.track)}
|
||||||
{@const downloadInfo = getDownloadStatus?.(trackId) ?? { status: 'none' as const, progress: 0 }}
|
{@const downloadInfo = getOfflineCacheStatus?.(trackId) ?? { status: 'none' as const, progress: 0 }}
|
||||||
{@const isTrackDownloaded = downloadInfo.status === 'ready'}
|
{@const isTrackDownloaded = downloadInfo.status === 'ready'}
|
||||||
<TrackRow
|
<TrackRow
|
||||||
trackId={trackId}
|
trackId={trackId}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { ArrowLeft, Play, Shuffle, Heart } from 'lucide-svelte';
|
import { ArrowLeft, Play, Shuffle, Heart } from 'lucide-svelte';
|
||||||
import TrackRow from '../TrackRow.svelte';
|
import TrackRow from '../TrackRow.svelte';
|
||||||
import AlbumMenu from '../AlbumMenu.svelte';
|
import AlbumMenu from '../AlbumMenu.svelte';
|
||||||
import { getDownloadState, type DownloadStatus, isAlbumFullyDownloaded } from '$lib/stores/downloadState';
|
import { getOfflineCacheState, type OfflineCacheStatus, isAlbumFullyCached } from '$lib/stores/offlineCacheState';
|
||||||
import { consumeContextTrackFocus } from '$lib/stores/playbackContextStore';
|
import { consumeContextTrackFocus } from '$lib/stores/playbackContextStore';
|
||||||
import {
|
import {
|
||||||
subscribe as subscribeAlbumFavorites,
|
subscribe as subscribeAlbumFavorites,
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
onTrackDownload?: (track: Track) => void;
|
onTrackDownload?: (track: Track) => void;
|
||||||
onTrackRemoveDownload?: (trackId: number) => void;
|
onTrackRemoveDownload?: (trackId: number) => void;
|
||||||
onTrackReDownload?: (track: Track) => void;
|
onTrackReDownload?: (track: Track) => void;
|
||||||
getTrackDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
getTrackOfflineCacheStatus?: (trackId: number) => { status: OfflineCacheStatus; progress: number };
|
||||||
onDownloadAlbum?: () => void;
|
onDownloadAlbum?: () => void;
|
||||||
onShareAlbumQobuz?: () => void;
|
onShareAlbumQobuz?: () => void;
|
||||||
onShareAlbumSonglink?: () => void;
|
onShareAlbumSonglink?: () => void;
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
onTrackDownload,
|
onTrackDownload,
|
||||||
onTrackRemoveDownload,
|
onTrackRemoveDownload,
|
||||||
onTrackReDownload,
|
onTrackReDownload,
|
||||||
getTrackDownloadStatus,
|
getTrackOfflineCacheStatus,
|
||||||
onDownloadAlbum,
|
onDownloadAlbum,
|
||||||
onShareAlbumQobuz,
|
onShareAlbumQobuz,
|
||||||
onShareAlbumSonglink,
|
onShareAlbumSonglink,
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
let scrollContainer: HTMLDivElement | null = $state(null);
|
let scrollContainer: HTMLDivElement | null = $state(null);
|
||||||
|
|
||||||
const albumFullyDownloaded = $derived(
|
const albumFullyDownloaded = $derived(
|
||||||
isAlbumFullyDownloaded(album.tracks.map(t => t.id))
|
isAlbumFullyCached(album.tracks.map(t => t.id))
|
||||||
);
|
);
|
||||||
|
|
||||||
const isVariousArtists = $derived(
|
const isVariousArtists = $derived(
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
onShareQobuz={onShareAlbumQobuz}
|
onShareQobuz={onShareAlbumQobuz}
|
||||||
onShareSonglink={onShareAlbumSonglink}
|
onShareSonglink={onShareAlbumSonglink}
|
||||||
onDownload={onDownloadAlbum}
|
onDownload={onDownloadAlbum}
|
||||||
isAlbumFullyDownloaded={albumFullyDownloaded}
|
isAlbumFullyCached={albumFullyDownloaded}
|
||||||
onOpenContainingFolder={onOpenAlbumFolder}
|
onOpenContainingFolder={onOpenAlbumFolder}
|
||||||
onReDownloadAlbum={onReDownloadAlbum}
|
onReDownloadAlbum={onReDownloadAlbum}
|
||||||
/>
|
/>
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
<!-- Track Rows -->
|
<!-- Track Rows -->
|
||||||
<div class="tracks">
|
<div class="tracks">
|
||||||
{#each album.tracks as track, trackIndex (`${track.id}-${downloadStateVersion}`)}
|
{#each album.tracks as track, trackIndex (`${track.id}-${downloadStateVersion}`)}
|
||||||
{@const downloadInfo = getTrackDownloadStatus?.(track.id) ?? { status: 'none' as const, progress: 0 }}
|
{@const downloadInfo = getTrackOfflineCacheStatus?.(track.id) ?? { status: 'none' as const, progress: 0 }}
|
||||||
{@const isTrackDownloaded = downloadInfo.status === 'ready'}
|
{@const isTrackDownloaded = downloadInfo.status === 'ready'}
|
||||||
<TrackRow
|
<TrackRow
|
||||||
trackId={track.id}
|
trackId={track.id}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import VirtualizedTrackList from '../VirtualizedTrackList.svelte';
|
import VirtualizedTrackList from '../VirtualizedTrackList.svelte';
|
||||||
import PlaylistCollage from '../PlaylistCollage.svelte';
|
import PlaylistCollage from '../PlaylistCollage.svelte';
|
||||||
import FavoritesEditModal from '../FavoritesEditModal.svelte';
|
import FavoritesEditModal from '../FavoritesEditModal.svelte';
|
||||||
import { type DownloadStatus } from '$lib/stores/downloadState';
|
import { type OfflineCacheStatus } from '$lib/stores/offlineCacheState';
|
||||||
import { consumeContextTrackFocus, setPlaybackContext } from '$lib/stores/playbackContextStore';
|
import { consumeContextTrackFocus, setPlaybackContext } from '$lib/stores/playbackContextStore';
|
||||||
import { normalizeFavoritesTabOrder } from '$lib/utils/favorites';
|
import { normalizeFavoritesTabOrder } from '$lib/utils/favorites';
|
||||||
import type { FavoritesPreferences } from '$lib/types';
|
import type { FavoritesPreferences } from '$lib/types';
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
onTrackDownload?: (track: DisplayTrack) => void;
|
onTrackDownload?: (track: DisplayTrack) => void;
|
||||||
onTrackRemoveDownload?: (trackId: number) => void;
|
onTrackRemoveDownload?: (trackId: number) => void;
|
||||||
onTrackReDownload?: (track: DisplayTrack) => void;
|
onTrackReDownload?: (track: DisplayTrack) => void;
|
||||||
getTrackDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
getTrackOfflineCacheStatus?: (trackId: number) => { status: OfflineCacheStatus; progress: number };
|
||||||
onPlaylistSelect?: (playlistId: number) => void;
|
onPlaylistSelect?: (playlistId: number) => void;
|
||||||
selectedTab?: TabType;
|
selectedTab?: TabType;
|
||||||
onTabNavigate?: (tab: TabType) => void;
|
onTabNavigate?: (tab: TabType) => void;
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
onTrackDownload,
|
onTrackDownload,
|
||||||
onTrackRemoveDownload,
|
onTrackRemoveDownload,
|
||||||
onTrackReDownload,
|
onTrackReDownload,
|
||||||
getTrackDownloadStatus,
|
getTrackOfflineCacheStatus,
|
||||||
onPlaylistSelect,
|
onPlaylistSelect,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
onTabNavigate,
|
onTabNavigate,
|
||||||
@@ -163,27 +163,27 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Download status tracking
|
// Download status tracking
|
||||||
let albumDownloadStatuses = $state<Map<string, boolean>>(new Map());
|
let albumOfflineCacheStatuses = $state<Map<string, boolean>>(new Map());
|
||||||
|
|
||||||
async function loadAlbumDownloadStatus(albumId: string) {
|
async function loadAlbumOfflineCacheStatus(albumId: string) {
|
||||||
if (!checkAlbumFullyDownloaded) return false;
|
if (!checkAlbumFullyDownloaded) return false;
|
||||||
try {
|
try {
|
||||||
const isDownloaded = await checkAlbumFullyDownloaded(albumId);
|
const isDownloaded = await checkAlbumFullyDownloaded(albumId);
|
||||||
albumDownloadStatuses.set(albumId, isDownloaded);
|
albumOfflineCacheStatuses.set(albumId, isDownloaded);
|
||||||
return isDownloaded;
|
return isDownloaded;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAllAlbumDownloadStatuses(albums: { id: string }[]) {
|
async function loadAllAlbumOfflineCacheStatuses(albums: { id: string }[]) {
|
||||||
if (!checkAlbumFullyDownloaded || albums.length === 0) return;
|
if (!checkAlbumFullyDownloaded || albums.length === 0) return;
|
||||||
await Promise.all(albums.map(album => loadAlbumDownloadStatus(album.id)));
|
await Promise.all(albums.map(album => loadAlbumOfflineCacheStatus(album.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAlbumDownloaded(albumId: string): boolean {
|
function isAlbumDownloaded(albumId: string): boolean {
|
||||||
void downloadStateVersion;
|
void downloadStateVersion;
|
||||||
return albumDownloadStatuses.get(albumId) || false;
|
return albumOfflineCacheStatuses.get(albumId) || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
@@ -449,7 +449,7 @@
|
|||||||
favoriteTracks = items as FavoriteTrack[];
|
favoriteTracks = items as FavoriteTrack[];
|
||||||
} else if (type === 'albums') {
|
} else if (type === 'albums') {
|
||||||
favoriteAlbums = items as FavoriteAlbum[];
|
favoriteAlbums = items as FavoriteAlbum[];
|
||||||
await loadAllAlbumDownloadStatuses(favoriteAlbums);
|
await loadAllAlbumOfflineCacheStatuses(favoriteAlbums);
|
||||||
} else if (type === 'artists') {
|
} else if (type === 'artists') {
|
||||||
favoriteArtists = items as FavoriteArtist[];
|
favoriteArtists = items as FavoriteArtist[];
|
||||||
}
|
}
|
||||||
@@ -1133,7 +1133,7 @@
|
|||||||
hideDownload={false}
|
hideDownload={false}
|
||||||
hideFavorite={false}
|
hideFavorite={false}
|
||||||
isFavoriteOverride={true}
|
isFavoriteOverride={true}
|
||||||
getDownloadStatus={getTrackDownloadStatus}
|
getOfflineCacheStatus={getTrackOfflineCacheStatus}
|
||||||
onDownload={onTrackDownload ? (t) => onTrackDownload(buildDisplayTrackFromFavorite(t)) : undefined}
|
onDownload={onTrackDownload ? (t) => onTrackDownload(buildDisplayTrackFromFavorite(t)) : undefined}
|
||||||
onRemoveDownload={onTrackRemoveDownload}
|
onRemoveDownload={onTrackRemoveDownload}
|
||||||
onShareQobuz={onTrackShareQobuz}
|
onShareQobuz={onTrackShareQobuz}
|
||||||
@@ -1204,7 +1204,7 @@
|
|||||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||||
{downloadStateVersion}
|
{downloadStateVersion}
|
||||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
onclick={() => { onAlbumClick?.(album.id); loadAlbumOfflineCacheStatus(album.id); }}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -1275,7 +1275,7 @@
|
|||||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||||
{downloadStateVersion}
|
{downloadStateVersion}
|
||||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
onclick={() => { onAlbumClick?.(album.id); loadAlbumOfflineCacheStatus(album.id); }}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
import { open } from '@tauri-apps/plugin-dialog';
|
||||||
import TrackRow from '../TrackRow.svelte';
|
import TrackRow from '../TrackRow.svelte';
|
||||||
import { type DownloadStatus } from '$lib/stores/downloadState';
|
import { type OfflineCacheStatus } from '$lib/stores/offlineCacheState';
|
||||||
import {
|
import {
|
||||||
subscribe as subscribeOffline,
|
subscribe as subscribeOffline,
|
||||||
getStatus as getOfflineStatus,
|
getStatus as getOfflineStatus,
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
onTrackDownload?: (track: DisplayTrack) => void;
|
onTrackDownload?: (track: DisplayTrack) => void;
|
||||||
onTrackRemoveDownload?: (trackId: number) => void;
|
onTrackRemoveDownload?: (trackId: number) => void;
|
||||||
onTrackReDownload?: (track: DisplayTrack) => void;
|
onTrackReDownload?: (track: DisplayTrack) => void;
|
||||||
getTrackDownloadStatus?: (trackId: number) => { status: DownloadStatus; progress: number };
|
getTrackOfflineCacheStatus?: (trackId: number) => { status: OfflineCacheStatus; progress: number };
|
||||||
downloadStateVersion?: number;
|
downloadStateVersion?: number;
|
||||||
onLocalTrackPlay?: (track: LocalLibraryTrack) => void;
|
onLocalTrackPlay?: (track: LocalLibraryTrack) => void;
|
||||||
onLocalTrackPlayNext?: (track: LocalLibraryTrack) => void;
|
onLocalTrackPlayNext?: (track: LocalLibraryTrack) => void;
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
onTrackDownload,
|
onTrackDownload,
|
||||||
onTrackRemoveDownload,
|
onTrackRemoveDownload,
|
||||||
onTrackReDownload,
|
onTrackReDownload,
|
||||||
getTrackDownloadStatus,
|
getTrackOfflineCacheStatus,
|
||||||
downloadStateVersion,
|
downloadStateVersion,
|
||||||
onLocalTrackPlay,
|
onLocalTrackPlay,
|
||||||
onLocalTrackPlayNext,
|
onLocalTrackPlayNext,
|
||||||
@@ -1099,7 +1099,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each displayTracks as track, idx (`${idx}-${track.id}-${downloadStateVersion}`)}
|
{#each displayTracks as track, idx (`${idx}-${track.id}-${downloadStateVersion}`)}
|
||||||
{@const downloadInfo = track.isLocal ? { status: 'none' as const, progress: 0 } : (getTrackDownloadStatus?.(track.id) ?? { status: 'none' as const, progress: 0 })}
|
{@const downloadInfo = track.isLocal ? { status: 'none' as const, progress: 0 } : (getTrackOfflineCacheStatus?.(track.id) ?? { status: 'none' as const, progress: 0 })}
|
||||||
{@const isActiveTrack = (
|
{@const isActiveTrack = (
|
||||||
track.isLocal
|
track.isLocal
|
||||||
? (track.localTrackId !== undefined && activeTrackId === track.localTrackId)
|
? (track.localTrackId !== undefined && activeTrackId === track.localTrackId)
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
import Dropdown from '../Dropdown.svelte';
|
import Dropdown from '../Dropdown.svelte';
|
||||||
import VolumeSlider from '../VolumeSlider.svelte';
|
import VolumeSlider from '../VolumeSlider.svelte';
|
||||||
import {
|
import {
|
||||||
getDownloadCacheStats,
|
getOfflineCacheStats,
|
||||||
clearDownloadCache,
|
clearOfflineCache,
|
||||||
type DownloadCacheStats
|
type OfflineCacheStats
|
||||||
} from '$lib/stores/downloadState';
|
} from '$lib/stores/offlineCacheState';
|
||||||
import { notifyDownloadSettingsChanged } from '$lib/stores/downloadSettingsStore';
|
import { notifyDownloadSettingsChanged } from '$lib/stores/downloadSettingsStore';
|
||||||
import { clearCache as clearLyricsCache } from '$lib/stores/lyricsStore';
|
import { clearCache as clearLyricsCache } from '$lib/stores/lyricsStore';
|
||||||
import {
|
import {
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
let isClearing = $state(false);
|
let isClearing = $state(false);
|
||||||
|
|
||||||
// Download cache state (offline storage)
|
// Download cache state (offline storage)
|
||||||
let downloadStats = $state<DownloadCacheStats | null>(null);
|
let downloadStats = $state<OfflineCacheStats | null>(null);
|
||||||
let isClearingDownloads = $state(false);
|
let isClearingDownloads = $state(false);
|
||||||
let isRepairingDownloads = $state(false);
|
let isRepairingDownloads = $state(false);
|
||||||
|
|
||||||
@@ -1165,7 +1165,7 @@
|
|||||||
|
|
||||||
async function loadDownloadStats() {
|
async function loadDownloadStats() {
|
||||||
try {
|
try {
|
||||||
downloadStats = await getDownloadCacheStats();
|
downloadStats = await getOfflineCacheStats();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load download stats:', err);
|
console.error('Failed to load download stats:', err);
|
||||||
}
|
}
|
||||||
@@ -1266,7 +1266,7 @@
|
|||||||
if (isClearingDownloads) return;
|
if (isClearingDownloads) return;
|
||||||
isClearingDownloads = true;
|
isClearingDownloads = true;
|
||||||
try {
|
try {
|
||||||
await clearDownloadCache();
|
await clearOfflineCache();
|
||||||
await loadDownloadStats();
|
await loadDownloadStats();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to clear download cache:', err);
|
console.error('Failed to clear download cache:', err);
|
||||||
|
|||||||
@@ -1,233 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
|
||||||
|
|
||||||
export type DownloadStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
|
||||||
|
|
||||||
export interface DownloadInfo {
|
|
||||||
status: DownloadStatus;
|
|
||||||
progress: number;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CachedTrackInfo {
|
|
||||||
trackId: number;
|
|
||||||
title: string;
|
|
||||||
artist: string;
|
|
||||||
album?: string;
|
|
||||||
albumId?: string;
|
|
||||||
durationSecs: number;
|
|
||||||
fileSizeBytes: number;
|
|
||||||
quality: string;
|
|
||||||
bitDepth?: number;
|
|
||||||
sampleRate?: number;
|
|
||||||
status: DownloadStatus;
|
|
||||||
progressPercent: number;
|
|
||||||
errorMessage?: string;
|
|
||||||
createdAt: string;
|
|
||||||
lastAccessedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadCacheStats {
|
|
||||||
totalTracks: number;
|
|
||||||
readyTracks: number;
|
|
||||||
downloadingTracks: number;
|
|
||||||
failedTracks: number;
|
|
||||||
totalSizeBytes: number;
|
|
||||||
limitBytes?: number;
|
|
||||||
cachePath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track download states by track ID
|
|
||||||
const downloadStates = new Map<number, DownloadInfo>();
|
|
||||||
|
|
||||||
// Listeners for state changes
|
|
||||||
const listeners = new Set<() => void>();
|
|
||||||
|
|
||||||
// Event unsubscribe functions
|
|
||||||
let unlisteners: UnlistenFn[] = [];
|
|
||||||
|
|
||||||
export function getDownloadState(trackId: number): DownloadInfo {
|
|
||||||
return downloadStates.get(trackId) || { status: 'none', progress: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setDownloadState(trackId: number, info: DownloadInfo): void {
|
|
||||||
downloadStates.set(trackId, info);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all tracks of an album are downloaded
|
|
||||||
export function isAlbumFullyDownloaded(trackIds: number[]): boolean {
|
|
||||||
if (trackIds.length === 0) return false;
|
|
||||||
|
|
||||||
return trackIds.every(trackId => {
|
|
||||||
const state = getDownloadState(trackId);
|
|
||||||
return state.status === 'ready';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function subscribe(listener: () => void): () => void {
|
|
||||||
listeners.add(listener);
|
|
||||||
return () => listeners.delete(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifyListeners(): void {
|
|
||||||
for (const listener of listeners) {
|
|
||||||
listener();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize download states from backend
|
|
||||||
export async function initDownloadStates(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const tracks = await invoke<CachedTrackInfo[]>('get_downloaded_tracks');
|
|
||||||
for (const track of tracks) {
|
|
||||||
downloadStates.set(track.trackId, {
|
|
||||||
status: track.status,
|
|
||||||
progress: track.progressPercent,
|
|
||||||
error: track.errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to init download states:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start listening for download events
|
|
||||||
export async function startDownloadEventListeners(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const unlistenStarted = await listen<{ trackId: number }>('download:started', (event) => {
|
|
||||||
console.log('Download started:', event.payload.trackId);
|
|
||||||
setDownloadState(event.payload.trackId, { status: 'downloading', progress: 0 });
|
|
||||||
});
|
|
||||||
|
|
||||||
const unlistenProgress = await listen<{
|
|
||||||
trackId: number;
|
|
||||||
progressPercent: number;
|
|
||||||
bytesDownloaded: number;
|
|
||||||
totalBytes?: number;
|
|
||||||
status: string;
|
|
||||||
}>('download:progress', (event) => {
|
|
||||||
const { trackId, progressPercent } = event.payload;
|
|
||||||
setDownloadState(trackId, { status: 'downloading', progress: progressPercent });
|
|
||||||
});
|
|
||||||
|
|
||||||
const unlistenCompleted = await listen<{ trackId: number; size: number }>('download:completed', (event) => {
|
|
||||||
console.log('Download completed:', event.payload.trackId);
|
|
||||||
setDownloadState(event.payload.trackId, { status: 'ready', progress: 100 });
|
|
||||||
});
|
|
||||||
|
|
||||||
const unlistenFailed = await listen<{ trackId: number; error: string }>('download:failed', (event) => {
|
|
||||||
console.error('Download failed:', event.payload.trackId, event.payload.error);
|
|
||||||
setDownloadState(event.payload.trackId, {
|
|
||||||
status: 'failed',
|
|
||||||
progress: 0,
|
|
||||||
error: event.payload.error,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
unlisteners = [unlistenStarted, unlistenProgress, unlistenCompleted, unlistenFailed];
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to setup download event listeners:', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop listening for events
|
|
||||||
export function stopDownloadEventListeners(): void {
|
|
||||||
for (const unlisten of unlisteners) {
|
|
||||||
unlisten();
|
|
||||||
}
|
|
||||||
unlisteners = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a download
|
|
||||||
export async function downloadTrack(track: {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
artist: string;
|
|
||||||
album?: string;
|
|
||||||
albumId?: string;
|
|
||||||
durationSecs: number;
|
|
||||||
quality: string;
|
|
||||||
bitDepth?: number;
|
|
||||||
sampleRate?: number;
|
|
||||||
}): Promise<void> {
|
|
||||||
try {
|
|
||||||
setDownloadState(track.id, { status: 'queued', progress: 0 });
|
|
||||||
await invoke('download_track', {
|
|
||||||
trackId: track.id,
|
|
||||||
title: track.title,
|
|
||||||
artist: track.artist,
|
|
||||||
album: track.album,
|
|
||||||
albumId: track.albumId,
|
|
||||||
durationSecs: track.durationSecs,
|
|
||||||
quality: track.quality,
|
|
||||||
bitDepth: track.bitDepth,
|
|
||||||
sampleRate: track.sampleRate,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to start download:', err);
|
|
||||||
setDownloadState(track.id, { status: 'failed', progress: 0, error: String(err) });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a download
|
|
||||||
export async function removeDownload(trackId: number): Promise<void> {
|
|
||||||
try {
|
|
||||||
await invoke('remove_downloaded_track', { trackId });
|
|
||||||
downloadStates.delete(trackId);
|
|
||||||
notifyListeners();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to remove download:', err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cache stats
|
|
||||||
export async function getDownloadCacheStats(): Promise<DownloadCacheStats> {
|
|
||||||
return invoke<DownloadCacheStats>('get_download_cache_stats');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all downloads
|
|
||||||
export async function clearDownloadCache(): Promise<void> {
|
|
||||||
await invoke('clear_download_cache');
|
|
||||||
downloadStates.clear();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open cache folder
|
|
||||||
export async function openDownloadCacheFolder(): Promise<void> {
|
|
||||||
await invoke('open_download_cache_folder');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cache limit
|
|
||||||
export async function setDownloadCacheLimit(limitMb: number | null): Promise<void> {
|
|
||||||
await invoke('set_download_cache_limit', { limitMb });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open containing folder for a specific album
|
|
||||||
export async function openAlbumFolder(albumId: string): Promise<void> {
|
|
||||||
await invoke('open_album_folder', { albumId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open containing folder for a specific track
|
|
||||||
export async function openTrackFolder(trackId: number): Promise<void> {
|
|
||||||
await invoke('open_track_folder', { trackId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-download a track (downloads again, overwriting if exists)
|
|
||||||
export async function reDownloadTrack(track: {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
artist: string;
|
|
||||||
album?: string;
|
|
||||||
albumId?: string;
|
|
||||||
durationSecs: number;
|
|
||||||
quality: string;
|
|
||||||
bitDepth?: number;
|
|
||||||
sampleRate?: number;
|
|
||||||
}): Promise<void> {
|
|
||||||
// Just call downloadTrack - backend handles overwriting
|
|
||||||
await downloadTrack(track);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
||||||
|
|
||||||
|
export type OfflineCacheStatus = 'none' | 'queued' | 'downloading' | 'ready' | 'failed';
|
||||||
|
|
||||||
|
export interface OfflineCacheInfo {
|
||||||
|
status: OfflineCacheStatus;
|
||||||
|
progress: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CachedTrackInfo {
|
||||||
|
trackId: number;
|
||||||
|
title: string;
|
||||||
|
artist: string;
|
||||||
|
album?: string;
|
||||||
|
albumId?: string;
|
||||||
|
durationSecs: number;
|
||||||
|
fileSizeBytes: number;
|
||||||
|
quality: string;
|
||||||
|
bitDepth?: number;
|
||||||
|
sampleRate?: number;
|
||||||
|
status: OfflineCacheStatus;
|
||||||
|
progressPercent: number;
|
||||||
|
errorMessage?: string;
|
||||||
|
createdAt: string;
|
||||||
|
lastAccessedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OfflineCacheStats {
|
||||||
|
totalTracks: number;
|
||||||
|
readyTracks: number;
|
||||||
|
downloadingTracks: number;
|
||||||
|
failedTracks: number;
|
||||||
|
totalSizeBytes: number;
|
||||||
|
limitBytes?: number;
|
||||||
|
cachePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track offline cache states by track ID
|
||||||
|
const offlineCacheStates = new Map<number, OfflineCacheInfo>();
|
||||||
|
|
||||||
|
// Listeners for state changes
|
||||||
|
const listeners = new Set<() => void>();
|
||||||
|
|
||||||
|
// Event unsubscribe functions
|
||||||
|
let unlisteners: UnlistenFn[] = [];
|
||||||
|
|
||||||
|
export function getOfflineCacheState(trackId: number): OfflineCacheInfo {
|
||||||
|
return offlineCacheStates.get(trackId) || { status: 'none', progress: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setOfflineCacheState(trackId: number, info: OfflineCacheInfo): void {
|
||||||
|
offlineCacheStates.set(trackId, info);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all tracks of an album are cached for offline
|
||||||
|
export function isAlbumFullyCached(trackIds: number[]): boolean {
|
||||||
|
if (trackIds.length === 0) return false;
|
||||||
|
|
||||||
|
return trackIds.every(trackId => {
|
||||||
|
const state = getOfflineCacheState(trackId);
|
||||||
|
return state.status === 'ready';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribe(listener: () => void): () => void {
|
||||||
|
listeners.add(listener);
|
||||||
|
return () => listeners.delete(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyListeners(): void {
|
||||||
|
for (const listener of listeners) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize offline cache states from backend
|
||||||
|
export async function initOfflineCacheStates(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const tracks = await invoke<CachedTrackInfo[]>('get_cached_tracks');
|
||||||
|
for (const track of tracks) {
|
||||||
|
offlineCacheStates.set(track.trackId, {
|
||||||
|
status: track.status,
|
||||||
|
progress: track.progressPercent,
|
||||||
|
error: track.errorMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to init offline cache states:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening for offline cache events
|
||||||
|
export async function startOfflineCacheEventListeners(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const unlistenStarted = await listen<{ trackId: number }>('offline:caching_started', (event) => {
|
||||||
|
console.log('Offline caching started:', event.payload.trackId);
|
||||||
|
setOfflineCacheState(event.payload.trackId, { status: 'downloading', progress: 0 });
|
||||||
|
});
|
||||||
|
|
||||||
|
const unlistenProgress = await listen<{
|
||||||
|
trackId: number;
|
||||||
|
progressPercent: number;
|
||||||
|
bytesDownloaded: number;
|
||||||
|
totalBytes?: number;
|
||||||
|
status: string;
|
||||||
|
}>('offline:caching_progress', (event) => {
|
||||||
|
const { trackId, progressPercent } = event.payload;
|
||||||
|
setOfflineCacheState(trackId, { status: 'downloading', progress: progressPercent });
|
||||||
|
});
|
||||||
|
|
||||||
|
const unlistenCompleted = await listen<{ trackId: number; size: number }>('offline:cached', (event) => {
|
||||||
|
console.log('Offline caching completed:', event.payload.trackId);
|
||||||
|
setOfflineCacheState(event.payload.trackId, { status: 'ready', progress: 100 });
|
||||||
|
});
|
||||||
|
|
||||||
|
const unlistenFailed = await listen<{ trackId: number; error: string }>('offline:cache_failed', (event) => {
|
||||||
|
console.error('Offline caching failed:', event.payload.trackId, event.payload.error);
|
||||||
|
setOfflineCacheState(event.payload.trackId, {
|
||||||
|
status: 'failed',
|
||||||
|
progress: 0,
|
||||||
|
error: event.payload.error,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
unlisteners = [unlistenStarted, unlistenProgress, unlistenCompleted, unlistenFailed];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to setup offline cache event listeners:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop listening for events
|
||||||
|
export function stopOfflineCacheEventListeners(): void {
|
||||||
|
for (const unlisten of unlisteners) {
|
||||||
|
unlisten();
|
||||||
|
}
|
||||||
|
unlisteners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache a track for offline listening
|
||||||
|
export async function cacheTrackForOffline(track: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
artist: string;
|
||||||
|
album?: string;
|
||||||
|
albumId?: string;
|
||||||
|
durationSecs: number;
|
||||||
|
quality: string;
|
||||||
|
bitDepth?: number;
|
||||||
|
sampleRate?: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
try {
|
||||||
|
setOfflineCacheState(track.id, { status: 'queued', progress: 0 });
|
||||||
|
await invoke('cache_track_for_offline', {
|
||||||
|
trackId: track.id,
|
||||||
|
title: track.title,
|
||||||
|
artist: track.artist,
|
||||||
|
album: track.album,
|
||||||
|
albumId: track.albumId,
|
||||||
|
durationSecs: track.durationSecs,
|
||||||
|
quality: track.quality,
|
||||||
|
bitDepth: track.bitDepth,
|
||||||
|
sampleRate: track.sampleRate,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to cache track for offline:', err);
|
||||||
|
setOfflineCacheState(track.id, { status: 'failed', progress: 0, error: String(err) });
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a track from offline cache
|
||||||
|
export async function removeCachedTrack(trackId: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
await invoke('remove_cached_track', { trackId });
|
||||||
|
offlineCacheStates.delete(trackId);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to remove cached track:', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get offline cache stats
|
||||||
|
export async function getOfflineCacheStats(): Promise<OfflineCacheStats> {
|
||||||
|
return invoke<OfflineCacheStats>('get_offline_cache_stats');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all offline cache
|
||||||
|
export async function clearOfflineCache(): Promise<void> {
|
||||||
|
await invoke('clear_offline_cache');
|
||||||
|
offlineCacheStates.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open offline cache folder
|
||||||
|
export async function openOfflineCacheFolder(): Promise<void> {
|
||||||
|
await invoke('open_offline_cache_folder');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set offline cache limit
|
||||||
|
export async function setOfflineCacheLimit(limitMb: number | null): Promise<void> {
|
||||||
|
await invoke('set_offline_cache_limit', { limitMb });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open containing folder for a specific album
|
||||||
|
export async function openAlbumFolder(albumId: string): Promise<void> {
|
||||||
|
await invoke('open_album_folder', { albumId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open containing folder for a specific track
|
||||||
|
export async function openTrackFolder(trackId: number): Promise<void> {
|
||||||
|
await invoke('open_track_folder', { trackId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh a cached track (re-cache, overwriting if exists)
|
||||||
|
export async function refreshCachedTrack(track: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
artist: string;
|
||||||
|
album?: string;
|
||||||
|
albumId?: string;
|
||||||
|
durationSecs: number;
|
||||||
|
quality: string;
|
||||||
|
bitDepth?: number;
|
||||||
|
sampleRate?: number;
|
||||||
|
}): Promise<void> {
|
||||||
|
// Just call cacheTrackForOffline - backend handles overwriting
|
||||||
|
await cacheTrackForOffline(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// COMPATIBILITY ALIASES - These maintain backward compatibility during refactor
|
||||||
|
// Will be removed once all references are updated
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/** @deprecated Use OfflineCacheStatus instead */
|
||||||
|
export type DownloadStatus = OfflineCacheStatus;
|
||||||
|
|
||||||
|
/** @deprecated Use OfflineCacheInfo instead */
|
||||||
|
export type DownloadInfo = OfflineCacheInfo;
|
||||||
|
|
||||||
|
/** @deprecated Use OfflineCacheStats instead */
|
||||||
|
export type DownloadCacheStats = OfflineCacheStats;
|
||||||
|
|
||||||
|
/** @deprecated Use getOfflineCacheState instead */
|
||||||
|
export const getDownloadState = getOfflineCacheState;
|
||||||
|
|
||||||
|
/** @deprecated Use setOfflineCacheState instead */
|
||||||
|
export const setDownloadState = setOfflineCacheState;
|
||||||
|
|
||||||
|
/** @deprecated Use isAlbumFullyCached instead */
|
||||||
|
export const isAlbumFullyDownloaded = isAlbumFullyCached;
|
||||||
|
|
||||||
|
/** @deprecated Use initOfflineCacheStates instead */
|
||||||
|
export const initDownloadStates = initOfflineCacheStates;
|
||||||
|
|
||||||
|
/** @deprecated Use startOfflineCacheEventListeners instead */
|
||||||
|
export const startDownloadEventListeners = startOfflineCacheEventListeners;
|
||||||
|
|
||||||
|
/** @deprecated Use stopOfflineCacheEventListeners instead */
|
||||||
|
export const stopDownloadEventListeners = stopOfflineCacheEventListeners;
|
||||||
|
|
||||||
|
/** @deprecated Use cacheTrackForOffline instead */
|
||||||
|
export const downloadTrack = cacheTrackForOffline;
|
||||||
|
|
||||||
|
/** @deprecated Use removeCachedTrack instead */
|
||||||
|
export const removeDownload = removeCachedTrack;
|
||||||
|
|
||||||
|
/** @deprecated Use getOfflineCacheStats instead */
|
||||||
|
export const getDownloadCacheStats = getOfflineCacheStats;
|
||||||
|
|
||||||
|
/** @deprecated Use clearOfflineCache instead */
|
||||||
|
export const clearDownloadCache = clearOfflineCache;
|
||||||
|
|
||||||
|
/** @deprecated Use openOfflineCacheFolder instead */
|
||||||
|
export const openDownloadCacheFolder = openOfflineCacheFolder;
|
||||||
|
|
||||||
|
/** @deprecated Use setOfflineCacheLimit instead */
|
||||||
|
export const setDownloadCacheLimit = setOfflineCacheLimit;
|
||||||
|
|
||||||
|
/** @deprecated Use refreshCachedTrack instead */
|
||||||
|
export const reDownloadTrack = refreshCachedTrack;
|
||||||
+32
-32
@@ -4,19 +4,19 @@
|
|||||||
import { listen, emitTo, type UnlistenFn } from '@tauri-apps/api/event';
|
import { listen, emitTo, type UnlistenFn } from '@tauri-apps/api/event';
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
|
|
||||||
// Download state management
|
// Offline cache state management
|
||||||
import {
|
import {
|
||||||
initDownloadStates,
|
initOfflineCacheStates,
|
||||||
startDownloadEventListeners,
|
startOfflineCacheEventListeners,
|
||||||
stopDownloadEventListeners,
|
stopOfflineCacheEventListeners,
|
||||||
downloadTrack,
|
cacheTrackForOffline,
|
||||||
removeDownload,
|
removeCachedTrack,
|
||||||
getDownloadState,
|
getOfflineCacheState,
|
||||||
openAlbumFolder,
|
openAlbumFolder,
|
||||||
openTrackFolder,
|
openTrackFolder,
|
||||||
subscribe as subscribeDownloads,
|
subscribe as subscribeOfflineCache,
|
||||||
type DownloadStatus
|
type OfflineCacheStatus
|
||||||
} from '$lib/stores/downloadState';
|
} from '$lib/stores/offlineCacheState';
|
||||||
|
|
||||||
// Toast state management
|
// Toast state management
|
||||||
import {
|
import {
|
||||||
@@ -652,7 +652,7 @@
|
|||||||
if (!album) return;
|
if (!album) return;
|
||||||
|
|
||||||
const tracksToDownload = album.tracks.filter(track => {
|
const tracksToDownload = album.tracks.filter(track => {
|
||||||
const status = getDownloadState(track.id).status;
|
const status = getOfflineCacheState(track.id).status;
|
||||||
return status === 'none' || status === 'failed';
|
return status === 'none' || status === 'failed';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -665,7 +665,7 @@
|
|||||||
|
|
||||||
for (const track of tracksToDownload) {
|
for (const track of tracksToDownload) {
|
||||||
try {
|
try {
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || album.artist || 'Unknown',
|
artist: track.artist || album.artist || 'Unknown',
|
||||||
@@ -1216,7 +1216,7 @@
|
|||||||
// Download handlers
|
// Download handlers
|
||||||
async function handleTrackDownload(track: Track) {
|
async function handleTrackDownload(track: Track) {
|
||||||
try {
|
try {
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || selectedAlbum?.artist || 'Unknown',
|
artist: track.artist || selectedAlbum?.artist || 'Unknown',
|
||||||
@@ -1236,7 +1236,7 @@
|
|||||||
|
|
||||||
async function handleTrackRemoveDownload(trackId: number) {
|
async function handleTrackRemoveDownload(trackId: number) {
|
||||||
try {
|
try {
|
||||||
await removeDownload(trackId);
|
await removeCachedTrack(trackId);
|
||||||
showToast('Removed from offline library', 'info');
|
showToast('Removed from offline library', 'info');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to remove from offline:', err);
|
console.error('Failed to remove from offline:', err);
|
||||||
@@ -1256,7 +1256,7 @@
|
|||||||
async function handleTrackReDownload(track: Track | DisplayTrack) {
|
async function handleTrackReDownload(track: Track | DisplayTrack) {
|
||||||
try {
|
try {
|
||||||
// Re-download uses the same download function - backend handles overwriting
|
// Re-download uses the same download function - backend handles overwriting
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || selectedAlbum?.artist || 'Unknown',
|
artist: track.artist || selectedAlbum?.artist || 'Unknown',
|
||||||
@@ -1275,14 +1275,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkTrackDownloaded(trackId: number): boolean {
|
function checkTrackDownloaded(trackId: number): boolean {
|
||||||
return getDownloadState(trackId).status === 'ready';
|
return getOfflineCacheState(trackId).status === 'ready';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownloadAlbum() {
|
async function handleDownloadAlbum() {
|
||||||
if (!selectedAlbum) return;
|
if (!selectedAlbum) return;
|
||||||
|
|
||||||
const tracksToDownload = selectedAlbum.tracks.filter(track => {
|
const tracksToDownload = selectedAlbum.tracks.filter(track => {
|
||||||
const status = getDownloadState(track.id).status;
|
const status = getOfflineCacheState(track.id).status;
|
||||||
return status === 'none' || status === 'failed';
|
return status === 'none' || status === 'failed';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1295,7 +1295,7 @@
|
|||||||
|
|
||||||
for (const track of tracksToDownload) {
|
for (const track of tracksToDownload) {
|
||||||
try {
|
try {
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || selectedAlbum.artist || 'Unknown',
|
artist: track.artist || selectedAlbum.artist || 'Unknown',
|
||||||
@@ -1330,7 +1330,7 @@
|
|||||||
|
|
||||||
for (const track of selectedAlbum.tracks) {
|
for (const track of selectedAlbum.tracks) {
|
||||||
try {
|
try {
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || selectedAlbum.artist || 'Unknown',
|
artist: track.artist || selectedAlbum.artist || 'Unknown',
|
||||||
@@ -1368,7 +1368,7 @@
|
|||||||
|
|
||||||
for (const track of album.tracks.data) {
|
for (const track of album.tracks.data) {
|
||||||
try {
|
try {
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.performer?.name || album.artist?.name || 'Unknown',
|
artist: track.performer?.name || album.artist?.name || 'Unknown',
|
||||||
@@ -1389,10 +1389,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrackDownloadStatus(trackId: number) {
|
function getTrackOfflineCacheStatus(trackId: number) {
|
||||||
// Access downloadStateVersion to trigger reactivity
|
// Access downloadStateVersion to trigger reactivity
|
||||||
void downloadStateVersion;
|
void downloadStateVersion;
|
||||||
return getDownloadState(trackId);
|
return getOfflineCacheState(trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDisplayTrackDownload(track: PlaylistTrack) {
|
async function handleDisplayTrackDownload(track: PlaylistTrack) {
|
||||||
@@ -1402,7 +1402,7 @@
|
|||||||
: track.hires
|
: track.hires
|
||||||
? 'Hi-Res'
|
? 'Hi-Res'
|
||||||
: '-';
|
: '-';
|
||||||
await downloadTrack({
|
await cacheTrackForOffline({
|
||||||
id: track.id,
|
id: track.id,
|
||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist || 'Unknown',
|
artist: track.artist || 'Unknown',
|
||||||
@@ -1844,7 +1844,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAlbumDownloadStatus(albumId: string): boolean {
|
function getAlbumOfflineCacheStatus(albumId: string): boolean {
|
||||||
void downloadStateVersion;
|
void downloadStateVersion;
|
||||||
return albumDownloadCache.get(albumId) || false;
|
return albumDownloadCache.get(albumId) || false;
|
||||||
}
|
}
|
||||||
@@ -1871,8 +1871,8 @@
|
|||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
|
||||||
// Initialize download states
|
// Initialize download states
|
||||||
initDownloadStates();
|
initOfflineCacheStates();
|
||||||
startDownloadEventListeners();
|
startOfflineCacheEventListeners();
|
||||||
|
|
||||||
// Initialize playback context and preferences stores
|
// Initialize playback context and preferences stores
|
||||||
initPlaybackContextStore();
|
initPlaybackContextStore();
|
||||||
@@ -1892,7 +1892,7 @@
|
|||||||
// This prevents API calls before authentication is complete
|
// This prevents API calls before authentication is complete
|
||||||
|
|
||||||
// Subscribe to download state changes to trigger reactivity
|
// Subscribe to download state changes to trigger reactivity
|
||||||
const unsubscribeDownloads = subscribeDownloads(() => {
|
const unsubscribeOfflineCache = subscribeOfflineCache(() => {
|
||||||
downloadStateVersion++;
|
downloadStateVersion++;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2109,8 +2109,8 @@
|
|||||||
document.removeEventListener('keydown', handleKeydown);
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
stopDownloadEventListeners();
|
stopOfflineCacheEventListeners();
|
||||||
unsubscribeDownloads();
|
unsubscribeOfflineCache();
|
||||||
unsubscribeToast();
|
unsubscribeToast();
|
||||||
unsubscribeUI();
|
unsubscribeUI();
|
||||||
unsubscribeAuth();
|
unsubscribeAuth();
|
||||||
@@ -2330,7 +2330,7 @@
|
|||||||
onTrackDownload={handleTrackDownload}
|
onTrackDownload={handleTrackDownload}
|
||||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||||
onTrackReDownload={handleTrackReDownload}
|
onTrackReDownload={handleTrackReDownload}
|
||||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
getTrackOfflineCacheStatus={getTrackOfflineCacheStatus}
|
||||||
onDownloadAlbum={handleDownloadAlbum}
|
onDownloadAlbum={handleDownloadAlbum}
|
||||||
onShareAlbumQobuz={shareAlbumQobuzLink}
|
onShareAlbumQobuz={shareAlbumQobuzLink}
|
||||||
onShareAlbumSonglink={shareAlbumSonglink}
|
onShareAlbumSonglink={shareAlbumSonglink}
|
||||||
@@ -2398,7 +2398,7 @@
|
|||||||
onTrackDownload={handleDisplayTrackDownload}
|
onTrackDownload={handleDisplayTrackDownload}
|
||||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||||
onTrackReDownload={handleDisplayTrackDownload}
|
onTrackReDownload={handleDisplayTrackDownload}
|
||||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
getTrackOfflineCacheStatus={getTrackOfflineCacheStatus}
|
||||||
{downloadStateVersion}
|
{downloadStateVersion}
|
||||||
onLocalTrackPlay={handleLocalTrackPlay}
|
onLocalTrackPlay={handleLocalTrackPlay}
|
||||||
onLocalTrackPlayNext={queueLocalTrackNext}
|
onLocalTrackPlayNext={queueLocalTrackNext}
|
||||||
@@ -2445,7 +2445,7 @@
|
|||||||
onTrackDownload={handleDisplayTrackDownload}
|
onTrackDownload={handleDisplayTrackDownload}
|
||||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||||
onTrackReDownload={handleDisplayTrackDownload}
|
onTrackReDownload={handleDisplayTrackDownload}
|
||||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
getTrackOfflineCacheStatus={getTrackOfflineCacheStatus}
|
||||||
onPlaylistSelect={selectPlaylist}
|
onPlaylistSelect={selectPlaylist}
|
||||||
selectedTab={getFavoritesTabFromView(activeView) ?? favoritesDefaultTab}
|
selectedTab={getFavoritesTabFromView(activeView) ?? favoritesDefaultTab}
|
||||||
onTabNavigate={(tab) => navigateToFavorites(tab)}
|
onTabNavigate={(tab) => navigateToFavorites(tab)}
|
||||||
|
|||||||
Reference in new issue
Block a user