fix(downloads): queue the whole album, and make every queued track findable offline
An album download put a handful of its tracks on the device while the button reported the album as downloaded. Two independent gaps, one shared cause. - `download_album` read its track list from `items WHERE album_id = ?` — the local catalog cache. Jellyfin does not return `AlbumId` on every listing endpoint, so tracks cached from one of those sit in `items` with a NULL `album_id` and are invisible to that query. On the reported database three whole albums (18, 12 and 9 tracks) had it NULL on every track; a partially linked album queued only the linked subset. - The frontend then resolved one stream URL per track from its own list and paired it with the returned row ids by position. The ids came back in the backend's `index_number` order over a different set of rows, so a row could be handed another track's URL and any track past the end of the shorter list was never started. On Android that loop also stopped wherever the webview was suspended. - `album_id` is what `OfflineRepository::get_items` joins a track to its album on, so a track that did download stayed invisible under its album offline — the same missing link seen from the other side. The operation now belongs to Rust end to end: - `HybridRepository::get_album_tracks` asks the server what the album contains. Cache-first `get_items` is right for browsing and wrong for deciding what to download; it errors offline so the caller falls back to the ungated local catalog, keeping the queue-while-offline flow. - `queue_album_tracks` writes the album link onto every track it queues, and creates an `items` row for tracks the cache has never seen. - Stream URLs resolve here, through the existing reconnect resolver, now scoped to the rows just queued so one album cannot start every unrelated pending row. Only the album id crosses the IPC boundary. - `album_file_names` gives each track its own file. A title repeated inside one album (deluxe edition, two discs) mapped to one path, so those downloads overwrote each other. Re-tapping download on a broken album heals it: missing tracks are queued and the tracks already on disk get their link. `download_series`/`download_season` still derive their episode lists from the cache the same way and want the same treatment. DR-173, UT-170..172. Rust 673 tests, frontend 975 tests, svelte-check and check:boundary clean. Note: this tree is shared with a concurrent session. Only the files above are committed; docs/traceability.md is left to be regenerated once that work lands.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
|
||||
interface Props {
|
||||
@@ -83,28 +82,14 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Download the album: queue all tracks, then start each one
|
||||
// Download the album. One call: the backend lists the album's tracks
|
||||
// from the server, queues every one of them, resolves each stream URL
|
||||
// and pumps the queue. This page's `tracks` are what the user is
|
||||
// looking at, not the download list — pairing them against the returned
|
||||
// ids by position is what used to leave most of an album unqueued.
|
||||
const repo = auth.getRepository();
|
||||
const basePath = `albums/${albumId}`;
|
||||
const downloadIds = await downloads.downloadAlbum(albumId, userId, basePath);
|
||||
|
||||
// Get target directory for downloads
|
||||
const targetDir = await commands.storageGetPath();
|
||||
|
||||
// Enqueue each track with its resolved stream URL. The backend queue
|
||||
// pump starts up to max_concurrent at a time and advances through the
|
||||
// rest automatically as slots free up — so we never hit (and silently
|
||||
// drop) the concurrency limit the way startDownload did.
|
||||
for (let i = 0; i < tracks.length && i < downloadIds.length; i++) {
|
||||
try {
|
||||
const streamUrl = await repo.getAudioStreamUrl(tracks[i].id);
|
||||
if (streamUrl) {
|
||||
await commands.enqueueDownload(downloadIds[i], streamUrl, targetDir);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Failed to enqueue download for track ${tracks[i].id}:`, e);
|
||||
}
|
||||
}
|
||||
await downloads.downloadAlbum(repo.getHandle(), albumId, userId, basePath);
|
||||
|
||||
// Refresh to get updated statuses
|
||||
await downloads.refresh(userId);
|
||||
|
||||
Reference in New Issue
Block a user