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:
@@ -119,6 +119,40 @@ impl HybridRepository {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Every track of an album, asked of the **server** rather than the cache.
|
||||
///
|
||||
/// Deliberately not `get_items`, which is cache-first: it answers from SQLite
|
||||
/// the moment the cache has any content. That is right for browsing and wrong
|
||||
/// for deciding what to download, because a partial or unlinked cache then
|
||||
/// decides how much of the album gets queued while the user is told the whole
|
||||
/// album is downloading. Downloading is the one operation that must know the
|
||||
/// album's *complete* contents.
|
||||
///
|
||||
/// Errors when the server cannot answer (offline); the caller falls back to
|
||||
/// the local catalog and the rows are queued either way, resolving on
|
||||
/// reconnect. Server results are written back to the cache, so browsing
|
||||
/// benefits from the round trip too.
|
||||
///
|
||||
/// TRACES: UR-018, UR-055 | DR-173
|
||||
pub async fn get_album_tracks(&self, album_id: &str) -> Result<Vec<MediaItem>, RepoError> {
|
||||
let options = Some(GetItemsOptions {
|
||||
include_item_types: Some(vec!["Audio".to_string()]),
|
||||
sort_by: Some("ParentIndexNumber,IndexNumber,SortName".to_string()),
|
||||
limit: Some(1000),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let result = self.online.get_items(album_id, options).await?;
|
||||
|
||||
if !result.items.is_empty() {
|
||||
if let Err(e) = self.offline.save_to_cache(album_id, &result.items).await {
|
||||
warn!("[HybridRepo] Failed to cache album tracks: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result.items)
|
||||
}
|
||||
|
||||
/// Search only the local SQLite cache (downloaded content).
|
||||
///
|
||||
/// Fast (100ms timeout) — used to render instant results before the server
|
||||
|
||||
Reference in New Issue
Block a user