mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
LR-BEHAV-BG-TASKS: background Reload names itself in bad-ids errors; a stopped Reload doesn't say the raw still can't be decoded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7b2d7b25ea
commit
5b5cdb646e
@@ -8,6 +8,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use lightcraft_engine::EngineError;
|
||||
use lightcraft_engine::cmd::reload;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -25,7 +26,11 @@ pub fn command(app: &mut LightcraftApp, p: &Value) -> Option<Result<Value, Strin
|
||||
}
|
||||
let items = match reload::plan(&mut app.session, p) {
|
||||
Ok(items) => items,
|
||||
Err(e) => return Some(Err(format!("photo.reload: {e}"))),
|
||||
// named as `Session::execute` names a shared parameter check's refusal
|
||||
Err(EngineError::BadParams { cmd, msg }) if cmd.is_empty() => {
|
||||
return Some(Err(EngineError::BadParams { cmd: "photo.reload".into(), msg }.to_string()));
|
||||
}
|
||||
Err(e) => return Some(Err(e.to_string())),
|
||||
};
|
||||
if items.is_empty() {
|
||||
return None;
|
||||
@@ -38,6 +43,7 @@ pub fn command(app: &mut LightcraftApp, p: &Value) -> Option<Result<Value, Strin
|
||||
let probe = reload::file_probe(&app.session);
|
||||
let cancel = Arc::new(AtomicBool::new(false));
|
||||
let flag = cancel.clone();
|
||||
let stopped = cancel.clone();
|
||||
let work = move |task: lightcraft_engine::activity::TaskHandle| {
|
||||
let total = items.len() as u64;
|
||||
task.progress(0, total);
|
||||
@@ -53,6 +59,11 @@ pub fn command(app: &mut LightcraftApp, p: &Value) -> Option<Result<Value, Strin
|
||||
return app.toast_error(ctx, e.to_string());
|
||||
}
|
||||
let cleared = preview_only.iter().filter(|id| app.session.catalog.photo(**id).is_some_and(|ph| ph.preview_only.is_none())).count();
|
||||
// stopped before the files were read: they weren't tried again, so say nothing about them
|
||||
// (a raw that did decode before the stop is still reported)
|
||||
if stopped.load(std::sync::atomic::Ordering::Relaxed) && cleared == 0 {
|
||||
return;
|
||||
}
|
||||
let message = match (count, preview_only.len(), cleared) {
|
||||
(1, 1, 1) => crate::i18n::tr("This raw is now developed from its raw data").to_string(),
|
||||
(1, 1, _) => crate::i18n::tr("This raw variant still can't be decoded").to_string(),
|
||||
|
||||
@@ -228,3 +228,57 @@ fn a_background_reload_writes_the_sidecar_with_auto_write_on() {
|
||||
assert_eq!(app.session.catalog.photo(id).unwrap().meta.gps, Some((41.0, 2.0)));
|
||||
assert!(std::fs::read_to_string(&sidecar).unwrap().contains("GPSLatitude"), "the sidecar was not written");
|
||||
}
|
||||
|
||||
/// Bad `ids` are refused naming the command once, as the engine's own reply does (`wait: true`).
|
||||
#[test]
|
||||
fn reload_with_bad_ids_names_the_command() {
|
||||
let dir = Dir(std::env::temp_dir().join(format!("lc-ui-reload-bad-{}", std::process::id())));
|
||||
let (mut app, _) = app_with_stale_photo(&dir.0);
|
||||
let background = app.run("photo.reload", json!({"ids": "x"})).unwrap_err();
|
||||
let waited = app.run("photo.reload", json!({"ids": "x", "wait": true})).unwrap_err();
|
||||
assert_eq!(background, waited);
|
||||
assert!(background.contains("`photo.reload`"), "{background}");
|
||||
assert!(!background.contains("``") && !background.starts_with("photo.reload:"), "{background}");
|
||||
}
|
||||
|
||||
/// A Reload stopped from the activity stack before its file was read says nothing (the raw wasn't
|
||||
/// tried again); one stopped after the file was read still says what came of it.
|
||||
#[test]
|
||||
fn a_cancelled_reload_reports_only_what_it_did() {
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
let dir = Dir(std::env::temp_dir().join(format!("lc-ui-reload-cancel-{}", std::process::id())));
|
||||
let (mut app, id) = app_with_stale_photo(&dir.0);
|
||||
let ctx = egui::Context::default();
|
||||
let native = app.session.media.file_probe.clone().unwrap();
|
||||
for read in [false, true] {
|
||||
// (the probe has started, the probe may return)
|
||||
let gate = Arc::new((Mutex::new((false, false)), Condvar::new()));
|
||||
let worker_gate = gate.clone();
|
||||
let native = native.clone();
|
||||
app.session.media.file_probe = Some(Arc::new(move |path| {
|
||||
let mut state = worker_gate.0.lock().unwrap();
|
||||
state.0 = true;
|
||||
worker_gate.1.notify_all();
|
||||
let _ = worker_gate.1.wait_timeout_while(state, Duration::from_secs(10), |s| !s.1).unwrap();
|
||||
// a read the cancel stopped comes back as an error, as a file not reached does
|
||||
if read { native(path) } else { Err(format!("{path}: cancelled")) }
|
||||
}));
|
||||
app.ui.toast = None;
|
||||
app.run("photo.reload", json!({"ids": [id.0]})).unwrap();
|
||||
let (state, _) = gate.1.wait_timeout_while(gate.0.lock().unwrap(), Duration::from_secs(5), |s| !s.0).unwrap();
|
||||
assert!(state.0, "the reload started reading");
|
||||
drop(state);
|
||||
let row = app.session.activity.list().into_iter().find(|r| r.kind == "reload").unwrap();
|
||||
app.session.activity.cancel(row.id).unwrap();
|
||||
gate.0.lock().unwrap().1 = true;
|
||||
gate.1.notify_all();
|
||||
assert!(crate::tasks::wait(&mut app, &ctx, Duration::from_secs(10)));
|
||||
if read {
|
||||
assert_eq!(app.session.catalog.photo(id).unwrap().preview_only, None);
|
||||
assert!(app.ui.toast.as_ref().is_some_and(|(t, _, _)| t == "This raw is now developed from its raw data"), "{:?}", app.ui.toast);
|
||||
} else {
|
||||
assert!(app.session.catalog.photo(id).unwrap().preview_only.is_some());
|
||||
assert!(app.ui.toast.is_none(), "a cancelled reload reported {:?}", app.ui.toast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user