diff --git a/Cargo.lock b/Cargo.lock index ee3905a8a..a22c489f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1059,7 +1059,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=c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e#c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e" dependencies = [ "chrono", "dunce", @@ -8040,7 +8040,6 @@ dependencies = [ "nix", "typed-path", "windows-sys 0.61.2", - "zopfli", ] [[package]] @@ -8055,18 +8054,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.4.12" diff --git a/Cargo.toml b/Cargo.toml index a3e3e9890..8a1ee4ab4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ craft-window = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63 # The `.vectorcraft` envelope: its members, version, embedded images and the data after the JSON. craft-vectorcraft-format = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb" } # 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 = "c365d75c48b0e47f4b43cdb6592d0cdc39d2d52e", features = ["egui"] } # internal vectorcraft-geom = { path = "crates/geom" } vectorcraft-color = { path = "crates/color" } diff --git a/apps/vectorcraft/src/app.rs b/apps/vectorcraft/src/app.rs index 2dfc4e6f4..f47b10e68 100644 --- a/apps/vectorcraft/src/app.rs +++ b/apps/vectorcraft/src/app.rs @@ -92,6 +92,9 @@ impl eframe::App for App { } fn on_exit(&mut self) { file_access::unconfined(|| save_prefs(&self.app)); + // A ready update installs once the process has exited. The macOS exit below skips + // dropping the app, so the helper is started here. + self.app.start_pending_update(); end_before_teardown(self.frames); } } diff --git a/crates/ui-egui/src/updates_ui.rs b/crates/ui-egui/src/updates_ui.rs index a62a221c5..c8dade12b 100644 --- a/crates/ui-egui/src/updates_ui.rs +++ b/crates/ui-egui/src/updates_ui.rs @@ -5,7 +5,7 @@ use serde_json::Value; use crate::VectorcraftApp; #[cfg(not(target_arch = "wasm32"))] -use craft_update::updater::{Action, Updater}; +use craft_update::updater::{Action, Status, Updater}; #[cfg(not(target_arch = "wasm32"))] use craft_update::{CheckResultDialog, CheckResultResponse}; @@ -20,10 +20,10 @@ pub(crate) struct Runtime { /// The context of the last frame, to wake the UI when a check is queued from a menu callback. #[cfg(not(target_arch = "wasm32"))] repaint: Option, - /// Whether the result of Help ▸ Check for Updates is in view. - pub(crate) manual_window_open: bool, /// The web build has no updater; its window only says so. #[cfg(target_arch = "wasm32")] + pub(crate) manual_window_open: bool, + #[cfg(target_arch = "wasm32")] pub(crate) status_message: Option, } @@ -38,6 +38,7 @@ impl Default for Runtime { check_requested: false, #[cfg(not(target_arch = "wasm32"))] repaint: None, + #[cfg(target_arch = "wasm32")] manual_window_open: false, #[cfg(target_arch = "wasm32")] status_message: None, @@ -51,13 +52,20 @@ impl VectorcraftApp { pub fn set_startup_update_check(&mut self, on: bool) { self.updates_rt.startup_check = on; } + + /// Starts the install helper of an update that is ready, so it installs once the process has + /// exited (does nothing when no update is ready). Call it in `on_exit` when the process ends + /// there, since the app is then never dropped. + #[cfg(not(target_arch = "wasm32"))] + pub fn start_pending_update(&mut self) { + self.updates_rt.updater.start_pending_update(); + } } pub(crate) fn check(app: &mut VectorcraftApp) -> Result { #[cfg(not(target_arch = "wasm32"))] { app.updates_rt.check_requested = true; - app.updates_rt.manual_window_open = true; if let Some(ctx) = &app.updates_rt.repaint { ctx.request_repaint(); } @@ -87,10 +95,18 @@ pub(crate) fn tick(app: &mut VectorcraftApp, ctx: &egui::Context) { let _ = (app, ctx); } +/// 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. +#[cfg(not(target_arch = "wasm32"))] +fn result_window_shows(status: &Status, prompt_open: bool) -> bool { + !prompt_open && !matches!(status, Status::Idle) +} + pub(crate) fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { #[cfg(not(target_arch = "wasm32"))] { - // 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 document holds unsaved work. let clean = !crate::unsaved::any_dirty(app); match app.updates_rt.updater.show_if(ctx, clean) { Action::OpenUrl(url) => app.open_url(&url), @@ -99,20 +115,15 @@ pub(crate) fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { } // An available update goes to the prompt above, which replaces the result dialog. let rt = &mut app.updates_rt; - if rt.manual_window_open && rt.updater.prompt_open() { - rt.manual_window_open = false; - } - if rt.manual_window_open { + if result_window_shows(rt.updater.status(), rt.updater.prompt_open()) { match CheckResultDialog::new(&crate::UPDATE_APP, env!("CARGO_PKG_VERSION")).show(ctx, rt.updater.status()) { CheckResultResponse::None => {} - CheckResultResponse::Close => rt.manual_window_open = false, + // Closing the dialog dismisses the outcome it showed. + CheckResultResponse::Close => rt.updater.dismiss_status(), CheckResultResponse::CheckAgain => rt.updater.check_now(ctx), + CheckResultResponse::RestartToUpdate => rt.updater.restart_to_update(), } } - // Closing the dialog dismisses the outcome it showed. - if !rt.manual_window_open { - rt.updater.dismiss_status(); - } } #[cfg(target_arch = "wasm32")] @@ -142,13 +153,12 @@ pub(crate) fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { #[cfg(test)] mod tests { use super::*; - #[cfg(not(target_arch = "wasm32"))] - use craft_update::updater::Status; use vectorcraft_engine::Session; #[test] fn default_runtime_is_idle() { let rt = Runtime::default(); + #[cfg(target_arch = "wasm32")] assert!(!rt.manual_window_open); #[cfg(not(target_arch = "wasm32"))] { @@ -158,6 +168,26 @@ mod tests { } } + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn result_window_shows_every_status_but_idle() { + let shown = [ + Status::Checking, + Status::UpToDate, + Status::Failed("offline".into()), + Status::Installing, + Status::Ready, + Status::Managed, + Status::Updated("99.0.0".into()), + Status::InstallFailed("locked".into()), + ]; + for status in &shown { + assert!(result_window_shows(status, false), "{status:?} shows"); + assert!(!result_window_shows(status, true), "{status:?} waits for the prompt"); + } + assert!(!result_window_shows(&Status::Idle, false)); + } + #[test] #[cfg(not(target_arch = "wasm32"))] fn startup_prompt_shows_and_closes() {