feat(downloads): add album download status to grid overlays (partial)
- Add backend command to check if album is fully downloaded - Add frontend functions to check album download status - Pass download handlers to HomeView - Update HomeView AlbumCards with download status props cp
This commit is contained in:
1 parent
932d13829e
commit
7952bbb7d6
4 files changed
+165
-8
No files matched your search
@@ -479,6 +479,35 @@ pub async fn open_album_folder(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if an album is fully downloaded (all tracks ready)
|
||||
#[tauri::command]
|
||||
pub async fn check_album_fully_downloaded(
|
||||
album_id: String,
|
||||
cache_state: State<'_, DownloadCacheState>,
|
||||
) -> Result<bool, String> {
|
||||
let db = cache_state.db.lock().await;
|
||||
|
||||
// Get all tracks for this album
|
||||
let tracks = db.get_all_tracks()?;
|
||||
let album_tracks: Vec<_> = tracks
|
||||
.into_iter()
|
||||
.filter(|t| t.album_id.as_deref() == Some(&album_id))
|
||||
.collect();
|
||||
|
||||
if album_tracks.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Check if all tracks are ready
|
||||
for track in album_tracks {
|
||||
if track.status != crate::download_cache::DownloadStatus::Ready {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Evict tracks if cache exceeds limit (LRU policy)
|
||||
async fn evict_if_needed(
|
||||
cache_state: &DownloadCacheState,
|
||||
|
||||
@@ -443,6 +443,7 @@ pub fn run() {
|
||||
download_cache::commands::set_download_cache_limit,
|
||||
download_cache::commands::open_download_cache_folder,
|
||||
download_cache::commands::open_album_folder,
|
||||
download_cache::commands::check_album_fully_downloaded,
|
||||
download_cache::commands::check_download_root_mounted,
|
||||
download_cache::commands::validate_download_path,
|
||||
download_cache::commands::move_downloads_to_path,
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
onAlbumShareQobuz?: (albumId: string) => void;
|
||||
onAlbumShareSonglink?: (albumId: string) => void;
|
||||
onAlbumDownload?: (albumId: string) => void;
|
||||
onOpenAlbumFolder?: (albumId: string) => void;
|
||||
onReDownloadAlbum?: (albumId: string) => void;
|
||||
checkAlbumFullyDownloaded?: (albumId: string) => Promise<boolean>;
|
||||
downloadStateVersion?: number;
|
||||
onArtistClick?: (artistId: number) => void;
|
||||
onTrackPlay?: (track: DisplayTrack) => void;
|
||||
}
|
||||
@@ -67,6 +71,10 @@
|
||||
onAlbumShareQobuz,
|
||||
onAlbumShareSonglink,
|
||||
onAlbumDownload,
|
||||
onOpenAlbumFolder,
|
||||
onReDownloadAlbum,
|
||||
checkAlbumFullyDownloaded,
|
||||
downloadStateVersion,
|
||||
onArtistClick,
|
||||
onTrackPlay
|
||||
}: Props = $props();
|
||||
@@ -132,6 +140,25 @@
|
||||
|
||||
let failedArtistImages = $state<Set<number>>(new Set());
|
||||
|
||||
// Download status tracking
|
||||
let albumDownloadStatuses = $state<Map<string, boolean>>(new Map());
|
||||
|
||||
async function loadAlbumDownloadStatus(albumId: string) {
|
||||
if (!checkAlbumFullyDownloaded) return false;
|
||||
try {
|
||||
const isDownloaded = await checkAlbumFullyDownloaded(albumId);
|
||||
albumDownloadStatuses.set(albumId, isDownloaded);
|
||||
return isDownloaded;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isAlbumDownloaded(albumId: string): boolean {
|
||||
void downloadStateVersion;
|
||||
return albumDownloadStatuses.get(albumId) || false;
|
||||
}
|
||||
|
||||
const hasContent = $derived(
|
||||
newReleases.length > 0
|
||||
|| pressAwards.length > 0
|
||||
@@ -510,7 +537,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -534,7 +564,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -558,7 +591,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -582,7 +618,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -606,7 +645,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -630,7 +672,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
@@ -711,7 +756,10 @@
|
||||
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
|
||||
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
|
||||
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
|
||||
onclick={() => onAlbumClick?.(album.id)}
|
||||
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
|
||||
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
|
||||
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
|
||||
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
|
||||
/>
|
||||
{/each}
|
||||
<div class="spacer"></div>
|
||||
|
||||
+80
-1
@@ -1012,6 +1012,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function openAlbumFolderById(albumId: string) {
|
||||
try {
|
||||
await openAlbumFolder(albumId);
|
||||
} catch (err) {
|
||||
console.error('Failed to open album folder:', err);
|
||||
showToast('Failed to open album folder', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function reDownloadAlbumById(albumId: string) {
|
||||
try {
|
||||
const album = await invoke<QobuzAlbum>('get_album', { albumId });
|
||||
if (!album || !album.tracks || album.tracks.data.length === 0) {
|
||||
showToast('Failed to load album for re-download', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(`Re-downloading all tracks from "${album.title}"`, 'info');
|
||||
|
||||
for (const track of album.tracks.data) {
|
||||
try {
|
||||
await downloadTrack({
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
artist: track.performer?.name || album.artist?.name || 'Unknown',
|
||||
album: album.title,
|
||||
albumId: album.id,
|
||||
durationSecs: track.duration,
|
||||
quality: track.hires ? 'Hi-Res' : '-',
|
||||
bitDepth: track.maximum_bit_depth,
|
||||
sampleRate: track.maximum_sampling_rate,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Failed to queue re-download for "${track.title}":`, err);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load album:', err);
|
||||
showToast('Failed to load album for re-download', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function getTrackDownloadStatus(trackId: number) {
|
||||
// Access downloadStateVersion to trigger reactivity
|
||||
void downloadStateVersion;
|
||||
@@ -1398,6 +1440,28 @@
|
||||
// Download state update trigger
|
||||
let downloadStateVersion = $state(0);
|
||||
|
||||
// Cache for album download statuses
|
||||
const albumDownloadCache = new Map<string, boolean>();
|
||||
|
||||
async function checkAlbumFullyDownloaded(albumId: string): Promise<boolean> {
|
||||
// Trigger reactivity with downloadStateVersion
|
||||
void downloadStateVersion;
|
||||
|
||||
try {
|
||||
const isDownloaded = await invoke<boolean>('check_album_fully_downloaded', { albumId });
|
||||
albumDownloadCache.set(albumId, isDownloaded);
|
||||
return isDownloaded;
|
||||
} catch {
|
||||
albumDownloadCache.set(albumId, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getAlbumDownloadStatus(albumId: string): boolean {
|
||||
void downloadStateVersion;
|
||||
return albumDownloadCache.get(albumId) || false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// Bootstrap app (theme, mouse nav, Last.fm restore)
|
||||
const { cleanup: cleanupBootstrap } = bootstrapApp();
|
||||
@@ -1719,6 +1783,10 @@
|
||||
onAlbumShareQobuz={shareAlbumQobuzLinkById}
|
||||
onAlbumShareSonglink={shareAlbumSonglinkById}
|
||||
onAlbumDownload={downloadAlbumById}
|
||||
onOpenAlbumFolder={openAlbumFolderById}
|
||||
onReDownloadAlbum={reDownloadAlbumById}
|
||||
checkAlbumFullyDownloaded={checkAlbumFullyDownloaded}
|
||||
{downloadStateVersion}
|
||||
onArtistClick={handleArtistClick}
|
||||
onTrackPlay={handleDisplayTrackPlay}
|
||||
/>
|
||||
@@ -1731,6 +1799,10 @@
|
||||
onAlbumShareQobuz={shareAlbumQobuzLinkById}
|
||||
onAlbumShareSonglink={shareAlbumSonglinkById}
|
||||
onAlbumDownload={downloadAlbumById}
|
||||
onOpenAlbumFolder={openAlbumFolderById}
|
||||
onReDownloadAlbum={reDownloadAlbumById}
|
||||
checkAlbumFullyDownloaded={checkAlbumFullyDownloaded}
|
||||
{downloadStateVersion}
|
||||
onTrackPlay={handleTrackPlay}
|
||||
onTrackPlayNext={queueQobuzTrackNext}
|
||||
onTrackPlayLater={queueQobuzTrackLater}
|
||||
@@ -1790,6 +1862,10 @@
|
||||
onAlbumShareQobuz={shareAlbumQobuzLinkById}
|
||||
onAlbumShareSonglink={shareAlbumSonglinkById}
|
||||
onAlbumDownload={downloadAlbumById}
|
||||
onOpenAlbumFolder={openAlbumFolderById}
|
||||
onReDownloadAlbum={reDownloadAlbumById}
|
||||
checkAlbumFullyDownloaded={checkAlbumFullyDownloaded}
|
||||
{downloadStateVersion}
|
||||
onLoadMore={loadMoreArtistAlbums}
|
||||
isLoadingMore={isArtistAlbumsLoading}
|
||||
onTrackPlay={handleDisplayTrackPlay}
|
||||
@@ -1845,6 +1921,10 @@
|
||||
onAlbumShareQobuz={shareAlbumQobuzLinkById}
|
||||
onAlbumShareSonglink={shareAlbumSonglinkById}
|
||||
onAlbumDownload={downloadAlbumById}
|
||||
onOpenAlbumFolder={openAlbumFolderById}
|
||||
onReDownloadAlbum={reDownloadAlbumById}
|
||||
checkAlbumFullyDownloaded={checkAlbumFullyDownloaded}
|
||||
{downloadStateVersion}
|
||||
onTrackPlay={handleDisplayTrackPlay}
|
||||
onArtistClick={handleArtistClick}
|
||||
onTrackPlayNext={queuePlaylistTrackNext}
|
||||
@@ -1858,7 +1938,6 @@
|
||||
onTrackDownload={handleDisplayTrackDownload}
|
||||
onTrackRemoveDownload={handleTrackRemoveDownload}
|
||||
getTrackDownloadStatus={getTrackDownloadStatus}
|
||||
{downloadStateVersion}
|
||||
/>
|
||||
{:else if activeView === 'playlist-manager'}
|
||||
<PlaylistManagerView
|
||||
|
||||
Reference in new issue
Block a user