mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 03:42:11 +00:00
Merge pull request #1980 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:
6 files changed
+110
-28
No files matched your search
Generated
+7
-8
@@ -847,12 +847,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",
|
||||
@@ -875,7 +875,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-media"
|
||||
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 = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
@@ -883,7 +883,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-pdf"
|
||||
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 = [
|
||||
"krilla",
|
||||
"thiserror 2.0.21",
|
||||
@@ -892,7 +892,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",
|
||||
@@ -912,7 +912,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",
|
||||
@@ -2791,7 +2791,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "krilla"
|
||||
version = "0.8.2"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=c5a1cbfb5f9049f9270955caffb5bee5997aebf7#c5a1cbfb5f9049f9270955caffb5bee5997aebf7"
|
||||
source = "git+https://github.com/dotCooCoo/craft-libs?rev=c7c84230631cbc01ee928f0a0e0d838b1ed612ae#c7c84230631cbc01ee928f0a0e0d838b1ed612ae"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bumpalo",
|
||||
@@ -6571,7 +6571,6 @@ dependencies = [
|
||||
"nix",
|
||||
"typed-path",
|
||||
"windows-sys 0.61.2",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+5
-5
@@ -59,11 +59,11 @@ harfrust = "0.13"
|
||||
vello_cpu = "0.2"
|
||||
vello_common = { version = "0.2", default-features = false }
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "tiff", "bmp"] }
|
||||
craft-media = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0", default-features = false }
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0", default-features = false, features = ["write", "write-simple-text"] }
|
||||
craft-media = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", version = "=0.1.0", default-features = false }
|
||||
craft-pdf = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", version = "=0.1.0", default-features = false, features = ["write", "write-simple-text"] }
|
||||
# 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-ui = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.1.0" }
|
||||
craft-update = { git = "https://github.com/dotCooCoo/craft-libs", version = "=0.1.0", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", features = ["egui"] }
|
||||
craft-ui = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", version = "=0.1.0" }
|
||||
i_overlay = "9"
|
||||
quick-xml = "0.41"
|
||||
zip = { version = "3", default-features = false, features = ["deflate"] }
|
||||
@@ -109,7 +109,7 @@ codegen-units = 16
|
||||
debug = 0
|
||||
|
||||
[patch.crates-io]
|
||||
krilla = { git = "https://github.com/storytold/craft-libs", rev = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7", version = "=0.8.2" }
|
||||
krilla = { git = "https://github.com/dotCooCoo/craft-libs", rev = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae", version = "=0.8.2" }
|
||||
ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
|
||||
@@ -51,6 +51,9 @@ impl eframe::App for App {
|
||||
}
|
||||
fn on_exit(&mut self) {
|
||||
save_prefs(&self.0);
|
||||
// The window is closing for good: a ready update's helper starts now and installs it once
|
||||
// the process has exited.
|
||||
self.0.start_pending_update();
|
||||
// macOS (#279): 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
|
||||
|
||||
@@ -137,6 +137,14 @@ impl SlideApp {
|
||||
updates_ui::enable(self);
|
||||
}
|
||||
|
||||
/// Start the helper of an update that is ready, so that it installs once the process has
|
||||
/// exited. The desktop shell calls it in `on_exit`, which ends the process on macOS before the
|
||||
/// app is dropped. Later calls do nothing.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn start_pending_update(&mut self) {
|
||||
updates_ui::start_pending_update(self);
|
||||
}
|
||||
|
||||
/// A message that must be seen wherever the user is: a toast, which also shows over the start
|
||||
/// screen and the File tab, where there is no status bar.
|
||||
pub fn notify(&mut self, msg: impl Into<String>) {
|
||||
|
||||
@@ -13,13 +13,11 @@ pub(crate) struct Runtime {
|
||||
/// The desktop host turns the check at start on; tests and the web build leave it off.
|
||||
enabled: bool,
|
||||
check_requested: bool,
|
||||
/// A manual check's result dialog is showing (checking, up to date or failed).
|
||||
result_open: bool,
|
||||
}
|
||||
|
||||
impl Default for Runtime {
|
||||
fn default() -> Self {
|
||||
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), enabled: false, check_requested: false, result_open: false }
|
||||
Self { updater: Updater::new(&crate::UPDATE_APP, VERSION), enabled: false, check_requested: false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +29,15 @@ pub(crate) fn enable(app: &mut SlideApp) {
|
||||
pub(crate) fn request_check(app: &mut SlideApp) {
|
||||
if !matches!(app.updates_rt.updater.status(), Status::Checking) {
|
||||
app.updates_rt.check_requested = true;
|
||||
app.updates_rt.result_open = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the helper of an update that is ready, so that it installs once the process has exited.
|
||||
/// Later calls do nothing.
|
||||
pub(crate) fn start_pending_update(app: &mut SlideApp) {
|
||||
app.updates_rt.updater.start_pending_update();
|
||||
}
|
||||
|
||||
pub(crate) fn tick(app: &mut SlideApp, ctx: &egui::Context) {
|
||||
let rt = &mut app.updates_rt;
|
||||
if std::mem::take(&mut rt.check_requested) {
|
||||
@@ -46,21 +49,31 @@ pub(crate) fn tick(app: &mut SlideApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
pub(crate) fn show(app: &mut SlideApp, ctx: &egui::Context) {
|
||||
// The restart helper is scheduled before the app closes, so no update starts over unsaved work.
|
||||
// An installation starts, and a ready update asks the app to close, only while nothing is unsaved.
|
||||
let clean = !app.session.documents().iter().any(|d| d.is_dirty());
|
||||
match app.updates_rt.updater.show_if(ctx, clean) {
|
||||
Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
|
||||
// The app's close path asks about unsaved work; the restart helper relaunches once the process exits.
|
||||
// The app's close path asks about unsaved work; `on_exit` starts the restart helper.
|
||||
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
||||
Action::None => {}
|
||||
}
|
||||
// The result window reports a requested check, and at the first start after an update what
|
||||
// that update came to (installed, or why it failed).
|
||||
let rt = &mut app.updates_rt;
|
||||
if rt.result_open {
|
||||
match CheckResultDialog::new(&crate::UPDATE_APP, VERSION).show(ctx, rt.updater.status()) {
|
||||
CheckResultResponse::Close => rt.result_open = false,
|
||||
CheckResultResponse::CheckAgain => rt.check_requested = true,
|
||||
CheckResultResponse::None => {}
|
||||
}
|
||||
if !matches!(rt.updater.status(), Status::Idle) {
|
||||
let response = CheckResultDialog::new(&crate::UPDATE_APP, VERSION).show(ctx, rt.updater.status());
|
||||
answer(rt, response);
|
||||
}
|
||||
}
|
||||
|
||||
/// Acts on the result window's answer.
|
||||
fn answer(rt: &mut Runtime, response: CheckResultResponse) {
|
||||
match response {
|
||||
CheckResultResponse::Close => rt.updater.dismiss_status(),
|
||||
CheckResultResponse::CheckAgain => rt.check_requested = true,
|
||||
// The next frame's `show_if` asks the window to close once nothing is unsaved.
|
||||
CheckResultResponse::RestartToUpdate => rt.updater.restart_to_update(),
|
||||
CheckResultResponse::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +90,62 @@ mod tests {
|
||||
ctx.run_ui(egui::RawInput::default(), |ui| show(&mut app, ui.ctx())).textures_delta.clear();
|
||||
assert!(app.updates_rt.updater.prompt_open());
|
||||
}
|
||||
|
||||
/// An app whose updater keeps its outcome records under `root`, reads no launcher choice and
|
||||
/// asks no server, with the check at start turned on.
|
||||
fn app_with_records(root: &std::path::Path) -> SlideApp {
|
||||
let mut app = SlideApp::new(deckcraft_engine::Session::new(), crate::Services::default());
|
||||
app.updates_rt.updater = Updater::new(&crate::UPDATE_APP, VERSION)
|
||||
.with_paths(craft_update::Paths::under(root))
|
||||
.with_pins_path(root.join("pins.json"))
|
||||
.with_source(std::sync::Arc::new(|| Ok(None)));
|
||||
app.updates_rt.enabled = true;
|
||||
app
|
||||
}
|
||||
|
||||
fn records_dir(name: &str) -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("deckcraft-updates-{name}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
dir
|
||||
}
|
||||
|
||||
/// The first start after an update reports it; OK dismisses the report.
|
||||
#[test]
|
||||
fn the_start_after_an_update_reports_it_until_dismissed() {
|
||||
let root = records_dir("installed");
|
||||
std::fs::create_dir_all(root.join("data")).unwrap();
|
||||
std::fs::write(root.join("data").join("self-update-done.txt"), VERSION).unwrap();
|
||||
let mut app = app_with_records(&root);
|
||||
let ctx = egui::Context::default();
|
||||
ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
tick(&mut app, ui.ctx());
|
||||
show(&mut app, ui.ctx());
|
||||
})
|
||||
.textures_delta
|
||||
.clear();
|
||||
assert!(matches!(app.updates_rt.updater.status(), Status::Updated(_)), "{:?}", app.updates_rt.updater.status());
|
||||
answer(&mut app.updates_rt, CheckResultResponse::Close);
|
||||
assert_eq!(app.updates_rt.updater.status(), &Status::Idle);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
/// The first start after a failed update says why; Check Again asks for a new check.
|
||||
#[test]
|
||||
fn the_start_after_a_failed_update_reports_why_and_offers_a_new_check() {
|
||||
let root = records_dir("failed");
|
||||
std::fs::create_dir_all(root.join("data").join("logs")).unwrap();
|
||||
std::fs::write(root.join("data").join("logs").join("self-update-error.txt"), "the disk is full").unwrap();
|
||||
let mut app = app_with_records(&root);
|
||||
let ctx = egui::Context::default();
|
||||
ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
tick(&mut app, ui.ctx());
|
||||
show(&mut app, ui.ctx());
|
||||
})
|
||||
.textures_delta
|
||||
.clear();
|
||||
assert_eq!(app.updates_rt.updater.status(), &Status::InstallFailed("the disk is full".into()));
|
||||
answer(&mut app.updates_rt, CheckResultResponse::CheckAgain);
|
||||
assert!(app.updates_rt.check_requested);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ use std::process::Command;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
const SHARED_REV: &str = "c5a1cbfb5f9049f9270955caffb5bee5997aebf7";
|
||||
const SHARED_REPO: &str = "https://github.com/dotCooCoo/craft-libs";
|
||||
const SHARED_REV: &str = "c7c84230631cbc01ee928f0a0e0d838b1ed612ae";
|
||||
const REGISTRY: &str = "registry+https://github.com/rust-lang/crates.io-index";
|
||||
|
||||
pub fn run(root: &Path) -> Result<(), String> {
|
||||
@@ -70,7 +71,7 @@ fn validate(metadata: &Value) -> Result<(), String> {
|
||||
}
|
||||
matches.first().copied().ok_or_else(|| format!("missing {name}"))
|
||||
};
|
||||
let source = format!("git+https://github.com/storytold/craft-libs?rev={SHARED_REV}#{SHARED_REV}");
|
||||
let source = format!("git+{SHARED_REPO}?rev={SHARED_REV}#{SHARED_REV}");
|
||||
for (name, version, expected_source, expected_features) in [
|
||||
("craft-pdf", "0.1.0", source.as_str(), vec!["write", "write-simple-text"]),
|
||||
("krilla", "0.8.2", source.as_str(), vec!["raster-images", "rayon", "simple-text"]),
|
||||
@@ -143,7 +144,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
fn metadata() -> Value {
|
||||
let source = format!("git+https://github.com/storytold/craft-libs?rev={SHARED_REV}#{SHARED_REV}");
|
||||
let source = format!("git+{SHARED_REPO}?rev={SHARED_REV}#{SHARED_REV}");
|
||||
json!({
|
||||
"packages": [
|
||||
{"id":"app", "name":"deckcraft-pdf"},
|
||||
|
||||
Reference in new issue
Block a user