From 8938e3fdbaeca630e29b3687b45f842358010e00 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 3 Jul 2026 18:47:50 +0200 Subject: [PATCH 1/3] Android launcher: drop monochrome (themed) icon, keep color only The monochrome adaptive-icon layer produced a poor themed-icon rendering. Remove the reference from mipmap-anydpi-v26/ic_launcher.xml and delete the ic_launcher_monochrome.png files so Android always uses the color adaptive icon (background + foreground). sync-android-sources.sh also drops any monochrome layer Tauri regenerates. Co-Authored-By: Claude Opus 4.8 --- scripts/sync-android-sources.sh | 5 +++++ .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 1 - .../res/mipmap-hdpi/ic_launcher_monochrome.png | Bin 8926 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_monochrome.png | Bin 4166 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_monochrome.png | Bin 15610 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_monochrome.png | Bin 32735 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_monochrome.png | Bin 61028 -> 0 bytes 7 files changed, 5 insertions(+), 1 deletion(-) delete mode 100644 src-tauri/android/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png delete mode 100644 src-tauri/android/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png delete mode 100644 src-tauri/android/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png delete mode 100644 src-tauri/android/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png delete mode 100644 src-tauri/android/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png diff --git a/scripts/sync-android-sources.sh b/scripts/sync-android-sources.sh index 1f40be6..c1f3d4a 100755 --- a/scripts/sync-android-sources.sh +++ b/scripts/sync-android-sources.sh @@ -65,6 +65,11 @@ if [ -d "$RES_SRC" ]; then cp "$dir"/* "$RES_DST/$name/" echo " Copied res: $name" done + # We ship only the color adaptive icon (background + foreground). Drop any + # monochrome layer Tauri may generate: the themed-icon monochrome doesn't + # render well, and our adaptive-icon xml no longer references it, so a stray + # ic_launcher_monochrome.png would just be dead weight. + rm -f "$RES_DST"/mipmap-*/ic_launcher_monochrome.png fi echo "✓ Android sources synced successfully" diff --git a/src-tauri/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 345888d..fcd3a1e 100644 --- a/src-tauri/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/src-tauri/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,5 +2,4 @@ - \ No newline at end of file diff --git a/src-tauri/android/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/src-tauri/android/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png deleted file mode 100644 index a801e663609d9768914cc2b496b233e8aa63c47e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8926 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%0A4lI7=-YfO>>}Rzyt&&vD7+ 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≦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 Id@%d^f7Zw~H2?qr From c58cc0cf46e6ab92561b6e49e3474c22be0f0e2c Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 3 Jul 2026 18:48:14 +0200 Subject: [PATCH 2/3] CI: replace broken per-commit Android APK build with a fast compile check build-and-test.yml built a full APK on every master push without running sync-android-sources.sh, so it used the wrong (Tauri-default) sources, was unsigned, and duplicated the ~15min build that build-release.yml does properly on tags. Replace it with cargo check --target aarch64-linux-android (~1min), which catches Android Rust breakage without linking, bundling, or signing. The signed release APK remains a tag-only artifact from build-release.yml. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/build-and-test.yml | 57 +++++++++-------------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index f8c57c3..1fbd334 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -60,16 +60,24 @@ jobs: cargo test cd .. - build: - name: Build Android APK + # Fast per-commit Android compile check. This does NOT build a shippable APK: + # the full signed release APK is built only on tag pushes by build-release.yml + # (which runs sync-android-sources.sh + signing). Running the full bundle here + # too would duplicate a ~15min build and, without the sync step, produced an + # unsigned APK missing our custom sources/icons/proguard rules anyway. + # `cargo check` for the Android target (~1min) catches Android-specific Rust + # breakage without linking, bundling, or signing. + android-check: + name: Android Compile Check runs-on: linux/amd64 needs: test container: image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest env: ANDROID_HOME: /opt/android-sdk - NDK_VERSION: 27.0.11902837 + ANDROID_SDK_ROOT: /opt/android-sdk NDK_HOME: /opt/android-sdk/ndk/27.0.11902837 + ANDROID_NDK_HOME: /opt/android-sdk/ndk/27.0.11902837 steps: - name: Checkout repository @@ -97,42 +105,13 @@ jobs: ${{ runner.os }}-bun- - name: Install dependencies - run: | - bun install + run: bun install - - name: Build frontend - run: bun run build - - - name: Ensure Android NDK - run: | - if [ ! -d "$NDK_HOME" ]; then - echo "NDK not found at $NDK_HOME, installing ndk;$NDK_VERSION" - yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" "ndk;$NDK_VERSION" - fi - echo "Using NDK at $NDK_HOME" - ls "$NDK_HOME" - - - name: Initialize Android project + - name: Cargo check (aarch64-linux-android) run: | + TC="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TC/aarch64-linux-android24-clang" + export CC_aarch64_linux_android="$TC/aarch64-linux-android24-clang" + export AR_aarch64_linux_android="$TC/llvm-ar" cd src-tauri - echo "" | bunx tauri android init - cd .. - - - name: Build Android APK - id: build - run: | - mkdir -p artifacts - bun run tauri android build --apk true --target aarch64 - - # Find the generated APK file - ARTIFACT=$(find src-tauri/gen/android/app/build/outputs/apk -name "*.apk" -type f -print -quit) - echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT - echo "Found artifact: ${ARTIFACT}" - - - name: Upload build artifact - uses: actions/upload-artifact@v3 - with: - name: jellytau-apk - path: ${{ steps.build.outputs.artifact }} - retention-days: 30 - if-no-files-found: error + cargo check --target aarch64-linux-android --lib From 2d141e5bf4785eee4d56401a81ff42d8de62be07 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Fri, 3 Jul 2026 19:37:34 +0200 Subject: [PATCH 3/3] Fix for offline mode --- src-tauri/src/commands/player/mod.rs | 41 +- src-tauri/src/repository/hybrid.rs | 98 +++- src-tauri/src/repository/offline.rs | 433 +++++++++++++++--- src-tauri/src/repository/online.rs | 143 +++++- src-tauri/src/storage/mod.rs | 1 + src-tauri/src/storage/schema.rs | 21 + src-tauri/src/thumbnail/cache.rs | 79 +++- .../library/GenericGenreBrowser.svelte | 11 + src/lib/components/library/GenreTags.svelte | 26 +- src/lib/components/player/MiniPlayer.svelte | 24 +- src/lib/services/favorites.ts | 40 +- src/lib/stores/home.ts | 14 +- src/routes/library/[id]/+page.svelte | 2 +- 13 files changed, 790 insertions(+), 143 deletions(-) diff --git a/src-tauri/src/commands/player/mod.rs b/src-tauri/src/commands/player/mod.rs index 9ea3c7a..8de8725 100644 --- a/src-tauri/src/commands/player/mod.rs +++ b/src-tauri/src/commands/player/mod.rs @@ -1422,13 +1422,14 @@ pub async fn player_play_album_track( info!(" [{}] {} (ID: {})", idx, track.name, track.id); } - // Find the index of the requested track + // Validate the requested track exists in the album (its position in the + // final queue is computed after building, since offline tracks are skipped). info!("Looking for track_id: {}", request.track_id); - let start_index = tracks.iter() + let album_index = tracks.iter() .position(|t| t.id == request.track_id) .ok_or_else(|| format!("Track {} not found in album", request.track_id))?; - info!("Track {} is at index {} in album", request.track_id, start_index); + info!("Track {} is at index {} in album", request.track_id, album_index); // Convert tracks to MediaItems let mut media_items = Vec::new(); @@ -1443,13 +1444,21 @@ pub async fn player_play_album_track( jellyfin_item_id: Some(jellyfin_id.clone()), } } else { - // Get stream URL from repository (works online/offline) - let stream_url = repository.get_audio_stream_url(&track.id).await - .map_err(|e| format!("Failed to get stream URL for {}: {}", track.name, e))?; - - MediaSource::Remote { - stream_url, - jellyfin_item_id: jellyfin_id.clone(), + // Non-downloaded track: needs a stream URL from the server. When the + // server is unreachable (offline), skip this track rather than failing + // the whole album — downloaded tracks must still be playable. + match repository.get_audio_stream_url(&track.id).await { + Ok(stream_url) => MediaSource::Remote { + stream_url, + jellyfin_item_id: jellyfin_id.clone(), + }, + Err(e) => { + warn!( + "[Player] Skipping track {} ({}) — no local download and stream URL unavailable: {}", + track.name, track.id, e + ); + continue; + } } }; @@ -1489,6 +1498,18 @@ pub async fn player_play_album_track( media_items.push(media_item); } + if media_items.is_empty() { + return Err("No playable tracks available (offline and nothing downloaded)".to_string()); + } + + // Tracks with no local download and no reachable server were skipped above, + // so positions shifted. Re-locate the requested track in the built queue. + // If the tapped track itself was skipped, fall back to the first item. + let start_index = media_items + .iter() + .position(|item| item.id == request.track_id) + .unwrap_or(0); + info!("Built queue with {} media items, starting at index {}", media_items.len(), start_index); // Handle shuffle before setting queue diff --git a/src-tauri/src/repository/hybrid.rs b/src-tauri/src/repository/hybrid.rs index ad1e3b8..36bff86 100644 --- a/src-tauri/src/repository/hybrid.rs +++ b/src-tauri/src/repository/hybrid.rs @@ -196,11 +196,41 @@ impl HybridRepository { #[async_trait] impl MediaRepository for HybridRepository { async fn get_libraries(&self) -> Result, RepoError> { - // Libraries change infrequently, try cache first with fast timeout - let cache_future = self.cache_with_timeout(self.offline.get_libraries()); - let server_future = self.online.get_libraries(); + // Cache-first (100ms). On a cache hit, refresh the cache from the server + // in the background. On a miss, fetch from the server and persist so the + // list is available on the next (possibly offline) startup. + let cache_result = self.cache_with_timeout(self.offline.get_libraries()).await; - self.parallel_race(cache_future, server_future).await + if let Ok(libs) = &cache_result { + if libs.has_content() { + debug!("[HybridRepo] Cache hit for libraries, returning immediately"); + let online = Arc::clone(&self.online); + let offline = Arc::clone(&self.offline); + tokio::spawn(async move { + if let Ok(server_libs) = online.get_libraries().await { + if !server_libs.is_empty() { + if let Err(e) = offline.save_libraries_to_cache(&server_libs).await { + warn!("[HybridRepo] Background library cache update failed: {:?}", e); + } + } + } + }); + return cache_result; + } + } + + // Cache miss — fetch from server and persist for offline use. + match self.online.get_libraries().await { + Ok(server_libs) => { + if !server_libs.is_empty() { + if let Err(e) = self.offline.save_libraries_to_cache(&server_libs).await { + warn!("[HybridRepo] Failed to cache {} libraries: {:?}", server_libs.len(), e); + } + } + Ok(server_libs) + } + Err(e) => cache_result.or(Err(e)), + } } async fn get_items(&self, parent_id: &str, options: Option) -> Result { @@ -377,20 +407,60 @@ impl MediaRepository for HybridRepository { } async fn get_genres(&self, parent_id: Option<&str>) -> Result, RepoError> { - let offline = Arc::clone(&self.offline); - let online = Arc::clone(&self.online); + // Cache-first (100ms). On a cache hit, refresh the cached genre catalog + // from the server in the background. On a miss, fetch from the server and + // persist so the full genre list is available offline. Mirrors + // get_libraries — NOT parallel_race, whose "any non-empty cache wins" + // rule would pin genres to whatever sparse set the local albums yield. let parent_id_str = parent_id.map(|s| s.to_string()); - let parent_id_clone = parent_id_str.clone(); - let cache_future = self.cache_with_timeout(async move { - offline.get_genres(parent_id_str.as_deref()).await - }); + let cache_offline = Arc::clone(&self.offline); + let cache_pid = parent_id_str.clone(); + let cache_result = self + .cache_with_timeout(async move { cache_offline.get_genres(cache_pid.as_deref()).await }) + .await; - let server_future = async move { - online.get_genres(parent_id_clone.as_deref()).await - }; + if let Ok(genres) = &cache_result { + if genres.has_content() { + debug!("[HybridRepo] Cache hit for genres, returning immediately"); + let online = Arc::clone(&self.online); + let offline = Arc::clone(&self.offline); + let pid = parent_id_str.clone(); + tokio::spawn(async move { + if let Ok(server_genres) = online.get_genres(pid.as_deref()).await { + if !server_genres.is_empty() { + if let Err(e) = + offline.save_genres_to_cache(pid.as_deref(), &server_genres).await + { + warn!("[HybridRepo] Background genre cache update failed: {:?}", e); + } + } + } + }); + return cache_result; + } + } - self.parallel_race(cache_future, server_future).await + // Cache miss — fetch from server and persist for offline use. + match self.online.get_genres(parent_id_str.as_deref()).await { + Ok(server_genres) => { + if !server_genres.is_empty() { + if let Err(e) = self + .offline + .save_genres_to_cache(parent_id_str.as_deref(), &server_genres) + .await + { + warn!( + "[HybridRepo] Failed to cache {} genres: {:?}", + server_genres.len(), + e + ); + } + } + Ok(server_genres) + } + Err(e) => cache_result.or(Err(e)), + } } async fn search(&self, query: &str, options: Option) -> Result { diff --git a/src-tauri/src/repository/offline.rs b/src-tauri/src/repository/offline.rs index f0eb33a..88e11d4 100644 --- a/src-tauri/src/repository/offline.rs +++ b/src-tauri/src/repository/offline.rs @@ -359,6 +359,94 @@ impl OfflineRepository { Ok(count) } + /// Cache the library (view) list from the server into the local database. + /// Called by HybridRepository after a successful online fetch so the list is + /// available offline. Without this, the `libraries` table stays empty and + /// offline startup shows no libraries at all. + pub async fn save_libraries_to_cache(&self, libraries: &[Library]) -> Result { + if libraries.is_empty() { + return Ok(0); + } + + let mut count = 0; + for (idx, lib) in libraries.iter().enumerate() { + let query = Query::with_params( + "INSERT OR REPLACE INTO libraries (id, server_id, name, collection_type, image_tag, sort_order, synced_at) + VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)", + vec![ + QueryParam::String(lib.id.clone()), + QueryParam::String(self.server_id.clone()), + QueryParam::String(lib.name.clone()), + QueryParam::String(lib.collection_type.clone()), + lib.image_tag.clone().map(QueryParam::String).unwrap_or(QueryParam::Null), + QueryParam::Int(idx as i32), + ], + ); + self.db_service.execute(query).await + .map_err(|e| RepoError::Database { message: e })?; + count += 1; + } + Ok(count) + } + + /// Cache the full server genre catalog for a library, so offline (and the + /// hybrid cache-first race) can return the complete list instead of only the + /// genres derivable from locally-cached albums. Replaces the scope's rows + /// wholesale so genres removed on the server don't linger. + pub async fn save_genres_to_cache( + &self, + parent_id: Option<&str>, + genres: &[Genre], + ) -> Result { + if genres.is_empty() { + return Ok(0); + } + + // library_id is part of the primary key; NULL keys don't de-dupe in + // SQLite, so store the "no library" scope as an empty string. + let library_id = parent_id.unwrap_or("").to_string(); + let server_id = self.server_id.clone(); + let genres: Vec<(String, String, Option)> = genres + .iter() + .map(|g| (g.id.clone(), g.name.clone(), g.album_count)) + .collect(); + let saved = genres.len(); + + self.db_service + .transaction(move |tx| { + use crate::storage::db_service::{Query, QueryParam}; + + // Clear the scope's existing genres, then re-insert the fresh set. + tx.execute(Query::with_params( + "DELETE FROM genres WHERE server_id = ? AND library_id = ?", + vec![ + QueryParam::String(server_id.clone()), + QueryParam::String(library_id.clone()), + ], + ))?; + + for (id, name, album_count) in &genres { + tx.execute(Query::with_params( + "INSERT OR REPLACE INTO genres (id, server_id, library_id, name, album_count, synced_at) + VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)", + vec![ + QueryParam::String(id.clone()), + QueryParam::String(server_id.clone()), + QueryParam::String(library_id.clone()), + QueryParam::String(name.clone()), + album_count.map(|c| QueryParam::Int(c as i32)).unwrap_or(QueryParam::Null), + ], + ))?; + } + + Ok(()) + }) + .await + .map_err(|e| RepoError::Database { message: e })?; + + Ok(saved) + } + /// Cache playlist items from server into local database /// Called by HybridRepository after fetching from online pub async fn save_playlist_items_to_cache( @@ -413,26 +501,17 @@ impl OfflineRepository { #[async_trait] impl MediaRepository for OfflineRepository { async fn get_libraries(&self) -> Result, RepoError> { - // Only show libraries that have downloaded content - // Check both direct children and nested children (e.g., albums inside library) + // Return every cached library for this server. We deliberately do NOT + // gate on `items.library_id` here: that column is not populated in the + // cache (the Jellyfin client doesn't parse it), so the old + // `INNER JOIN items i ON i.library_id = l.id` matched nothing and left + // offline startup with zero libraries. Navigating into a library still + // filters to downloaded content via get_items, so listing all cached + // libraries is correct — it's the "local first" list the UI browses. let query = Query::with_params( - "SELECT DISTINCT l.id, l.name, l.collection_type, l.image_tag + "SELECT l.id, l.name, l.collection_type, l.image_tag FROM libraries l - INNER JOIN items i ON i.library_id = l.id WHERE l.server_id = ? - AND ( - -- Direct playable items with downloads - (i.item_type IN ('Audio', 'Movie', 'Episode') - AND EXISTS (SELECT 1 FROM downloads d WHERE d.item_id = i.id AND d.status = 'completed')) - OR - -- Container items with downloaded children - (i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') - AND EXISTS ( - SELECT 1 FROM items children - INNER JOIN downloads d ON children.id = d.item_id - WHERE children.parent_id = i.id AND d.status = 'completed' - )) - ) ORDER BY l.sort_order ASC, l.name ASC", vec![QueryParam::String(self.server_id.clone())], ); @@ -494,7 +573,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') @@ -513,17 +592,38 @@ impl MediaRepository for OfflineRepository { i.parent_index_number, i.is_folder, i.premiere_date FROM items i INNER JOIN available_items ai ON i.id = ai.id - WHERE i.server_id = ? AND i.parent_id = ?{} + WHERE i.server_id = ? + AND ( + i.parent_id = ? OR i.album_id = ? OR i.season_id = ? OR i.series_id = ? + -- When the requested parent is a LIBRARY, there is no per-item + -- link back to it (library_id/parent_id are NULL in the cache), + -- so match every item on the server and let the type filter + -- (e.g. MusicAlbum / Movie / Series) narrow it. This is what + -- makes library landing pages show albums/movies/shows offline. + OR EXISTS ( + SELECT 1 FROM libraries l + WHERE l.id = ? AND l.server_id = i.server_id + ) + ){} ORDER BY {} LIMIT {} OFFSET {}", type_filter, order_by, limit, start_index ); + // The requested id is compared against every hierarchy-linkage column + // because `parent_id` is not populated for cached items — music tracks + // link to their album via `album_id`, episodes to their season/series + // via `season_id`/`series_id`, and a library parent matches via the + // `libraries` EXISTS clause. See [[offline-libraries-never-cached]]. let query = Query::with_params( sql, vec![ QueryParam::String(self.server_id.clone()), - QueryParam::String(parent_id.to_string()), + QueryParam::String(parent_id.to_string()), // i.parent_id = ? + QueryParam::String(parent_id.to_string()), // i.album_id = ? + QueryParam::String(parent_id.to_string()), // i.season_id = ? + QueryParam::String(parent_id.to_string()), // i.series_id = ? + QueryParam::String(parent_id.to_string()), // libraries.id = ? ], ); @@ -566,7 +666,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') @@ -611,7 +711,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') @@ -744,7 +844,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children (Albums) SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type = 'MusicAlbum' @@ -849,59 +949,32 @@ impl MediaRepository for OfflineRepository { } async fn get_genres(&self, parent_id: Option<&str>) -> Result, RepoError> { - // Derive genres from cached albums, tallying how many albums carry each - // so the frontend can rank by popularity. We scope to MusicAlbum (genres - // power the music landing) and read every matching row — NOT DISTINCT — - // so the per-genre counts are real. Genres are stored as a JSON array - // string per item. - let (sql, params) = if let Some(pid) = parent_id { - ( - "SELECT genres FROM items WHERE server_id = ? AND library_id = ? \ - AND item_type = 'MusicAlbum' AND genres IS NOT NULL", - vec![ - QueryParam::String(self.server_id.clone()), - QueryParam::String(pid.to_string()), - ], - ) - } else { - ( - "SELECT genres FROM items WHERE server_id = ? \ - AND item_type = 'MusicAlbum' AND genres IS NOT NULL", - vec![QueryParam::String(self.server_id.clone())], - ) - }; + // Read the cached server genre catalog (populated by the hybrid repo via + // save_genres_to_cache). This is the FULL genre list for the library, not + // just the genres derivable from locally-cached albums — so offline keeps + // the same variety the server has. library_id NULL is stored as ''. + let library_id = parent_id.unwrap_or("").to_string(); - let query = Query::with_params(sql, params); + let query = Query::with_params( + "SELECT id, name, album_count FROM genres WHERE server_id = ? AND library_id = ?", + vec![ + QueryParam::String(self.server_id.clone()), + QueryParam::String(library_id), + ], + ); - let genres_rows: Vec = self + let genres: Vec = self .db_service - .query_many(query, |row| row.get(0)) + .query_many(query, |row| { + Ok(Genre { + id: row.get(0)?, + name: row.get(1)?, + album_count: row.get::<_, Option>(2)?.map(|c| c as u32), + }) + }) .await .map_err(|e| RepoError::Database { message: e })?; - // genre name -> album count - let mut counts: std::collections::HashMap = std::collections::HashMap::new(); - for genres_json in genres_rows { - if let Ok(genres_vec) = serde_json::from_str::>(&genres_json) { - // De-dupe within one album so a genre listed twice counts once. - let mut seen = std::collections::HashSet::new(); - for genre in genres_vec { - if seen.insert(genre.clone()) { - *counts.entry(genre).or_insert(0) += 1; - } - } - } - } - - let genres = counts - .into_iter() - .map(|(name, count)| Genre { - id: name.clone(), - name, - album_count: Some(count), - }) - .collect(); - Ok(genres) } @@ -943,7 +1016,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') @@ -1157,7 +1230,7 @@ impl MediaRepository for OfflineRepository { -- Containers with downloaded children SELECT DISTINCT i.id FROM items i - INNER JOIN items children ON children.parent_id = i.id + INNER JOIN items children ON (children.parent_id = i.id OR children.album_id = i.id OR children.season_id = i.id OR children.series_id = i.id) INNER JOIN downloads d ON children.id = d.item_id WHERE d.status = 'completed' AND i.item_type IN ('MusicAlbum', 'Series', 'Season', 'BoxSet', 'Folder', 'CollectionFolder') @@ -1541,6 +1614,32 @@ mod tests { ); CREATE INDEX idx_playlist_items_playlist ON playlist_items(playlist_id, sort_order); + + CREATE TABLE downloads ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + item_id TEXT NOT NULL, + status TEXT NOT NULL + ); + + CREATE TABLE libraries ( + id TEXT PRIMARY KEY, + server_id TEXT NOT NULL, + name TEXT NOT NULL, + collection_type TEXT, + image_tag TEXT, + sort_order INTEGER DEFAULT 0, + synced_at TEXT + ); + + CREATE TABLE genres ( + id TEXT NOT NULL, + server_id TEXT NOT NULL, + library_id TEXT, + name TEXT NOT NULL, + album_count INTEGER, + synced_at TEXT DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (server_id, library_id, name) + ); "#).unwrap(); // Insert a test server @@ -1721,6 +1820,196 @@ mod tests { assert_eq!(result.unwrap(), 3); } + /// Regression: a MusicAlbum whose tracks link via `album_id` (and have a + /// NULL `parent_id`, which is how the Jellyfin cache actually stores them) + /// must be recognized as available offline when a track is downloaded. + /// + /// Before the fix, `get_item(album_id)` only matched children by + /// `children.parent_id = i.id`, so a fully-downloaded album returned + /// NotFound offline and playback fell through to the (unreachable) server. + #[tokio::test] + async fn test_get_item_album_available_via_album_id_link() { + use crate::storage::db_service::DatabaseService; + let db_service = create_test_db(); + + for sql in [ + // Album container (no children by parent_id). + "INSERT INTO items (id, server_id, name, item_type, album_id, parent_id) \ + VALUES ('album-1', 'test-server', 'Hadestown', 'MusicAlbum', NULL, NULL)", + // Track linked to the album ONLY via album_id, parent_id NULL. + "INSERT INTO items (id, server_id, name, item_type, album_id, parent_id) \ + VALUES ('track-1', 'test-server', 'Wait For Me', 'Audio', 'album-1', NULL)", + "INSERT INTO downloads (item_id, status) VALUES ('track-1', 'completed')", + ] { + db_service.execute(Query::new(sql)).await.unwrap(); + } + + let repo = OfflineRepository::new( + db_service.clone(), + "test-server".to_string(), + "test-user".to_string(), + ); + + // The downloaded track itself resolves offline. + assert!( + repo.get_item("track-1").await.is_ok(), + "downloaded track should be available offline" + ); + + // The album must also resolve offline because it has a downloaded child + // linked by album_id (not parent_id). + let album = repo.get_item("album-1").await; + assert!( + album.is_ok(), + "album with an album_id-linked downloaded track should be available offline, got {:?}", + album.err() + ); + assert_eq!(album.unwrap().id, "album-1"); + + // Browsing into the album (get_items) must return its tracks even though + // they link by album_id and have a NULL parent_id. This is the call + // play_album_track makes to build the queue. + let tracks = repo.get_items("album-1", None).await.unwrap(); + assert_eq!(tracks.items.len(), 1, "get_items(album_id) should return the track"); + assert_eq!(tracks.items[0].id, "track-1"); + } + + /// Regression: TV episodes link to their season/series via `season_id` / + /// `series_id` (NOT `parent_id`, which is NULL in the cache). A downloaded + /// episode must make both its Season and Series available offline, and + /// browsing either container must return the episode. + #[tokio::test] + async fn test_get_item_tv_available_via_season_series_link() { + use crate::storage::db_service::DatabaseService; + let db_service = create_test_db(); + + for sql in [ + "INSERT INTO items (id, server_id, name, item_type, parent_id) \ + VALUES ('series-1', 'test-server', 'Gilmore Girls', 'Series', NULL)", + "INSERT INTO items (id, server_id, name, item_type, series_id, parent_id) \ + VALUES ('season-1', 'test-server', 'Season 1', 'Season', 'series-1', NULL)", + // Episode links to both season and series; parent_id NULL. + "INSERT INTO items (id, server_id, name, item_type, season_id, series_id, parent_id) \ + VALUES ('ep-1', 'test-server', 'Pilot', 'Episode', 'season-1', 'series-1', NULL)", + "INSERT INTO downloads (item_id, status) VALUES ('ep-1', 'completed')", + ] { + db_service.execute(Query::new(sql)).await.unwrap(); + } + + let repo = OfflineRepository::new( + db_service.clone(), + "test-server".to_string(), + "test-user".to_string(), + ); + + assert!(repo.get_item("ep-1").await.is_ok(), "downloaded episode available offline"); + assert!( + repo.get_item("season-1").await.is_ok(), + "season with a season_id-linked downloaded episode should be available offline" + ); + assert!( + repo.get_item("series-1").await.is_ok(), + "series with a series_id-linked downloaded episode should be available offline" + ); + + // Browsing the season returns the episode. + let season_items = repo.get_items("season-1", None).await.unwrap(); + assert!( + season_items.items.iter().any(|i| i.id == "ep-1"), + "get_items(season_id) should return the episode" + ); + + // Browsing the series returns the episode (via series_id link). + let series_items = repo.get_items("series-1", None).await.unwrap(); + assert!( + series_items.items.iter().any(|i| i.id == "ep-1"), + "get_items(series_id) should surface the downloaded episode" + ); + } + + /// Regression: offline startup must list cached libraries. Previously + /// `get_libraries` joined on `items.library_id` (always NULL in the cache), + /// so it returned nothing offline and the app showed no libraries at all. + /// The list must round-trip through `save_libraries_to_cache`. + #[tokio::test] + async fn test_libraries_cache_roundtrip_available_offline() { + let db_service = create_test_db(); + let repo = OfflineRepository::new( + db_service.clone(), + "test-server".to_string(), + "test-user".to_string(), + ); + + // Empty cache → no libraries (this is the state that fell through to the + // server and hung offline). + assert!(repo.get_libraries().await.unwrap().is_empty()); + + // Simulate the online path persisting the server's library list. + let server_libs = vec![ + Library { id: "music".into(), name: "Music".into(), collection_type: "music".into(), image_tag: None }, + Library { id: "movies".into(), name: "Movies".into(), collection_type: "movies".into(), image_tag: Some("tag".into()) }, + ]; + let saved = repo.save_libraries_to_cache(&server_libs).await.unwrap(); + assert_eq!(saved, 2); + + // Now offline get_libraries returns them without touching the server. + let offline_libs = repo.get_libraries().await.unwrap(); + let names: Vec<&str> = offline_libs.iter().map(|l| l.name.as_str()).collect(); + assert_eq!(names, vec!["Music", "Movies"], "cached libraries available offline in sort order"); + + // Re-saving is idempotent (INSERT OR REPLACE), not duplicating rows. + repo.save_libraries_to_cache(&server_libs).await.unwrap(); + assert_eq!(repo.get_libraries().await.unwrap().len(), 2); + } + + /// Regression: the music/TV/movie landing pages lost genre variety because + /// offline `get_genres` derived genres from cached albums only — so the + /// hybrid cache-first race pinned the list to whatever sparse set the local + /// albums yielded instead of the server's full catalog. The full genre list + /// must round-trip through `save_genres_to_cache` and come back scoped by + /// library. + #[tokio::test] + async fn test_genres_cache_roundtrip_scoped_by_library() { + let db_service = create_test_db(); + let repo = OfflineRepository::new( + db_service.clone(), + "test-server".to_string(), + "test-user".to_string(), + ); + + // Empty cache → no genres. + assert!(repo.get_genres(Some("music-lib")).await.unwrap().is_empty()); + + // Simulate the online path persisting the server's full genre catalog. + let server_genres = vec![ + Genre { id: "g1".into(), name: "Rock".into(), album_count: Some(42) }, + Genre { id: "g2".into(), name: "Jazz".into(), album_count: Some(17) }, + Genre { id: "g3".into(), name: "Ambient".into(), album_count: None }, + ]; + let saved = repo.save_genres_to_cache(Some("music-lib"), &server_genres).await.unwrap(); + assert_eq!(saved, 3); + + // Offline get_genres returns the full set for that library, counts intact. + let mut offline_genres = repo.get_genres(Some("music-lib")).await.unwrap(); + offline_genres.sort_by(|a, b| a.name.cmp(&b.name)); + let names: Vec<&str> = offline_genres.iter().map(|g| g.name.as_str()).collect(); + assert_eq!(names, vec!["Ambient", "Jazz", "Rock"]); + let rock = offline_genres.iter().find(|g| g.name == "Rock").unwrap(); + assert_eq!(rock.album_count, Some(42)); + + // Genres are scoped: a different library sees nothing. + assert!(repo.get_genres(Some("other-lib")).await.unwrap().is_empty()); + + // Re-saving replaces the scope's rows (server removed "Jazz"). + let updated = vec![ + Genre { id: "g1".into(), name: "Rock".into(), album_count: Some(50) }, + ]; + repo.save_genres_to_cache(Some("music-lib"), &updated).await.unwrap(); + let after = repo.get_genres(Some("music-lib")).await.unwrap(); + assert_eq!(after.len(), 1, "stale genres removed on refresh"); + assert_eq!(after[0].album_count, Some(50), "counts updated on refresh"); + } + // ===== Playlist Tests ===== /// Helper to seed items into the DB for playlist tests diff --git a/src-tauri/src/repository/online.rs b/src-tauri/src/repository/online.rs index 7f62a18..da7e7c9 100644 --- a/src-tauri/src/repository/online.rs +++ b/src-tauri/src/repository/online.rs @@ -144,6 +144,17 @@ impl OnlineRepository { /// Make authenticated GET request async fn get_json Deserialize<'de>>(&self, endpoint: &str) -> Result { + // Fast-fail when connectivity is known-offline. Without this every request + // still runs the full HTTP retry/backoff cycle (~7s) before giving up, + // which stalls cache-miss paths and makes offline browsing feel janky. + // The offline recovery probe (connectivity monitor) flips us back to + // reachable the moment the server returns, so this never sticks. + if let Some(reporter) = &self.connectivity { + if !reporter.is_reachable().await { + return Err(RepoError::Offline); + } + } + let result = self.get_json_inner(endpoint).await; self.report_outcome(&result).await; result @@ -1413,12 +1424,43 @@ impl MediaRepository for OnlineRepository { quality: &str, media_source_id: Option<&str>, ) -> String { - let mut url = format!("{}/Videos/{}/download", self.server_url, item_id); + // NOTE: Jellyfin's `/Videos/{id}/download` endpoint is not universally + // available (returns 404 on many server configs), which silently broke + // every movie/TV download. Use the progressive `stream.mp4` endpoint + // instead — it is always present and supports HTTP Range, which the + // download worker relies on for resume. + let mut url = format!("{}/Videos/{}/stream.mp4", self.server_url, item_id); let mut params = vec![format!("api_key={}", self.access_token)]; - // Add quality parameter if not "original" - if quality != "original" { - params.push(format!("quality={}", quality)); + // Map the frontend quality preset to concrete transcode params. For + // "original" we request a direct static copy (no transcode) which is + // byte-range resumable; other presets ask the server to transcode. + match quality { + "high" => { + params.push("videoBitrate=8000000".to_string()); + params.push("maxHeight=1080".to_string()); + params.push("audioBitrate=384000".to_string()); + params.push("videoCodec=h264".to_string()); + params.push("audioCodec=aac".to_string()); + } + "medium" => { + params.push("videoBitrate=4000000".to_string()); + params.push("maxHeight=720".to_string()); + params.push("audioBitrate=256000".to_string()); + params.push("videoCodec=h264".to_string()); + params.push("audioCodec=aac".to_string()); + } + "low" => { + params.push("videoBitrate=1500000".to_string()); + params.push("maxHeight=480".to_string()); + params.push("audioBitrate=128000".to_string()); + params.push("videoCodec=h264".to_string()); + params.push("audioCodec=aac".to_string()); + } + // "original" (and any unknown value) → direct, resumable copy. + _ => { + params.push("Static=true".to_string()); + } } // Add media source ID if provided @@ -1426,10 +1468,8 @@ impl MediaRepository for OnlineRepository { params.push(format!("mediaSourceId={}", source_id)); } - if !params.is_empty() { - url.push('?'); - url.push_str(¶ms.join("&")); - } + url.push('?'); + url.push_str(¶ms.join("&")); url } @@ -1759,6 +1799,25 @@ mod tests { } } + /// When connectivity is known-offline, `get_json` must fast-fail with + /// `RepoError::Offline` instead of running the full HTTP retry cycle (~7s). + /// This is what keeps offline browsing snappy. `test.server.com` is + /// unroutable, so if the guard were absent this would hang on retries; the + /// assertion returning promptly with `Offline` proves the short-circuit. + #[tokio::test] + async fn test_get_json_fast_fails_when_offline() { + let (repo, reporter) = create_test_repository_with_connectivity(); + reporter.mark_unreachable_for_test().await; + assert!(!reporter.is_reachable().await, "precondition: offline"); + + let result: Result = repo.get_json("/System/Info").await; + assert!( + matches!(result, Err(RepoError::Offline)), + "known-offline get_json should return Offline immediately, got {:?}", + result + ); + } + /// A network error routes through the debounced path. A single failure stays /// online (debounce window not yet elapsed). #[tokio::test] @@ -1887,6 +1946,74 @@ mod tests { assert_eq!(tags.primary(), None); } + // ===== Video download URL (real impl) ===== + // + // These exercise the PRODUCTION `OnlineRepository::get_video_download_url`, + // not a mock. A prior mock in online_integration_test.rs used the correct + // `stream.mp4` endpoint while the real impl shipped `/Videos/{id}/download`, + // which returns 404 on real servers and silently broke every movie/TV + // download. Assert the real builder targets the resumable stream endpoint. + // + // @req-test: DR-013 - Repository pattern for online/offline data access + + #[test] + fn test_video_download_url_uses_stream_not_download_endpoint() { + let repo = create_test_repository(); + let url = repo.get_video_download_url("item123", "original", None); + + // Must NOT use the /download endpoint (404 on real servers). + assert!( + !url.contains("/download"), + "download URL must not use the broken /Videos/{{id}}/download endpoint: {url}" + ); + // Must use the progressive, range-resumable stream endpoint. + assert!( + url.contains("/Videos/item123/stream.mp4"), + "download URL must target /Videos/{{id}}/stream.mp4: {url}" + ); + assert!(url.contains("api_key=test-access-token"), "url: {url}"); + } + + #[test] + fn test_video_download_url_original_is_static_direct_copy() { + let repo = create_test_repository(); + let url = repo.get_video_download_url("item123", "original", None); + + // "original" must request a direct static copy (byte-range resumable), + // with no transcode params. + assert!(url.contains("Static=true"), "url: {url}"); + assert!(!url.contains("videoBitrate"), "original must not transcode: {url}"); + assert!(!url.contains("maxHeight"), "original must not transcode: {url}"); + } + + #[test] + fn test_video_download_url_quality_presets_transcode() { + let repo = create_test_repository(); + + for (quality, height) in [("high", "1080"), ("medium", "720"), ("low", "480")] { + let url = repo.get_video_download_url("item123", quality, None); + assert!( + url.contains("/Videos/item123/stream.mp4"), + "{quality} must use stream.mp4: {url}" + ); + assert!(url.contains("videoBitrate="), "{quality} must set bitrate: {url}"); + assert!( + url.contains(&format!("maxHeight={height}")), + "{quality} must cap height at {height}: {url}" + ); + assert!(url.contains("videoCodec=h264"), "{quality}: {url}"); + // Transcoded presets must not also ask for a static copy. + assert!(!url.contains("Static=true"), "{quality} must not be Static: {url}"); + } + } + + #[test] + fn test_video_download_url_passes_media_source_id() { + let repo = create_test_repository(); + let url = repo.get_video_download_url("item123", "original", Some("src-42")); + assert!(url.contains("mediaSourceId=src-42"), "url: {url}"); + } + #[test] fn test_jellyfin_item_deserialize_with_image_tags() { // Test full JellyfinItem deserialization with ImageTags diff --git a/src-tauri/src/storage/mod.rs b/src-tauri/src/storage/mod.rs index 8cb5589..814d5bc 100644 --- a/src-tauri/src/storage/mod.rs +++ b/src-tauri/src/storage/mod.rs @@ -201,6 +201,7 @@ mod tests { "thumbnails", "playlists", "playlist_items", + "genres", ]; for table in expected_tables { diff --git a/src-tauri/src/storage/schema.rs b/src-tauri/src/storage/schema.rs index 237fbdf..5be635e 100644 --- a/src-tauri/src/storage/schema.rs +++ b/src-tauri/src/storage/schema.rs @@ -23,6 +23,7 @@ pub const MIGRATIONS: &[(&str, &str)] = &[ ("016_autoplay_max_episodes", MIGRATION_016), ("017_downloads_resume_url", MIGRATION_017), ("018_items_is_folder", MIGRATION_018), + ("019_genres_cache", MIGRATION_019), ]; /// Initial schema migration @@ -693,3 +694,23 @@ ALTER TABLE items ADD COLUMN is_folder INTEGER DEFAULT 0; -- Force re-fetch of all cached items so is_folder is populated from the server. UPDATE items SET synced_at = NULL; "#; + +/// Migration to cache the full server genre catalog. Previously genres were +/// derived on the fly from cached albums, which meant offline (and the hybrid +/// cache-first race) only ever saw genres for the handful of locally-cached +/// albums — collapsing the variety on the music/TV/movie landing pages. This +/// table stores the complete genre list per library so offline has the real +/// catalog and cache-first routing returns the right thing. +const MIGRATION_019: &str = r#" +CREATE TABLE IF NOT EXISTS genres ( + id TEXT NOT NULL, + server_id TEXT NOT NULL, + library_id TEXT, + name TEXT NOT NULL, + album_count INTEGER, + synced_at TEXT DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (server_id, library_id, name) +); + +CREATE INDEX IF NOT EXISTS idx_genres_scope ON genres(server_id, library_id); +"#; diff --git a/src-tauri/src/thumbnail/cache.rs b/src-tauri/src/thumbnail/cache.rs index 4980af7..a418873 100644 --- a/src-tauri/src/thumbnail/cache.rs +++ b/src-tauri/src/thumbnail/cache.rs @@ -64,7 +64,8 @@ impl ThumbnailCache { image_type: &str, tag: &str, ) -> Option { - let query = Query::with_params( + // Primary lookup: exact (item_id, image_type, tag) match. + let exact = Query::with_params( "SELECT file_path FROM thumbnails WHERE item_id = ? AND image_type = ? AND image_tag = ?", vec![ @@ -74,12 +75,60 @@ impl ThumbnailCache { ], ); - let path_str: String = db.query_optional(query, |row| row.get(0)).await.ok()??; - let path = PathBuf::from(&path_str); + if let Ok(Some(path_str)) = db.query_optional(exact, |row| row.get::<_, String>(0)).await { + let path = PathBuf::from(&path_str); + if path.exists() { + self.touch(&db, item_id, image_type, Some(tag)).await; + return Some(path); + } + // File gone — drop the stale row and fall through to the tag-agnostic + // lookup below (another cached image for this item may still exist). + let _ = db.execute(Query::with_params( + "DELETE FROM thumbnails WHERE item_id = ? AND image_type = ? AND image_tag = ?", + vec![ + QueryParam::String(item_id.to_string()), + QueryParam::String(image_type.to_string()), + QueryParam::String(tag.to_string()), + ], + )).await; + } + // Fallback: any cached image for this item + type, newest first. The + // `image_tag` is a cache-busting version, and callers don't always pass + // the same tag the image was cached under — e.g. the mini player asks for + // the album image using the *track's* primary_image_tag. Ignoring the tag + // here lets those still resolve offline instead of hitting the server. + let any_tag = Query::with_params( + "SELECT file_path FROM thumbnails + WHERE item_id = ? AND image_type = ? + ORDER BY cached_at DESC LIMIT 1", + vec![ + QueryParam::String(item_id.to_string()), + QueryParam::String(image_type.to_string()), + ], + ); + + let path_str: String = db.query_optional(any_tag, |row| row.get(0)).await.ok()??; + let path = PathBuf::from(&path_str); if path.exists() { - // Update last_accessed for LRU tracking - let update_query = Query::with_params( + self.touch(&db, item_id, image_type, None).await; + Some(path) + } else { + None + } + } + + /// Update `last_accessed` for LRU tracking. When `tag` is `Some`, scope to + /// that exact row; when `None`, touch every row for the item + type. + async fn touch( + &self, + db: &Arc, + item_id: &str, + image_type: &str, + tag: Option<&str>, + ) { + let query = match tag { + Some(tag) => Query::with_params( "UPDATE thumbnails SET last_accessed = CURRENT_TIMESTAMP WHERE item_id = ? AND image_type = ? AND image_tag = ?", vec![ @@ -87,23 +136,17 @@ impl ThumbnailCache { QueryParam::String(image_type.to_string()), QueryParam::String(tag.to_string()), ], - ); - let _ = db.execute(update_query).await; - Some(path) - } else { - // Clean up stale database entry - let delete_query = Query::with_params( - "DELETE FROM thumbnails - WHERE item_id = ? AND image_type = ? AND image_tag = ?", + ), + None => Query::with_params( + "UPDATE thumbnails SET last_accessed = CURRENT_TIMESTAMP + WHERE item_id = ? AND image_type = ?", vec![ QueryParam::String(item_id.to_string()), QueryParam::String(image_type.to_string()), - QueryParam::String(tag.to_string()), ], - ); - let _ = db.execute(delete_query).await; - None - } + ), + }; + let _ = db.execute(query).await; } /// Save thumbnail to cache diff --git a/src/lib/components/library/GenericGenreBrowser.svelte b/src/lib/components/library/GenericGenreBrowser.svelte index 82232ea..257825f 100644 --- a/src/lib/components/library/GenericGenreBrowser.svelte +++ b/src/lib/components/library/GenericGenreBrowser.svelte @@ -1,5 +1,6 @@ diff --git a/src/lib/components/player/MiniPlayer.svelte b/src/lib/components/player/MiniPlayer.svelte index 34ed958..232b194 100644 --- a/src/lib/components/player/MiniPlayer.svelte +++ b/src/lib/components/player/MiniPlayer.svelte @@ -19,6 +19,7 @@ import { goto } from "$app/navigation"; import type { MediaItem } from "$lib/api/types"; import { auth } from "$lib/stores/auth"; + import { commands } from "$lib/api/bindings"; import { mergedMedia, mergedIsPlaying, @@ -70,6 +71,27 @@ // In remote mode, this automatically uses the remote session's nowPlayingItem const displayMedia = $derived($mergedMedia || $currentQueueItem); const displayIsPlaying = $derived($mergedIsPlaying); + + // The player's MediaItem doesn't carry favorite state, so read it from the + // local user_data cache (offline-safe — same source the optimistic toggle + // writes to). Re-runs whenever the current track changes. + let isFavorite = $state(false); + let favoriteLoadedFor = ""; + $effect(() => { + const id = displayMedia?.id; + if (!id || id === favoriteLoadedFor) return; + favoriteLoadedFor = id; + isFavorite = false; + const userId = auth.getUserId(); + if (!userId) return; + commands + .storageGetPlaybackProgress(userId, id) + .then((p) => { + // Guard against a race if the track changed while awaiting. + if (displayMedia?.id === id) isFavorite = p?.isFavorite ?? false; + }) + .catch(() => {}); + }); const displayPosition = $derived($mergedPosition); const displayDuration = $derived($mergedDuration); @@ -358,7 +380,7 @@ {#if displayMedia} {/if} diff --git a/src/lib/services/favorites.ts b/src/lib/services/favorites.ts index c39c9f0..fa579e1 100644 --- a/src/lib/services/favorites.ts +++ b/src/lib/services/favorites.ts @@ -1,8 +1,10 @@ // Favorites service - Handles toggling favorite status with optimistic updates // TRACES: UR-017 | DR-021 +import { get } from "svelte/store"; import { commands } from "$lib/api/bindings"; import { auth } from "$lib/stores/auth"; +import { isConnected } from "$lib/stores/connectivity"; /** * Toggle the favorite status of an item. @@ -31,21 +33,31 @@ export async function toggleFavorite( // 1. Update local database first (optimistic update) await commands.storageToggleFavorite(userId, itemId, newIsFavorite); - // 2. Sync to Jellyfin server - try { - const repo = auth.getRepository(); - if (newIsFavorite) { - await repo.markFavorite(itemId); - } else { - await repo.unmarkFavorite(itemId); - } + // 2. Sync to Jellyfin server. + // + // Only attempt this when we're actually connected. When offline, the server + // call can hang on a long network timeout rather than failing fast — which + // blocks the caller (and leaves the favorite button greyed out with a wait + // cursor) until the request finally gives up, effectively only recovering + // once we're back online. The local DB write above keeps the pending_sync + // flag set, so the change still syncs later; we just don't block the UI on + // an unreachable server here. + if (get(isConnected)) { + try { + const repo = auth.getRepository(); + if (newIsFavorite) { + await repo.markFavorite(itemId); + } else { + await repo.unmarkFavorite(itemId); + } - // 3. Mark as synced - await commands.storageMarkSynced(userId, itemId); - } catch (error) { - console.error("Failed to sync favorite to server:", error); - // Favorite is stored locally and will be synced later - // via sync queue (when implemented) + // 3. Mark as synced + await commands.storageMarkSynced(userId, itemId); + } catch (error) { + console.error("Failed to sync favorite to server:", error); + // Favorite is stored locally and will be synced later + // via sync queue (when implemented) + } } return newIsFavorite; diff --git a/src/lib/stores/home.ts b/src/lib/stores/home.ts index 0386a2b..f67a137 100644 --- a/src/lib/stores/home.ts +++ b/src/lib/stores/home.ts @@ -36,7 +36,10 @@ function createHomeStore() { try { const repo = auth.getRepository(); - const [resume, nextUp, latest, recentAudio, resumeMovies] = await Promise.all([ + // Use allSettled so one failing section (e.g. Next Up is online-only and + // rejects offline) doesn't wipe out the whole homepage. Each section falls + // back to an empty list; cached sections (resume/latest/recent) still show. + const settled = await Promise.allSettled([ repo.getResumeItems(undefined, 12), repo.getNextUpEpisodes(undefined, 12), repo.getLatestItems("", 16), @@ -44,6 +47,15 @@ function createHomeStore() { repo.getResumeMovies(12), ]); + const valueOr = (i: number, fallback: T): T => + settled[i].status === "fulfilled" ? (settled[i] as PromiseFulfilledResult).value : fallback; + + const resume = valueOr(0, [] as typeof initialState.resumeItems); + const nextUp = valueOr(1, [] as typeof initialState.nextUpItems); + const latest = valueOr(2, [] as typeof initialState.latestItems); + const recentAudio = valueOr(3, [] as typeof initialState.recentlyPlayedAudio); + const resumeMovies = valueOr(4, [] as typeof initialState.resumeMovies); + // Use resume items or latest as hero items const hero = resume.length >= 3 ? resume.slice(0, 5) : latest.slice(0, 5); diff --git a/src/routes/library/[id]/+page.svelte b/src/routes/library/[id]/+page.svelte index f34ec5c..a139836 100644 --- a/src/routes/library/[id]/+page.svelte +++ b/src/routes/library/[id]/+page.svelte @@ -511,7 +511,7 @@ {#if item.genres?.length}
- +
{/if}