fix(offline): one server-only rule for both library views
Two defects with one cause: "server only" was a private $derived inside MediaCard. The list view (what LibraryGrid renders when the stored view preference is list) had no notion of it at all, so a library browsed as a list offline showed every revealed item as an ordinary tappable row that plays nothing, with no way to queue it. And the rule asked the downloads store whether *this item id* was downloaded — but only a playable leaf (Audio, Movie, Episode) ever has a download row. An album's tracks carry them, the album does not, so a fully downloaded album greyed itself out and offered to queue what was already on the device. The rule moves to the pure $lib/utils/serverOnly and both views call it. The container half is answered by the backend rather than guessed at: get_download_disk_usage().sizes already carries container subtotals beside leaf sizes (DR-085), so deviceContentIds is membership in a Rust-computed map, not a frontend list of which item types are containers. That map was loaded only by the Downloads page, so the shell primes it at startup and re-reads it whenever the offline gate settles. Queueing is shared too, since the list view had no copy to diverge from. TRACES: UR-052, UR-055 | DR-292 | UT-257, UT-258
This commit is contained in:
@@ -112,6 +112,22 @@ function createDownloadedCatalogStore() {
|
||||
|
||||
export const downloadedCatalog = createDownloadedCatalogStore();
|
||||
|
||||
/**
|
||||
* Every item id the device holds bytes for — playable leaves *and* the
|
||||
* containers above them, as the backend's disk-usage map reports them.
|
||||
*
|
||||
* This is what stops the offline browse greying out a fully downloaded album:
|
||||
* only a leaf (Audio, Movie, Episode) ever has a download row of its own, so
|
||||
* asking the downloads store about an album id always answered "no". Which ids
|
||||
* are containers, and which children roll up into them, stays a Rust question
|
||||
* (`get_download_disk_usage`); the frontend only reads membership.
|
||||
*
|
||||
* Refreshed with the rest of the catalog — see `downloadedCatalog.refresh()`.
|
||||
*
|
||||
* TRACES: UR-052, UR-056 | DR-292
|
||||
*/
|
||||
export const deviceContentIds = derived(downloadedCatalog, ($c) => new Set(Object.keys($c.sizes)));
|
||||
|
||||
export const downloadedLibraries = derived(downloadedCatalog, ($c) => $c.libraries);
|
||||
export const downloadedDeviceTotal = derived(downloadedCatalog, ($c) => $c.deviceTotalBytes);
|
||||
export const downloadedItemCount = derived(downloadedCatalog, ($c) => $c.itemCount);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Queue a server-only item for download on the next reconnect.
|
||||
*
|
||||
* Offline this only persists a `pending` downloads row with no `stream_url`;
|
||||
* the reconnect handler resolves the URL and the pump starts it (see
|
||||
* `offlineCatalog`). Shared by the grid card and the list row so the two
|
||||
* surfaces queue identically — the list view previously had no way to queue at
|
||||
* all.
|
||||
*
|
||||
* TRACES: UR-052 | DR-292
|
||||
*/
|
||||
|
||||
import type { MediaItem } from "$lib/api/types";
|
||||
import { downloads } from "$lib/stores/downloads";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
|
||||
export async function queueOfflineDownload(item: MediaItem): Promise<void> {
|
||||
const userId = auth.getUserId();
|
||||
if (!userId) throw new Error("Not signed in");
|
||||
|
||||
// A sensible on-disk path; the backend heals `stream_url` on reconnect.
|
||||
const filePath = `downloads/${item.id}`;
|
||||
await downloads.downloadItem(
|
||||
item.id,
|
||||
userId,
|
||||
filePath,
|
||||
undefined,
|
||||
undefined,
|
||||
item.name,
|
||||
item.artists?.join(", ") ?? undefined,
|
||||
item.albumName ?? undefined,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user