diff --git a/crates/ui-egui/src/app/frame.rs b/crates/ui-egui/src/app/frame.rs index 6e92091c7..d4a153e2a 100644 --- a/crates/ui-egui/src/app/frame.rs +++ b/crates/ui-egui/src/app/frame.rs @@ -66,6 +66,7 @@ impl LightcraftApp { if import_tick_allowed(ctx) { import::tick(self, ctx); } + panels::library_problem::import_waiting(self, ctx); lightroom_import::tick(self, ctx); recheck::tick(self); tasks::poll(self, ctx); diff --git a/crates/ui-egui/src/dialogs/settings/libraries.rs b/crates/ui-egui/src/dialogs/settings/libraries.rs index 8e41e8dd4..40e0d6385 100644 --- a/crates/ui-egui/src/dialogs/settings/libraries.rs +++ b/crates/ui-egui/src/dialogs/settings/libraries.rs @@ -11,6 +11,10 @@ pub(crate) fn can_switch_library(app: &LightcraftApp) -> Result<(), String> { if app.import.is_some() || app.scan.is_some() { return Err("wait for the import to finish before switching libraries".into()); } + // they were meant for the library open now (issue #2779) + if !app.waiting_import.files.is_empty() { + return Err("wait for the photos waiting to be imported before switching libraries".into()); + } Ok(()) } diff --git a/crates/ui-egui/src/panels/library_problem.rs b/crates/ui-egui/src/panels/library_problem.rs index 7f69775e3..b470e1420 100644 --- a/crates/ui-egui/src/panels/library_problem.rs +++ b/crates/ui-egui/src/panels/library_problem.rs @@ -823,8 +823,9 @@ fn open(app: &mut LightcraftApp, path: Option) { /// Import the files that waited for a library, in the background like files dropped on the window /// (issue #374). While another import or a Synchronize Folder runs they keep waiting, and a toast -/// says why once (issue #2779). -fn import_waiting(app: &mut LightcraftApp, ctx: &egui::Context) { +/// says why once (issue #2779). Called from the background logic, so they go in as soon as that +/// ends even while the window is hidden. +pub(crate) fn import_waiting(app: &mut LightcraftApp, ctx: &egui::Context) { if app.waiting_import.files.is_empty() { return; } @@ -854,7 +855,6 @@ pub fn logic(app: &mut LightcraftApp, ctx: &egui::Context) { let files = app.library_problem.take().map(|p| p.pending_import).unwrap_or_default(); app.waiting_import.files.extend(files); } - import_waiting(app, ctx); let batch_in_flight = app.synthetic_in_flight; alt_at_launch(app, ctx); // Alt/Option can create the launch list here, after the frame's input was accepted. Drop diff --git a/crates/ui-egui/src/tests/library_problem.rs b/crates/ui-egui/src/tests/library_problem.rs index 29a0aca66..d3f8eb78a 100644 --- a/crates/ui-egui/src/tests/library_problem.rs +++ b/crates/ui-egui/src/tests/library_problem.rs @@ -817,8 +817,13 @@ fn waiting_files_wait_for_a_running_import_instead_of_being_dropped() { let img = lightcraft_raster::Rgba8 { width: 8, height: 8, data: vec![[v, 3, 9, 255]; 64] }; lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &lightcraft_codecs::EncodeMeta::default()).unwrap() }; - let (busy, waiting) = (dir.join("busy.png"), dir.join("waiting.png")); - std::fs::write(&busy, png(10)).unwrap(); + // enough photos that the first import is still running two frames later + let busy = dir.join("busy"); + std::fs::create_dir_all(&busy).unwrap(); + for i in 0..100u8 { + std::fs::write(busy.join(format!("{i}.png")), png(i)).unwrap(); + } + let waiting = dir.join("waiting.png"); std::fs::write(&waiting, png(200)).unwrap(); let app = LightcraftApp::new(lightcraft_engine::Session::new().with_fs(), Services { png: None, ..Default::default() }); @@ -832,13 +837,30 @@ fn waiting_files_wait_for_a_running_import_instead_of_being_dropped() { // a library opens another way (File ▸ Open Library…) while an import is running h.app.session.open_library(&lib, false).unwrap(); crate::import::start_paths(&mut h.app, vec![busy.to_string_lossy().to_string()]).unwrap(); + // the UI pass takes the files; the next background tick tries to import them + h.step(); h.step(); assert!(h.app.library_problem.is_none(), "the problem is over"); assert_eq!(h.app.waiting_import.files, vec![waiting.to_string_lossy().to_string()], "the waiting file is kept"); let toast = h.app.ui.toast.as_ref().map(|t| t.0.clone()).unwrap_or_default(); assert!(toast.contains("An import is running") && toast.contains("once it ends"), "{toast}"); + // the files belong to this library: no switch while they wait, also when what they wait for + // is a Synchronize Folder, which doesn't hold switching back by itself (the import set aside) + let running = h.app.import.take(); + let r = h.app.run("app.openLibrary", json!({"path": dir.join("other").to_string_lossy()})); + h.app.import = running; + assert!(r.as_ref().is_err_and(|e| e.contains("waiting")), "{r:?}"); + assert_eq!(h.app.session.library.as_ref().map(|l| l.dir.clone()), Some(lib.clone())); - assert!(h.step_until(T, |h| h.app.import.is_none() && h.app.session.catalog.len() == 2), "{} photos", h.app.session.catalog.len()); + // the window is hidden from here on: only the background logic runs, and the waiting file + // still goes in once the first import ends + let t0 = std::time::Instant::now(); + while !(h.app.import.is_none() && h.app.session.catalog.len() == 101) && t0.elapsed() < T { + let ctx = h.view.ctx.clone(); + h.app.logic(&ctx); + std::thread::sleep(Duration::from_millis(2)); + } + assert_eq!(h.app.session.catalog.len(), 101, "both imports went in while hidden"); assert!(h.app.waiting_import.files.is_empty()); let _ = h.app.session.close_library(); let _ = std::fs::remove_dir_all(&dir);