From 3e6bcfb564e5b5f373ae868380ac717e9eb95f10 Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 9 Jul 2026 21:19:13 -0700 Subject: [PATCH] feat(desktop): instant startup from persisted library snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seed self.library from load_library_snapshot before spawning the scan thread, so a warm launch renders the library immediately (status '… refreshing…') instead of a blank/scanning window. Write the snapshot after every scan (the startup thread and rescan). The background scan is unchanged and still swaps in authoritative data; the snapshot only ever shows briefly-stale state. Verified live against the real 5574-video library: second launch renders the full list within 2s (well under the ~64s cold scan) with the refreshing status; deleting the snapshot row falls back to the scanning spinner. Co-Authored-By: Claude Opus 4.8 --- src/app.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7a549b5..709b78f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -482,7 +482,20 @@ impl App { // DB lookups that don't depend on the scan, so they stay synchronous. let (library_load_tx, library_load_rx) = std::sync::mpsc::channel::>(); - let status = "Scanning library…".to_string(); + // Instant startup: if we persisted the library last run, show it + // immediately while the (slow, disk-bound) rescan runs in the + // background and swaps in fresh data via `library_load_rx`. + let seeded_library: Vec = + db.load_library_snapshot(&channels_root).unwrap_or_default(); + let status = if seeded_library.is_empty() { + "Scanning library…".to_string() + } else { + format!( + "{} channels, {} videos (refreshing…)", + seeded_library.len(), + seeded_library.iter().map(|c| c.total_videos()).sum::() + ) + }; { let db = db.clone(); let channels_root = channels_root.clone(); @@ -502,12 +515,13 @@ impl App { if let Err(e) = db.sync_search_index(&library::build_search_entries(&library)) { eprintln!("search index sync failed: {e}"); } + db.save_library_snapshot(&channels_root, &library); let _ = library_load_tx.send(library); ctx.request_repaint(); // wake update() to swap the result in }) .ok(); } - let library: Vec = Vec::new(); + let library: Vec = seeded_library; let folders = db.list_folders().unwrap_or_default(); let watched = db.get_watched().unwrap_or_default(); let flags = db.get_video_flags().unwrap_or_default(); @@ -804,6 +818,7 @@ impl App { self.thumb_pending.retain(|p| valid.contains(p)); // Bump the generation so the card cache recomputes against the new library. self.library_generation += 1; + self.db.save_library_snapshot(&self.channels_root, &self.library); self.status = format!( "Rescanned: {} channels, {} videos", self.library.len(),