fix(cli,fuse,gui): make --wait fail when an import or re-date did

`import-google-photos --wait` and `redate-photos --wait` exited 0 however
the run ended, and a run that failed as a whole left no summary, so the
CLI said no import had run and the app said it finished. Failed runs now
leave a summary with the reason, `--wait` exits 1 on failure, cancel or
failed photos, and the app shows the reason (B239).
This commit is contained in:
nvrl committed 2026-10-16 11:56:22 +02:00
1 parent 937a0a6824
commit 4836c5a84a
15 files changed
+6259 -6102

No files matched your search

+42 -2
View File
@@ -2598,7 +2598,10 @@ fn cmd_import_google_photos(archives: Vec<PathBuf>, dry_run: bool, wait: bool) -
} => continue,
CtlResponse::ImportStatus { summary, .. } => {
print_import_summary(summary.as_ref());
return Ok(());
return finished_run(
"the import",
summary.map(|s| (s.failed, s.cancelled, s.error)),
);
}
CtlResponse::Error { message, kind } => bail!("{}", cli_error(kind, &message)),
other => bail!("unexpected response: {other:?}"),
@@ -2641,7 +2644,10 @@ fn cmd_redate_photos(
CtlResponse::RedateStatus { running: true, .. } => continue,
CtlResponse::RedateStatus { summary, .. } => {
print_redate_summary(summary.as_ref());
return Ok(());
return finished_run(
"the re-date",
summary.map(|s| (s.failed, s.cancelled, s.error)),
);
}
CtlResponse::Error { message, kind } => bail!("{}", cli_error(kind, &message)),
other => bail!("unexpected response: {other:?}"),
@@ -2649,6 +2655,22 @@ fn cmd_redate_photos(
}
}
/// How a run `--wait` followed ended, as the exit status: an error when it
/// failed, was cancelled, or left photos failed, so a script can tell.
/// `outcome` is `(failed, cancelled, error)`; `None` when the daemon has no
/// result, which happens when it restarted during the run.
fn finished_run(what: &str, outcome: Option<(usize, bool, Option<String>)>) -> Result<()> {
match outcome {
None => bail!("{what} ended without a result; the service may have restarted"),
Some((_, _, Some(error))) => bail!("{what} failed: {error}"),
Some((_, true, None)) => bail!("{what} was cancelled"),
Some((failed, false, None)) if failed > 0 => {
bail!("{what} finished, but {failed} photo(s) failed")
}
Some(_) => Ok(()),
}
}
/// A `YYYY-MM-DD` day as epoch seconds at UTC midnight. Reuses the Takeout
/// scanner's date reader, which already accepts exactly this spelling.
fn parse_day(day: &str) -> Result<i64> {
@@ -2684,6 +2706,10 @@ fn print_redate_summary(summary: Option<&pdfs_core::control::RedateSummary>) {
println!("No re-date has run yet.");
return;
};
if let Some(error) = &summary.error {
println!("Failed : {error}");
return;
}
println!("Photos examined : {}", summary.examined);
println!("Mis-dated : {}", summary.candidates);
if summary.dry_run {
@@ -2743,6 +2769,10 @@ fn print_import_summary(summary: Option<&pdfs_core::control::ImportSummary>) {
println!("No import has run yet.");
return;
};
if let Some(error) = &summary.error {
println!("Failed : {error}");
return;
}
println!("Photos in export : {}", summary.found);
println!("Uploaded : {}", summary.uploaded);
println!("Already on Proton: {}", summary.duplicates);
@@ -3757,6 +3787,16 @@ mod login_wait_tests {
assert_eq!(login_poll_delay(u32::MAX), Duration::from_secs(15));
}
#[test]
fn a_waited_for_run_that_did_not_fully_succeed_exits_with_an_error() {
assert!(finished_run("the import", Some((0, false, None))).is_ok());
assert!(finished_run("the import", Some((3, false, None))).is_err());
assert!(finished_run("the import", Some((0, true, None))).is_err());
let failed = finished_run("the import", Some((0, false, Some("not a zip".into()))));
assert!(failed.unwrap_err().to_string().contains("not a zip"));
assert!(finished_run("the import", None).is_err());
}
#[test]
fn waiting_for_a_session_stops_hammering_the_keyring() {
let mut checks_left = 20;