mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
b4c8ed4b65
* fix(sidebar): keep offline-download toast from squishing in a short window The toast lives in the sidebar nav flex column; without flex-shrink: 0 the column compressed it vertically when the main window was small. The label now also ellipsis-truncates instead of overflowing on a narrow sidebar. * fix(offline): make offline downloads cancellable down to the Rust transfer A running offline download could not be stopped — the sidebar X button only dropped not-yet-started tracks between batches of 8, and the Rust transfer had no cancellation path at all, so in-flight HTTP streams always ran to completion. Add an offline_cancel_flags() registry (mirroring sync_cancel_flags for the device-sync side) plus additive cancel_offline_downloads / clear_offline_cancel commands. download_track_offline takes an optional download_id, checks the flag right after acquiring its semaphore slot, and threads it through finalize_streamed_download / stream_to_file so an in-flight stream aborts at the next chunk — the partial .part file is cleaned up by the existing error path. * fix(offline): cancel per-track and clear the sidebar toast immediately downloadAlbum tags each run with a downloadId, checks for cancellation before every track instead of once per 8-track batch (which never re-ran for albums of 8 or fewer tracks), and persists tracks that finished before the cancel so they are not orphaned on disk. cancelDownload / cancelAllDownloads drop every job for the album and call cancel_offline_downloads so Rust aborts the in-flight transfers — the toast disappears at once instead of lingering on stuck rows. Adds offlineJobStore cancellation tests. * docs(changelog): offline download cancel button + toast sizing fixes
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
import { create } from 'zustand';
|
|
import { invoke } from '@tauri-apps/api/core';
|
|
|
|
export interface DownloadJob {
|
|
trackId: string;
|
|
albumId: string;
|
|
albumName: string;
|
|
trackTitle: string;
|
|
trackIndex: number;
|
|
totalTracks: number;
|
|
status: 'queued' | 'downloading' | 'done' | 'error';
|
|
/** Unique per `downloadAlbum` run — keys the Rust-side cancellation flag. */
|
|
downloadId: string;
|
|
}
|
|
|
|
interface OfflineJobState {
|
|
jobs: DownloadJob[];
|
|
bulkProgress: Record<string, { done: number; total: number }>;
|
|
cancelDownload: (albumId: string) => void;
|
|
cancelAllDownloads: () => void;
|
|
}
|
|
|
|
// Module-level cancellation set — checked by downloadAlbum before each track.
|
|
export const cancelledDownloads = new Set<string>();
|
|
|
|
/** Tells Rust to abort any in-flight `download_track_offline` calls for these jobs. */
|
|
function abortDownloadsInRust(jobs: DownloadJob[]) {
|
|
const downloadIds = [...new Set(jobs.map(j => j.downloadId).filter(Boolean))];
|
|
if (downloadIds.length > 0) {
|
|
invoke('cancel_offline_downloads', { downloadIds }).catch(() => {});
|
|
}
|
|
}
|
|
|
|
export const useOfflineJobStore = create<OfflineJobState>()((set, get) => ({
|
|
jobs: [],
|
|
bulkProgress: {},
|
|
|
|
cancelDownload: (albumId) => {
|
|
cancelledDownloads.add(albumId);
|
|
// Abort the in-flight Rust transfers, then drop every job for this album
|
|
// (queued AND downloading) so the sidebar toast clears right away.
|
|
abortDownloadsInRust(get().jobs.filter(j => j.albumId === albumId));
|
|
set(state => ({
|
|
jobs: state.jobs.filter(j => j.albumId !== albumId),
|
|
}));
|
|
},
|
|
|
|
cancelAllDownloads: () => {
|
|
const active = get().jobs.filter(
|
|
j => j.status === 'queued' || j.status === 'downloading',
|
|
);
|
|
[...new Set(active.map(j => j.albumId))].forEach(id => cancelledDownloads.add(id));
|
|
abortDownloadsInRust(active);
|
|
// Keep only already-settled jobs (done/error) — the active ones are gone,
|
|
// so the toast disappears instead of lingering on stuck "downloading" rows.
|
|
set(state => ({
|
|
jobs: state.jobs.filter(j => j.status !== 'queued' && j.status !== 'downloading'),
|
|
}));
|
|
},
|
|
}));
|