Updates: check only when asked (no check at start)
By the owner's decision there is no update check at start and no in-place install for the foreseeable future. The startup option, its setting and the first-frame trigger are gone; Help ▸ Check for updates is the only request. Settings written by a build that had the option are ignored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2ea5846a16
commit
de9b9e82bc
|
|
@ -460,7 +460,7 @@ impl PrintCraftApp {
|
|||
"page.split" => self.dialog = Some(Dialog::Split),
|
||||
"help.shortcuts" => self.dialog = Some(Dialog::Shortcuts),
|
||||
"help.about" => self.dialog = Some(Dialog::About),
|
||||
"help.check_updates" => self.check_for_updates(true),
|
||||
"help.check_updates" => self.check_for_updates(),
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
|
|
|
|||
|
|
@ -283,8 +283,6 @@ pub struct PrintCraftApp {
|
|||
pub dialog: Option<Dialog>,
|
||||
/// How to ask for the latest release (the desktop app sets it; see `updates`).
|
||||
pub update_source: Option<updates::UpdateSource>,
|
||||
/// Check for updates when PrintCraft starts (opt-in; persisted).
|
||||
pub check_updates_at_start: bool,
|
||||
pub(crate) updates: updates::Updates,
|
||||
pub palette_open: bool,
|
||||
pub palette_query: String,
|
||||
|
|
@ -456,7 +454,6 @@ impl PrintCraftApp {
|
|||
follow_system_theme: false,
|
||||
dialog: None,
|
||||
update_source: None,
|
||||
check_updates_at_start: false,
|
||||
updates: updates::Updates::default(),
|
||||
palette_open: false,
|
||||
palette_query: String::new(),
|
||||
|
|
@ -833,7 +830,6 @@ impl PrintCraftApp {
|
|||
"custom_stamps": stamps_ui::encode(&self.custom_stamps),
|
||||
"javascript": self.session.javascript(),
|
||||
"actions": actions_ui::encode(&self.custom_actions),
|
||||
"check_updates_at_start": self.check_updates_at_start,
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
|
@ -850,9 +846,6 @@ impl PrintCraftApp {
|
|||
if let Ok(t) = serde_json::from_value::<ThemeKind>(v["theme"].clone()) {
|
||||
self.theme = t;
|
||||
}
|
||||
if let Some(b) = v["check_updates_at_start"].as_bool() {
|
||||
self.check_updates_at_start = b;
|
||||
}
|
||||
if let Ok(s) = serde_json::from_value::<Vec<Vec<[f32; 2]>>>(v["signature"].clone())
|
||||
&& s.iter().all(|st| st.iter().all(|p| p.iter().all(|x| x.is_finite())))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
//! The desktop app supplies how to ask ([`PrintCraftApp::update_source`]), so this crate has no
|
||||
//! network code; without a source (the web build, tests) the command opens the releases page.
|
||||
//! PrintCraft never downloads or installs anything itself: the user downloads the new version.
|
||||
//! A check when PrintCraft starts is opt-in (it contacts GitHub without being asked) and stays
|
||||
//! silent unless a newer version exists.
|
||||
//! It asks only when the user does: there is no check at start (the owner's decision).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -52,10 +51,7 @@ pub(crate) enum Check {
|
|||
#[default]
|
||||
Idle,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Running {
|
||||
rx: std::sync::mpsc::Receiver<Result<Release, String>>,
|
||||
manual: bool,
|
||||
},
|
||||
Running(std::sync::mpsc::Receiver<Result<Release, String>>),
|
||||
Done(Result<Release, String>),
|
||||
}
|
||||
|
||||
|
|
@ -64,24 +60,19 @@ pub(crate) struct Updates {
|
|||
pub(crate) check: Check,
|
||||
/// The Updates dialog is showing.
|
||||
pub(crate) open: bool,
|
||||
/// The check at start has been considered (first frame, once the context is known).
|
||||
started: bool,
|
||||
}
|
||||
|
||||
impl PrintCraftApp {
|
||||
/// Check for a newer release. `manual` (Help ▸ Check for updates) shows the outcome, whatever
|
||||
/// it is; otherwise (at start) the dialog only appears when a newer version exists.
|
||||
pub fn check_for_updates(&mut self, manual: bool) {
|
||||
/// Help ▸ Check for updates: ask for the latest release and show the outcome.
|
||||
pub fn check_for_updates(&mut self) {
|
||||
let Some(source) = self.update_source.clone() else {
|
||||
if manual {
|
||||
self.open_url(RELEASES_PAGE);
|
||||
}
|
||||
self.open_url(RELEASES_PAGE);
|
||||
return;
|
||||
};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if matches!(self.updates.check, Check::Running { .. }) {
|
||||
self.updates.open |= manual;
|
||||
self.updates.open = true;
|
||||
if matches!(self.updates.check, Check::Running(_)) {
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
|
@ -93,36 +84,24 @@ impl PrintCraftApp {
|
|||
ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
self.updates.check = Check::Running { rx, manual };
|
||||
self.updates.open = manual;
|
||||
self.updates.check = Check::Running(rx);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = source;
|
||||
if manual {
|
||||
self.open_url(RELEASES_PAGE);
|
||||
}
|
||||
self.open_url(RELEASES_PAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the opt-in check at start (first frame), and pick up a finished check (each frame).
|
||||
/// Pick up a finished check (each frame).
|
||||
pub(crate) fn poll_updates(&mut self) {
|
||||
if !self.updates.started {
|
||||
self.updates.started = true;
|
||||
if self.check_updates_at_start {
|
||||
self.check_for_updates(false);
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Check::Running { rx, manual } = &self.updates.check {
|
||||
let manual = *manual;
|
||||
if let Check::Running(rx) = &self.updates.check {
|
||||
let result = match rx.try_recv() {
|
||||
Ok(r) => r,
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => return,
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => Err("the update check stopped unexpectedly".into()),
|
||||
};
|
||||
let newer = result.as_ref().is_ok_and(|r| is_newer(&r.version, env!("CARGO_PKG_VERSION")));
|
||||
self.updates.open |= manual || newer;
|
||||
self.updates.check = Check::Done(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +128,7 @@ pub(crate) fn dialog(app: &mut PrintCraftApp, ctx: &egui::Context) {
|
|||
ui.label("No check has run yet.");
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Check::Running { .. } => {
|
||||
Check::Running(_) => {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spinner();
|
||||
ui.label("Checking for a newer version…");
|
||||
|
|
@ -172,11 +151,6 @@ pub(crate) fn dialog(app: &mut PrintCraftApp, ctx: &egui::Context) {
|
|||
}
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.scope(|ui| {
|
||||
// The light theme's field colour is the dialog's own white: outline the box.
|
||||
ui.visuals_mut().widgets.inactive.bg_stroke = egui::Stroke::new(1.0, t.text_muted);
|
||||
ui.checkbox(&mut app.check_updates_at_start, "Check for updates when PrintCraft starts");
|
||||
});
|
||||
ui.label(
|
||||
egui::RichText::new("Asks GitHub for the latest release. Nothing is downloaded or installed automatically.").color(t.text_muted).small(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@ fn source(answer: Result<&str, &str>) -> UpdateSource {
|
|||
Arc::new(move || answer.clone().map(|v| Release { url: format!("https://github.com/storytold/printcraft/releases/tag/{v}"), version: v }))
|
||||
}
|
||||
|
||||
fn harness(answer: Result<&str, &str>, at_start: bool) -> Harness<'static, PrintCraftApp> {
|
||||
fn harness(answer: Result<&str, &str>) -> Harness<'static, PrintCraftApp> {
|
||||
Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(move |_cc| {
|
||||
let mut app = PrintCraftApp::new();
|
||||
app.update_source = Some(source(answer));
|
||||
app.check_updates_at_start = at_start;
|
||||
app
|
||||
})
|
||||
}
|
||||
|
|
@ -48,7 +47,7 @@ fn versions_compare_by_number() {
|
|||
|
||||
#[test]
|
||||
fn a_newer_release_is_offered_for_download() {
|
||||
let mut h = harness(Ok("v99.0.0"), false);
|
||||
let mut h = harness(Ok("v99.0.0"));
|
||||
h.state_mut().execute("help.check_updates");
|
||||
settle(&mut h);
|
||||
h.get_by_label_contains("PrintCraft 99.0.0 is available");
|
||||
|
|
@ -60,42 +59,39 @@ fn a_newer_release_is_offered_for_download() {
|
|||
|
||||
#[test]
|
||||
fn an_up_to_date_or_failed_check_says_so() {
|
||||
let mut h = harness(Ok(concat!("v", env!("CARGO_PKG_VERSION"))), false);
|
||||
let mut h = harness(Ok(concat!("v", env!("CARGO_PKG_VERSION"))));
|
||||
h.state_mut().execute("help.check_updates");
|
||||
settle(&mut h);
|
||||
h.get_by_label_contains("is up to date");
|
||||
assert!(h.query_by_label("Download").is_none());
|
||||
|
||||
let mut h = harness(Err("couldn't reach GitHub"), false);
|
||||
let mut h = harness(Err("couldn't reach GitHub"));
|
||||
h.state_mut().execute("help.check_updates");
|
||||
settle(&mut h);
|
||||
h.get_by_label_contains("Couldn't check for updates: couldn't reach GitHub");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_check_at_start_is_opt_in_and_quiet_unless_there_is_news() {
|
||||
// Off (the default): nothing is asked.
|
||||
let mut h = harness(Ok("v99.0.0"), false);
|
||||
fn nothing_is_asked_until_the_user_checks() {
|
||||
// No check at start (the owner's decision): the source is only called from the command.
|
||||
let calls = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
let counted = calls.clone();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_eframe(move |_cc| {
|
||||
let mut app = PrintCraftApp::new();
|
||||
// Settings from a build that had the startup option are ignored.
|
||||
app.restore(r#"{"check_updates_at_start": true}"#);
|
||||
let counted = counted.clone();
|
||||
app.update_source = Some(Arc::new(move || {
|
||||
counted.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
Ok(Release { version: "v99.0.0".into(), url: "https://github.com/storytold/printcraft/releases/tag/v99.0.0".into() })
|
||||
}));
|
||||
app
|
||||
});
|
||||
settle(&mut h);
|
||||
assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 0);
|
||||
assert!(h.query_by_label_contains("is available").is_none());
|
||||
// On, with a newer release: the dialog appears by itself.
|
||||
let mut h = harness(Ok("v99.0.0"), true);
|
||||
h.state_mut().execute("help.check_updates");
|
||||
settle(&mut h);
|
||||
assert_eq!(calls.load(std::sync::atomic::Ordering::SeqCst), 1);
|
||||
h.get_by_label_contains("PrintCraft 99.0.0 is available");
|
||||
// On, up to date or failing: silent.
|
||||
for answer in [Ok(concat!("v", env!("CARGO_PKG_VERSION"))), Err("offline")] {
|
||||
let mut h = harness(answer, true);
|
||||
settle(&mut h);
|
||||
for message in ["is up to date", "Couldn't check for updates", "is available"] {
|
||||
assert!(h.query_by_label_contains(message).is_none(), "{answer:?}: {message}");
|
||||
}
|
||||
}
|
||||
// The preference is remembered.
|
||||
let mut app = PrintCraftApp::new();
|
||||
app.check_updates_at_start = true;
|
||||
let mut restored = PrintCraftApp::new();
|
||||
restored.restore(&app.persist());
|
||||
assert!(restored.check_updates_at_start);
|
||||
restored.restore("{}");
|
||||
assert!(restored.check_updates_at_start, "missing keys change nothing");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1687,15 +1687,15 @@ notes = ""
|
|||
|
||||
[[feature]]
|
||||
id = "view.check-for-updates"
|
||||
title = "Help ▸ Check for updates (and an optional check at start)"
|
||||
title = "Help ▸ Check for updates"
|
||||
area = "B"
|
||||
tier = "P2"
|
||||
milestone = "M3"
|
||||
status = "partial"
|
||||
commands = ["help.check_updates"]
|
||||
tools = []
|
||||
evidence = ["crates/ui-egui/tests/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/updates.rs::the_check_at_start_is_opt_in_and_quiet_unless_there_is_news", "apps/printcraft/src/updates.rs::answers_are_read_and_only_our_release_pages_are_offered"]
|
||||
notes = "asks GitHub Releases and offers the release page; the check at start is opt-in. Missing: installing the update in place (needs signed release artifacts)"
|
||||
evidence = ["crates/ui-egui/tests/updates.rs::a_newer_release_is_offered_for_download", "crates/ui-egui/tests/updates.rs::nothing_is_asked_until_the_user_checks", "apps/printcraft/src/updates.rs::answers_are_read_and_only_our_release_pages_are_offered"]
|
||||
notes = "asks GitHub Releases on request and offers the release page. By the owner's decision there is no check at start and no in-place install (Acrobat has both)"
|
||||
|
||||
[[feature]]
|
||||
id = "view.single-key-accelerators"
|
||||
|
|
|
|||
Loading…
Reference in New Issue