mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-02 06:57:04 -06:00
* feat: startup update prompt modal and direct self-updating Ref: storytold/craft-libs#319 * build: pin craft-update to craft-libs main (b9ba47f) and refresh the lock * refactor(updates): drive the flow with craft-update's Updater Results are read independent of the prompt, a dropped worker is an error, failures return to the prompt, and no update starts over unsaved work. A manual check during the startup check is honored.
79 lines
2.8 KiB
Rust
79 lines
2.8 KiB
Rust
//! One background check at start-up (and on demand from Help) for a newer release, and the modal
|
|
//! prompt that offers to update and restart. The flow itself is `craft_update::updater::Updater`.
|
|
|
|
use craft_update::updater::{Action, Status, Updater};
|
|
|
|
use crate::SlideApp;
|
|
|
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
|
|
pub(crate) struct Runtime {
|
|
updater: Updater,
|
|
/// The desktop host turns the check at start on; tests and the web build leave it off.
|
|
enabled: bool,
|
|
check_requested: bool,
|
|
}
|
|
|
|
impl Default for Runtime {
|
|
fn default() -> Self {
|
|
Self { updater: Updater::new(&craft_update::suite::DECKCRAFT, VERSION), enabled: false, check_requested: false }
|
|
}
|
|
}
|
|
|
|
pub(crate) fn enable(app: &mut SlideApp) {
|
|
app.updates_rt.enabled = true;
|
|
}
|
|
|
|
/// Help > Check for Updates…: runs a check and reports the outcome either way.
|
|
pub(crate) fn request_check(app: &mut SlideApp) {
|
|
if !matches!(app.updates_rt.updater.status(), Status::Checking) {
|
|
app.updates_rt.check_requested = true;
|
|
app.notify("Checking for updates…");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn tick(app: &mut SlideApp, ctx: &egui::Context) {
|
|
let rt = &mut app.updates_rt;
|
|
if std::mem::take(&mut rt.check_requested) {
|
|
rt.updater.check_now(ctx);
|
|
} else if rt.enabled {
|
|
rt.updater.check_at_start(ctx);
|
|
}
|
|
rt.updater.tick(ctx);
|
|
let outcome = match rt.updater.status() {
|
|
Status::UpToDate => Some(format!("DeckCraft {VERSION} is up to date.")),
|
|
Status::Failed(e) => Some(format!("Could not check for updates: {e}")),
|
|
Status::Idle | Status::Checking => None,
|
|
};
|
|
if let Some(message) = outcome {
|
|
rt.updater.dismiss_status();
|
|
app.notify(message);
|
|
}
|
|
}
|
|
|
|
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.
|
|
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.
|
|
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
|
Action::None => {}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn an_offered_release_opens_the_prompt() {
|
|
let mut app = SlideApp::new(deckcraft_engine::Session::new(), crate::Services::default());
|
|
let ctx = egui::Context::default();
|
|
assert!(!app.updates_rt.updater.prompt_open());
|
|
app.updates_rt.updater.offer(craft_update::Release { tag: "v99.0.0".into(), ..Default::default() });
|
|
ctx.run_ui(egui::RawInput::default(), |ui| show(&mut app, ui.ctx())).textures_delta.clear();
|
|
assert!(app.updates_rt.updater.prompt_open());
|
|
}
|
|
}
|