feat(slint): seed per-row offline status from the index.db on load
A cached track now shows its "ready" check when a view is built, persisting across navigation within the session (was: only live downloads showed state). - offline_cache.rs: a session-wide CACHED_IDS set (mirrors fav_cache), seeded from index.db ready rows via load_cached_ids() on login/restore, kept in sync as downloads complete (mark_cached true) and copies are removed (false). is_cached(id) reads it. - The 7 TrackItem builders seed cache-status from is_cached (favorites/search clone/precompute the id to avoid moving it before the borrow). - auth.rs calls load_cached_ids after the offline cache activates. qbz-slint compiles clean. cc
This commit is contained in:
9 files changed
+74
-10
No files matched your search
@@ -257,7 +257,7 @@ pub fn apply_album(window: &AppWindow, data: AlbumData) {
|
||||
artist_id: track.artist_id.into(),
|
||||
album_id: album_id.clone(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&track.id) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
})
|
||||
|
||||
@@ -503,7 +503,7 @@ pub fn apply_artist(window: &AppWindow, data: ArtistData) {
|
||||
artist_id: track.artist_id.into(),
|
||||
album_id: track.album_id.into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&track.id) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
})
|
||||
|
||||
@@ -89,6 +89,7 @@ where
|
||||
|
||||
// Bring up the per-user offline cache (shared index.db + library.db with Tauri).
|
||||
crate::offline::activate(user_id).await;
|
||||
crate::offline_cache::load_cached_ids().await;
|
||||
|
||||
// Persist the token so the next launch restores the session silently.
|
||||
if let Err(e) = qbz_credentials::save_oauth_token(&token) {
|
||||
@@ -137,6 +138,7 @@ where
|
||||
core.set_session(session).await.map_err(|e| e.to_string())?;
|
||||
runtime.activate(user_id).await?;
|
||||
crate::offline::activate(user_id).await;
|
||||
crate::offline_cache::load_cached_ids().await;
|
||||
log::info!("[qbz-slint] restored saved session for user {user_id}");
|
||||
Ok(Some(SessionInfo {
|
||||
user_id,
|
||||
|
||||
@@ -402,7 +402,7 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
|
||||
let rows: Vec<TrackItem> = items
|
||||
.into_iter()
|
||||
.map(|t| TrackItem {
|
||||
id: t.id.into(),
|
||||
id: t.id.clone().into(),
|
||||
number: "".into(),
|
||||
title: t.title.into(),
|
||||
artist: t.artist.into(),
|
||||
@@ -419,7 +419,7 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
|
||||
artist_id: t.artist_id.into(),
|
||||
album_id: t.album_id.into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&t.id) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
})
|
||||
|
||||
@@ -763,7 +763,7 @@ fn top_track_to_item(t: &TopTrack) -> TrackItem {
|
||||
artist_id: t.artist_id.clone().into(),
|
||||
album_id: t.album_id.clone().into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&t.id) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ fn to_item(track: &Track) -> TrackItem {
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&track.id.to_string()) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
}
|
||||
|
||||
@@ -8,16 +8,75 @@
|
||||
//! The heavy lifting (download pipeline, CMAF store, vault) lives in the
|
||||
//! shared `qbz-offline-cache` crate; this is the thin Slint orchestration.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, Mutex as StdMutex, OnceLock};
|
||||
|
||||
use qbz_app::shell::AppRuntime;
|
||||
use qbz_offline_cache::{CacheEvent, CacheEventSink, TrackCacheInfo};
|
||||
use qbz_offline_cache::{CacheEvent, CacheEventSink, OfflineCacheStatus, TrackCacheInfo};
|
||||
|
||||
use crate::adapter::SlintAdapter;
|
||||
use crate::AppWindow;
|
||||
|
||||
type Runtime = Arc<AppRuntime<SlintAdapter>>;
|
||||
|
||||
/// Session-wide set of track ids that have a READY offline copy. Seeded from
|
||||
/// the index.db on login (`load_cached_ids`) and kept in sync as downloads
|
||||
/// complete / copies are removed. Read at row-build time to seed each row's
|
||||
/// cache-status (mirrors `fav_cache`), so a cached track shows its check when
|
||||
/// the view is revisited without re-querying the DB per row.
|
||||
static CACHED_IDS: OnceLock<StdMutex<HashSet<u64>>> = OnceLock::new();
|
||||
|
||||
fn cached_ids() -> &'static StdMutex<HashSet<u64>> {
|
||||
CACHED_IDS.get_or_init(|| StdMutex::new(HashSet::new()))
|
||||
}
|
||||
|
||||
/// True if `track_id` (string form, as the row carries it) has a ready offline
|
||||
/// copy. Used to seed `TrackItem.cache-status` when building track lists.
|
||||
pub fn is_cached(track_id: &str) -> bool {
|
||||
let Ok(id) = track_id.parse::<u64>() else {
|
||||
return false;
|
||||
};
|
||||
cached_ids().lock().map(|s| s.contains(&id)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn mark_cached(track_id: u64, cached: bool) {
|
||||
if let Ok(mut set) = cached_ids().lock() {
|
||||
if cached {
|
||||
set.insert(track_id);
|
||||
} else {
|
||||
set.remove(&track_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Seed the ready-set from the active offline cache's index.db. Called once
|
||||
/// after the offline cache is activated (login / session restore).
|
||||
pub async fn load_cached_ids() {
|
||||
let Some(off) = crate::offline::get().await else {
|
||||
return;
|
||||
};
|
||||
let ids: Vec<u64> = {
|
||||
let guard = off.db.lock().await;
|
||||
match guard.as_ref() {
|
||||
Some(db) => db
|
||||
.get_all_tracks()
|
||||
.map(|tracks| {
|
||||
tracks
|
||||
.into_iter()
|
||||
.filter(|t| matches!(t.status, OfflineCacheStatus::Ready))
|
||||
.map(|t| t.track_id)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
};
|
||||
if let Ok(mut set) = cached_ids().lock() {
|
||||
*set = ids.into_iter().collect();
|
||||
log::info!("[qbz-slint] offline: seeded {} cached track ids", set.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a sink that reflects cache + unlock events onto every visible row
|
||||
/// matching the event's track id (and surfaces terminal toasts). Shared by
|
||||
/// the cache trigger AND the play path (UnlockStart/End → padlock).
|
||||
@@ -35,6 +94,7 @@ pub fn row_sink(weak: slint::Weak<AppWindow>) -> CacheEventSink {
|
||||
push_status(&weak, track_id, 2, p);
|
||||
}
|
||||
CacheEvent::Completed { track_id, .. } => {
|
||||
mark_cached(track_id, true);
|
||||
push_status(&weak, track_id, 3, 1.0);
|
||||
crate::toast::success_weak(&weak, "Cached for offline");
|
||||
}
|
||||
@@ -261,6 +321,7 @@ pub fn remove_cached(
|
||||
let _ = db.remove_qobuz_cached_track(id);
|
||||
}
|
||||
}
|
||||
mark_cached(id, false);
|
||||
push_status(&weak, id, 0, 0.0);
|
||||
crate::toast::success_weak(&weak, "Removed from offline");
|
||||
});
|
||||
|
||||
@@ -382,7 +382,7 @@ fn to_item(track: &Track) -> TrackItem {
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if crate::offline_cache::is_cached(&track.id.to_string()) { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
}
|
||||
|
||||
@@ -356,6 +356,7 @@ fn album_item(row: AlbumRow) -> AlbumCardItem {
|
||||
|
||||
fn track_item(row: TrackRowData) -> TrackItem {
|
||||
let is_favorite = crate::fav_cache::is_favorite(&row.id);
|
||||
let is_cached = crate::offline_cache::is_cached(&row.id);
|
||||
TrackItem {
|
||||
id: row.id.into(),
|
||||
number: "".into(),
|
||||
@@ -372,7 +373,7 @@ fn track_item(row: TrackRowData) -> TrackItem {
|
||||
artist_id: row.artist_id.into(),
|
||||
album_id: row.album_id.into(),
|
||||
removing: false,
|
||||
cache_status: 0,
|
||||
cache_status: if is_cached { 3 } else { 0 },
|
||||
cache_progress: 0.0,
|
||||
unlocking: false,
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user