Updates: the updater reports installing, ready, installed and failed, and installs at exit

craft-update moves from craft-libs c5a1cbfb to c7c84230, a branch that is
craft-libs main at 0a9d1f48 with storytold/craft-libs#687 and #696 merged
in. Cargo.lock changes the craft-update entry and drops zopfli from zip.

The check result window now shows whenever the updater's status is not
idle, not only after Help > Check for Updates, and never over the startup
prompt. At the first start after an update it reports that the update
installed or why it failed. Restart to Update calls
Updater::restart_to_update. The install helper starts when the app exits
and drops the updater, so a canceled close keeps the update ready.
This commit is contained in:
dotCooCoo
2026-11-08 12:45:18 -08:00
parent 909af5e389
commit 3a9aebc210
3 changed files with 46 additions and 37 deletions
Generated
+1 -14
View File
@@ -1211,7 +1211,7 @@ dependencies = [
[[package]]
name = "craft-update"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
dependencies = [
"chrono",
"dunce",
@@ -6223,7 +6223,6 @@ dependencies = [
"nix 0.31.3",
"typed-path",
"windows-sys 0.61.2",
"zopfli",
]
[[package]]
@@ -6238,18 +6237,6 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zopfli"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]
name = "zune-core"
version = "0.5.3"
+1 -1
View File
@@ -71,7 +71,7 @@ egui-wgpu = "0.37"
# Monotonic time on native and web (performance.now on wasm).
web-time = "1.1"
# craft-update: app-specific pretend versions apply to startup and manual checks.
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", features = ["egui"] }
craft-update = { git = "https://github.com/dotCooCoo/craft-libs", version = "=0.1.0", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", features = ["egui"] }
rfd = "0.17"
arboard = { version = "3.6", default-features = false }
+44 -22
View File
@@ -17,15 +17,13 @@ pub struct Runtime {
enabled: bool,
/// Help ▸ Check for Updates… was chosen; the next frame's `tick` starts it with the context.
check_requested: bool,
/// The check-result dialog is open (from a requested check until it is closed).
result_open: bool,
/// The current result was already written to the command history.
result_echoed: bool,
}
impl Default for Runtime {
fn default() -> Self {
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), enabled: false, check_requested: false, result_open: false, result_echoed: false }
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), enabled: false, check_requested: false, result_echoed: false }
}
}
@@ -39,7 +37,6 @@ impl CadApp {
/// Help ▸ Check for Updates…: check now, and show the result when there is nothing newer.
pub fn check_now(app: &mut CadApp) {
app.updates_rt.check_requested = true;
app.updates_rt.result_open = true;
app.updates_rt.result_echoed = false;
}
@@ -55,45 +52,70 @@ pub fn tick(app: &mut CadApp, ctx: &egui::Context) {
let outcome = match rt.updater.status() {
Status::UpToDate => Some(crate::tl!("CADCraft is up to date.")),
Status::Failed(_) => Some(crate::tl!("Could not check for updates.")),
Status::Idle | Status::Checking => None,
};
let Some(message) = outcome else {
// A check that found an update leaves `Idle`; the prompt takes over from the dialog.
if rt.result_open && matches!(rt.updater.status(), Status::Idle) {
rt.result_open = false;
Status::Idle | Status::Checking | Status::Installing | Status::Ready | Status::Managed | Status::Updated(_) | Status::InstallFailed(_) => {
None
}
return;
};
if !rt.result_open {
rt.updater.dismiss_status();
} else if !std::mem::replace(&mut rt.result_echoed, true) {
if let Some(message) = outcome
&& !std::mem::replace(&mut rt.result_echoed, true)
{
app.session.echo(message);
}
}
/// Whether the check result window shows: for every status but idle (a requested check, and the
/// first start after an update, which reports that it installed or why it failed), and never
/// over the startup prompt.
fn result_window_shows(status: &Status, prompt_open: bool) -> bool {
!prompt_open && !matches!(status, Status::Idle)
}
/// Draw the prompt while there is one and act on its answer.
pub fn show(app: &mut CadApp, ctx: &egui::Context) {
// The restart helper is scheduled before the app closes, so no update starts over unsaved work.
// The close for a ready update waits until no drawing holds unsaved changes.
let clean = !app.session.docs.iter().any(|d| d.is_dirty());
let rt = &mut app.updates_rt;
if rt.result_open {
if result_window_shows(rt.updater.status(), rt.updater.prompt_open()) {
match CheckResultDialog::new(&crate::UPDATE_APP, VERSION).show(ctx, rt.updater.status()) {
CheckResultResponse::Close => {
rt.result_open = false;
rt.updater.dismiss_status();
}
CheckResultResponse::Close => rt.updater.dismiss_status(),
CheckResultResponse::CheckAgain => {
rt.result_echoed = false;
rt.check_requested = true;
}
// The next `show_if` asks the window to close once nothing is unsaved.
CheckResultResponse::RestartToUpdate => rt.updater.restart_to_update(),
CheckResultResponse::None => {}
}
}
match app.updates_rt.updater.show_if(ctx, clean) {
Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
// The guarded close path asks about unsaved changes; the restart helper relaunches once the
// process has exited.
// The guarded close path asks about unsaved changes. The install helper starts when the
// app exits and drops the updater.
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
Action::None => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn result_window_shows_every_status_but_idle() {
let shown = [
Status::Checking,
Status::UpToDate,
Status::Failed("no network".into()),
Status::Installing,
Status::Ready,
Status::Managed,
Status::Updated("0.2.0".into()),
Status::InstallFailed("disk full".into()),
];
for status in &shown {
assert!(result_window_shows(status, false), "{status:?}");
assert!(!result_window_shows(status, true), "{status:?} over the prompt");
}
assert!(!result_window_shows(&Status::Idle, false));
}
}