Sidebar: count missing files on a worker thread; read browsed folders in smaller batches

The Missing count stat()ed every catalog photo on the UI thread, which on a network share
(and with browsed NAS photos in the catalog) froze the window for seconds during a folder
browse. It now runs in the background and shows the last finished count.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Clément L
2026-10-04 10:32:18 +02:00
co-authored by Claude Sonnet 5.5
parent 919e65efaf
commit e076f65df8
2 changed files with 42 additions and 10 deletions
+2 -1
View File
@@ -291,7 +291,8 @@ pub fn start(app: &mut LightcraftApp, d: &ImportDialog) -> Result<Value, String>
/// Run one batch of the import in progress (called every frame).
pub fn tick(app: &mut LightcraftApp, ctx: &egui::Context) {
let Some(task) = app.import.as_mut() else { return };
let n = task.queue.len().min(BATCH);
// (browsing reads a sidecar per file, slow on a network share: keep each frame short)
let n = task.queue.len().min(if task.browse { 2 } else { BATCH });
let batch: Vec<String> = task.queue.drain(..n).collect();
let mut p = task.params.clone();
p["paths"] = json!(batch);
+40 -9
View File
@@ -146,20 +146,51 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
});
}
/// How many library photos have no file (cached in egui memory, refreshed every 5 s).
/// How many library photos have no file. Checking stats every file, which on a network share
/// takes seconds, so it runs on a worker thread: the count shown is the last finished one,
/// refreshed at most every 5 s (or when the catalog changed).
fn missing_count(app: &mut LightcraftApp, ui: &mut egui::Ui) -> usize {
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
#[derive(Clone, Default)]
struct Job {
n: std::sync::Arc<AtomicUsize>,
running: std::sync::Arc<AtomicBool>,
at: f64,
rev: u64,
}
let id = egui::Id::new("missing-count");
let now = ui.input(|i| i.time);
let rev = app.session.catalog.revision;
if let Some((n, at, r)) = ui.data(|d| d.get_temp::<(usize, f64, u64)>(id))
&& now - at < 5.0
&& r == rev
{
return n;
let mut job: Job = ui.data(|d| d.get_temp(id)).unwrap_or(Job { rev: u64::MAX, at: f64::MIN, ..Default::default() });
let fresh = now - job.at < 5.0 && job.rev == rev;
if !fresh && !job.running.load(Relaxed) {
let paths: Vec<String> = app
.session
.catalog
.photos()
.filter(|p| !p.deleted)
.filter_map(|p| match &p.source {
lightcraft_catalog::Source::File { path } => Some(path.clone()),
_ => None,
})
.collect();
job.running.store(true, Relaxed);
job.at = now;
job.rev = rev;
let (n, running, ctx) = (job.n.clone(), job.running.clone(), ui.ctx().clone());
let work = move || {
let missing = if cfg!(target_arch = "wasm32") { 0 } else { paths.iter().filter(|p| !std::path::Path::new(p).exists()).count() };
n.store(missing, Relaxed);
running.store(false, Relaxed);
ctx.request_repaint();
};
#[cfg(not(target_arch = "wasm32"))]
std::thread::spawn(work);
#[cfg(target_arch = "wasm32")]
work();
ui.data_mut(|d| d.insert_temp(id, job.clone()));
}
let n = lightcraft_engine::cmd::missing::missing(&app.session).len();
ui.data_mut(|d| d.insert_temp(id, (n, now, rev)));
n
job.n.load(Relaxed)
}
/// Folders on this computer to browse without adding (Lightroom's Local): Pictures, Desktop,