Merge pull request #3621 from dotCooCoo/chore/craft-update-updater-fixes

Updates: the updater reports installing, ready, installed and failed, and installs at exit
This commit is contained in:
Dylan M. Taylor
2026-11-08 22:12:47 -05:00
committed by GitHub
7 changed files with 69 additions and 32 deletions
Generated
+8 -9
View File
@@ -910,12 +910,12 @@ dependencies = [
[[package]]
name = "craft-fonts-build"
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"
[[package]]
name = "craft-i18n"
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 = [
"fixed_decimal",
"icu_decimal",
@@ -938,7 +938,7 @@ dependencies = [
[[package]]
name = "craft-print"
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 = [
"craft-winprint-gdi",
"log",
@@ -952,7 +952,7 @@ dependencies = [
[[package]]
name = "craft-ui"
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 = [
"craft-fonts-build",
"craft-i18n",
@@ -972,7 +972,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",
@@ -1018,7 +1018,7 @@ dependencies = [
[[package]]
name = "craft-window"
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 = [
"egui",
"objc2 0.6.5",
@@ -1029,7 +1029,7 @@ dependencies = [
[[package]]
name = "craft-winprint-gdi"
version = "0.1.0"
source = "git+https://github.com/storytold/winprint.rs?rev=417ac3c4d14d3da5485a99c7d5ce8f05664ec312#417ac3c4d14d3da5485a99c7d5ce8f05664ec312"
source = "git+https://github.com/storytold/winprint.rs?rev=64e7afd4d1483512385012c340ecaaaf0f3beb7f#64e7afd4d1483512385012c340ecaaaf0f3beb7f"
dependencies = [
"windows-sys 0.61.2",
]
@@ -6485,7 +6485,7 @@ dependencies = [
[[package]]
name = "winprint"
version = "0.2.1"
source = "git+https://github.com/storytold/winprint.rs?rev=5f80b9baf6b58937c401656634e42d33f00ddf49#5f80b9baf6b58937c401656634e42d33f00ddf49"
source = "git+https://github.com/storytold/winprint.rs?rev=64e7afd4d1483512385012c340ecaaaf0f3beb7f#64e7afd4d1483512385012c340ecaaaf0f3beb7f"
dependencies = [
"flate2",
"fmt-derive",
@@ -6935,7 +6935,6 @@ dependencies = [
"nix",
"typed-path",
"windows-sys 0.61.2",
"zopfli",
]
[[package]]
+3 -3
View File
@@ -26,9 +26,9 @@ gridcraft-mcp = { path = "crates/mcp" }
gridcraft-pdf = { path = "crates/pdf" }
gridcraft-math = { path = "crates/math" }
# Every craft-libs crate (craft-update and craft-window here, craft-ui in ui-egui, craft-print in
# the app) comes from one storytold/craft-libs revision.
# the app) comes from one craft-libs revision.
# craft-update: app-specific pretend versions apply to startup and manual checks.
craft-update = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0", features = ["egui"] }
craft-update = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", version = "=0.1.0", features = ["egui"] }
# foundation
thiserror = "2"
@@ -71,7 +71,7 @@ skrifa = { version = "0.44", default-features = false, features = ["std"] }
epaint_default_fonts = "0.37"
# One native window per workbook: the window list, Window menu titles and the immediate-viewport
# driver. Same craft-libs revision as craft-update above.
craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", default-features = false, features = ["egui"] }
craft-window = { version = "0.1.0", git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", default-features = false, features = ["egui"] }
[workspace.lints.rust]
unsafe_code = "forbid"
+1 -1
View File
@@ -34,7 +34,7 @@ winit = { version = "0.30", default-features = false, features = ["x11", "waylan
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Same craft-libs revision as the workspace's craft-update and craft-window (see the root
# Cargo.toml).
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
craft-print = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
craft-update = { workspace = true }
rfd = { workspace = true }
arboard = { workspace = true, features = ["image-data"] }
+3
View File
@@ -64,6 +64,9 @@ impl eframe::App for App {
fn on_exit(&mut self) {
print::finish_pending_jobs();
save_prefs(&self.0);
// A ready update installs once the process has exited. The macOS exit below skips
// dropping the app, so the helper is started here.
self.0.start_pending_update();
// macOS (#117): end the process here, before eframe tears the window down. AccessKit's
// macOS adapter subclasses the window's content view and restores its class on teardown;
// AppKit's Touch Bar support observes that view (KVO), so in the next display cycle
+1 -1
View File
@@ -11,7 +11,7 @@ workspace = true
[dependencies]
# The shared capture slot (`craft_ui::capture`) comes from the one craft-libs revision every
# craft-libs dependency uses (see the root Cargo.toml).
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7" }
craft-ui = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae" }
gridcraft-engine = { workspace = true }
gridcraft-chart = { workspace = true }
# `monochrome_emoji_fonts` restores the Noto Emoji and emoji-icon faces that 0.37 moved behind
+8
View File
@@ -64,4 +64,12 @@ impl SheetApp {
self.message = Some(("GridCraft".into(), "The web version is always the latest release.".into()));
}
}
/// Starts the install helper of an update that is ready, so it installs once the process has
/// exited (a no-op on the web, and when no update is ready). Call it in `on_exit` when the
/// process ends there, since the app is then never dropped.
pub fn start_pending_update(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
updates_ui::start_pending_update(self);
}
}
+45 -18
View File
@@ -15,13 +15,11 @@ pub(crate) struct Runtime {
startup: bool,
/// Help ▸ Check for Updates… was chosen; the next `tick` starts it.
manual: bool,
/// The result dialog of a requested check is showing.
result_open: bool,
}
impl Default for Runtime {
fn default() -> Self {
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), startup: false, manual: false, result_open: false }
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), startup: false, manual: false }
}
}
@@ -38,19 +36,26 @@ pub(crate) fn tick(app: &mut SheetApp, ctx: &egui::Context) {
let rt = &mut app.updates_rt;
if std::mem::take(&mut rt.manual) {
rt.updater.check_now(ctx);
rt.result_open = true;
} else if rt.startup {
rt.updater.check_at_start(ctx);
}
rt.updater.tick(ctx);
// A requested check that found a release leaves the status idle and the prompt opens.
if matches!(rt.updater.status(), Status::Idle) {
rt.result_open = false;
}
}
/// Starts the helper of a ready update; it installs once the process has exited.
pub(crate) fn start_pending_update(app: &mut SheetApp) {
app.updates_rt.updater.start_pending_update();
}
/// 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)
}
pub(crate) fn show(app: &mut SheetApp, 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 workbook holds unsaved work.
let clean = !app.session.documents().iter().any(|d| d.is_dirty());
match app.updates_rt.updater.show_if(ctx, clean) {
Action::OpenUrl(url) => {
@@ -58,20 +63,42 @@ pub(crate) fn show(app: &mut SheetApp, ctx: &egui::Context) {
open(&url);
}
}
// The guarded close path asks about unsaved work; the restart helper relaunches once the
// process has exited.
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
// Quit closes every workbook window. The install helper starts once the app exits.
Action::Close => app.request_quit(ctx),
Action::None => {}
}
if app.updates_rt.result_open {
let rt = &mut app.updates_rt;
let rt = &mut app.updates_rt;
if result_window_shows(rt.updater.status(), rt.updater.prompt_open()) {
match craft_update::CheckResultDialog::new(&crate::UPDATE_APP, VERSION).show(ctx, rt.updater.status()) {
CheckResultResponse::Close => {
rt.updater.dismiss_status();
rt.result_open = false;
}
CheckResultResponse::Close => rt.updater.dismiss_status(),
CheckResultResponse::CheckAgain => rt.updater.check_now(ctx),
// The next `show_if` asks the window to close once nothing is unsaved.
CheckResultResponse::RestartToUpdate => rt.updater.restart_to_update(),
CheckResultResponse::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));
}
}