Issue #3406: Auto Import says when it can't read the watched folder

A watched folder that was removed, unmounted or not allowed looked like one with nothing new: the listing error was logged at debug only. It is now logged as a warning once when it starts (and once when the folder can be read again), and Settings > Import shows the reason under the watched folder.
This commit is contained in:
Antonio Sokic
2026-10-30 19:58:09 +01:00
parent 5194cc20de
commit 052d95cd66
4 changed files with 52 additions and 4 deletions
+20 -4
View File
@@ -91,11 +91,27 @@ impl LightcraftApp {
&& !self.tasks.is_running(LABEL)
{
self.ui.auto_import_at = now;
let work = move || lightcraft_engine::cmd::library::list_auto_import_folder(&folder);
let done = |app: &mut LightcraftApp, _ctx: &egui::Context, listing: Result<Vec<(String, u64)>, String>| {
let listed = folder.clone();
let work = move || lightcraft_engine::cmd::library::list_auto_import_folder(&listed);
let done = move |app: &mut LightcraftApp, _ctx: &egui::Context, listing: Result<Vec<(String, u64)>, String>| {
let listing = match listing {
Ok(l) => l,
Err(e) => return log::debug!("auto import: {e}"),
Ok(l) => {
if app.ui.auto_import_error.take().is_some() {
log::info!("auto import: the watched folder can be read again: {folder}");
}
l
}
// removed, renamed, unmounted or not allowed: said once, not every few seconds
Err(e) => {
let error = Some((folder, e));
if app.ui.auto_import_error != error {
if let Some((_, e)) = &error {
log::warn!("auto import: can't read the watched folder {e}");
}
app.ui.auto_import_error = error;
}
return;
}
};
let p = serde_json::json!({"listing": listing, "start": false});
let Ok(r) = app.session.execute("library.autoImportScan", &p) else { return };
@@ -163,6 +163,15 @@ pub(super) fn import_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens)
let _ = app.run("library.autoImport", json!({"folder": null}));
}
});
// the last listing of the watched folder failed: say why (it reads "OS error: reason")
if let (Some(folder), Some((failed, e))) = (&d.auto_folder, &app.ui.auto_import_error)
&& folder == failed
{
let reason = e.strip_prefix(folder.as_str()).and_then(|r| r.strip_prefix(": ")).unwrap_or(e);
row(ui, t, "", |ui| {
ui.label(RichText::new(crate::i18n::tr_format!("Can't read this folder: {}", reason)).size(11.0).color(t.caution));
});
}
if d.auto_folder.is_some() {
row(ui, t, "", |ui| {
let mut copy = d.auto_copy;
+5
View File
@@ -166,6 +166,10 @@ pub struct UiState {
/// scanning).
#[serde(skip)]
pub auto_import_at: f64,
/// The watched folder that couldn't be listed and why, as the last listing found it: logged once when it
/// starts and once when the folder can be read again, and shown in Settings ▸ Import.
#[serde(skip)]
pub auto_import_error: Option<(String, String)>,
/// When the automatic XMP writes that failed were last tried again (monotonic app time), and how
/// many were waiting then.
#[serde(skip)]
@@ -421,6 +425,7 @@ impl Default for UiState {
external_edits: Vec::new(),
was_focused: true,
auto_import_at: 0.0,
auto_import_error: None,
sidecar_retry_at: 0.0,
sidecars_waiting: 0,
known_cards: None,
+18
View File
@@ -1107,3 +1107,21 @@ fn public_drop_take_preserves_reentered_barrier_order_and_clears_protection() {
assert_eq!(photos(&h), 0, "neither taken handles nor refused arrivals replay");
}
}
/// A watched folder that can't be listed (removed, unmounted, not allowed) is noted with the reason, for
/// Settings ▸ Import to show, and the note goes once the folder can be read again.
#[test]
fn auto_import_notes_a_watched_folder_it_cannot_read() {
let dir = Scratch::new("auto-unreadable");
let mut h = app(&dir);
let watch = dir.path("watch");
h.app.session.import_defaults.auto_folder = Some(watch.clone());
assert!(h.step_until(T, |h| h.app.ui.auto_import_error.is_some()), "the missing folder is noted");
let (folder, e) = h.app.ui.auto_import_error.clone().unwrap();
assert_eq!(folder, watch);
assert!(e.starts_with(&watch) && e.len() > watch.len() + 2, "the error says why: {e}");
write_png(&dir.path("watch/w.png"), 3);
assert!(h.step_until(T, |h| h.app.ui.auto_import_error.is_none()), "readable again: the note goes");
assert!(h.step_until(T, |h| photos(h) == 1), "and the photo arrives");
h.app.session.import_defaults.auto_folder = None;
}