mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-27 11:37:02 -06:00
refactor(updates): drive the flow with craft-update's Updater
Startup check is opt-in for the desktop app, the Windows update helper is dispatched before argument parsing, and a successful update closes through the app's own close path.
This commit is contained in:
Generated
+11
-10
@@ -1033,7 +1033,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-update"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=fcd3441993ee238144ee56538ad81b4a333f1ed9#fcd3441993ee238144ee56538ad81b4a333f1ed9"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=13ceaf2f31620531f39a54f6b994d581a6082a93#13ceaf2f31620531f39a54f6b994d581a6082a93"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dunce",
|
||||
@@ -1255,6 +1255,7 @@ name = "effectcraft"
|
||||
version = "0.17.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"craft-update",
|
||||
"effectcraft-engine",
|
||||
"effectcraft-host",
|
||||
"effectcraft-ui-egui",
|
||||
@@ -2048,7 +2049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3422,7 +3423,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"png",
|
||||
"thiserror 2.0.21",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3547,7 +3548,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4714,7 +4715,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4771,7 +4772,7 @@ dependencies = [
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5130,7 +5131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5446,7 +5447,7 @@ dependencies = [
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.5",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5724,7 +5725,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6516,7 +6517,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ repository = "https://github.com/storytold/effectcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "9f75d3dc19b4b514c4f3d4847ed1ec1b0e86b4ef", version = "=0.1.0", default-features = false, features = ["read"] }
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "fcd3441993ee238144ee56538ad81b4a333f1ed9", features = ["egui"] }
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "13ceaf2f31620531f39a54f6b994d581a6082a93", features = ["egui"] }
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" }
|
||||
# internal
|
||||
effectcraft-time = { path = "crates/time" }
|
||||
|
||||
@@ -48,3 +48,6 @@ objc2-app-kit = { version = "0.3", default-features = false, features = ["std",
|
||||
# PhotoCraft and PdfCraft) registers the handler (Objective-C interop needs unsafe, which our
|
||||
# workspace forbids) behind a safe API. Pinned exactly: review the diff before bumping.
|
||||
fmv-macos-events = "=0.1.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
craft-update = { workspace = true }
|
||||
|
||||
@@ -139,6 +139,11 @@ fn install_file_dialogs(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
// Windows: the update helper is a copy of this program started with `--finish-update`.
|
||||
#[cfg(windows)]
|
||||
if let Some(result) = craft_update::self_update::finish(&craft_update::suite::EFFECTCRAFT, &std::env::args_os().skip(1).collect::<Vec<_>>()) {
|
||||
std::process::exit(i32::from(result.is_err()));
|
||||
}
|
||||
// First, so the panic hook and every start-up warning are recorded. Records go to stderr
|
||||
// and the session log; Help ▸ Enable Logging writes through this logger too, and warnings
|
||||
// feed the compatibility report.
|
||||
@@ -264,6 +269,7 @@ fn main() -> eframe::Result {
|
||||
// reported in a toast and forgotten).
|
||||
let _ = session.execute("window.restoreScriptPanels", json!({}));
|
||||
let mut app = EffectcraftApp::new(session);
|
||||
app.set_startup_update_check(true);
|
||||
app.set_gpu_failure_bridge(gpu_failures);
|
||||
app.ui.start_screen = show_home;
|
||||
if let Some(n) = notice {
|
||||
|
||||
@@ -2,47 +2,48 @@
|
||||
|
||||
use crate::EffectcraftApp;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use craft_update::updater::{Action, Status, Updater};
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
startup_checked: bool,
|
||||
manual_checked: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
updater: Updater,
|
||||
/// The desktop app turns the check at start on; tests and the web build leave it off.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
startup_check: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
check_requested: bool,
|
||||
pub(crate) manual_window_open: bool,
|
||||
pub(crate) status_message: Option<String>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) prompt: Option<craft_update::StartupPrompt>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
rx: Option<std::sync::mpsc::Receiver<Result<Option<craft_update::Release>, String>>>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
checking: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
update_rx: Option<std::sync::mpsc::Receiver<Result<(), String>>>,
|
||||
}
|
||||
|
||||
impl Default for Runtime {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
startup_checked: false,
|
||||
manual_checked: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
updater: Updater::new(&craft_update::suite::EFFECTCRAFT, env!("CARGO_PKG_VERSION")),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
startup_check: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
check_requested: false,
|
||||
manual_window_open: false,
|
||||
status_message: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
prompt: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
rx: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
checking: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
update_rx: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EffectcraftApp {
|
||||
/// Whether the app asks for a newer release when it starts (off until the desktop app turns it on).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_startup_update_check(&mut self, on: bool) {
|
||||
self.updates_rt.startup_check = on;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check(app: &mut EffectcraftApp) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
app.updates_rt.manual_checked = true;
|
||||
app.updates_rt.manual_window_open = false;
|
||||
app.updates_rt.status_message = None;
|
||||
start_check(app);
|
||||
app.updates_rt.check_requested = true;
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
@@ -51,157 +52,37 @@ pub(crate) fn check(app: &mut EffectcraftApp) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn start_check(app: &mut EffectcraftApp) {
|
||||
if app.updates_rt.checking {
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.updates_rt.rx = Some(rx);
|
||||
app.updates_rt.checking = true;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let http = match craft_update::Http::new("EffectCraft/Update") {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
let _ = tx.send(Err(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let res = craft_update::check_for_update(&craft_update::suite::EFFECTCRAFT, &http, env!("CARGO_PKG_VERSION"));
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn tick(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if !app.updates_rt.startup_checked {
|
||||
app.updates_rt.startup_checked = true;
|
||||
start_check(app);
|
||||
}
|
||||
|
||||
if app.updates_rt.checking {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(100));
|
||||
}
|
||||
|
||||
let mut completed = None;
|
||||
if let Some(rx) = &app.updates_rt.rx {
|
||||
if let Ok(res) = rx.try_recv() {
|
||||
completed = Some(res);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(res) = completed {
|
||||
app.updates_rt.rx = None;
|
||||
app.updates_rt.checking = false;
|
||||
match res {
|
||||
Ok(Some(release)) => {
|
||||
let mut prompt = craft_update::StartupPrompt::new(&craft_update::suite::EFFECTCRAFT, release, env!("CARGO_PKG_VERSION"));
|
||||
prompt.open = true;
|
||||
app.updates_rt.prompt = Some(prompt);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
Ok(None) => {
|
||||
if app.updates_rt.manual_checked {
|
||||
app.updates_rt.manual_checked = false;
|
||||
app.updates_rt.status_message = Some(format!("EffectCraft {} is currently the newest version available.", env!("CARGO_PKG_VERSION")));
|
||||
app.updates_rt.manual_window_open = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if app.updates_rt.manual_checked {
|
||||
app.updates_rt.manual_checked = false;
|
||||
app.updates_rt.status_message = Some(format!("Could not check for updates:\n{e}"));
|
||||
app.updates_rt.manual_window_open = true;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
let rt = &mut app.updates_rt;
|
||||
if std::mem::take(&mut rt.check_requested) {
|
||||
rt.updater.check_now(ctx);
|
||||
} else if rt.startup_check {
|
||||
rt.updater.check_at_start(ctx);
|
||||
}
|
||||
rt.updater.tick(ctx);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let _ = (app, ctx);
|
||||
}
|
||||
|
||||
pub(crate) fn show(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut download_url = None;
|
||||
if let Some(prompt) = &mut app.updates_rt.prompt {
|
||||
if let Some(result) = app.updates_rt.update_rx.as_ref().and_then(|rx| rx.try_recv().ok()) {
|
||||
app.updates_rt.update_rx = None;
|
||||
match result {
|
||||
// The app's close path asks about unsaved work; the restart helper relaunches once the process exits.
|
||||
Ok(()) => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
||||
Err(e) => {
|
||||
prompt.status_message = Some(e);
|
||||
prompt.is_installing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
match prompt.show(ctx) {
|
||||
craft_update::StartupPromptResponse::Pending => {}
|
||||
craft_update::StartupPromptResponse::UpdateAndRestart => {
|
||||
prompt.is_installing = true;
|
||||
if let Some(install) = craft_update::self_update::current(&craft_update::suite::EFFECTCRAFT) {
|
||||
let mut release = prompt.release.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.updates_rt.update_rx = Some(rx);
|
||||
let ctx = ctx.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut run = || -> Result<(), String> {
|
||||
let http = craft_update::Http::new("EffectCraft/Update").map_err(|e| format!("could not initialize HTTP: {e}"))?;
|
||||
if release.assets.is_empty()
|
||||
&& let Some(full) = craft_update::check_for_update(&craft_update::suite::EFFECTCRAFT, &http, env!("CARGO_PKG_VERSION"))
|
||||
.map_err(|e| format!("could not fetch release assets: {e}"))?
|
||||
{
|
||||
release = full;
|
||||
}
|
||||
let paths =
|
||||
craft_update::Paths::for_app(&craft_update::suite::EFFECTCRAFT).map_err(|e| format!("could not find paths: {e}"))?;
|
||||
let platform = craft_update::Platform::current().ok_or("unsupported platform for self update")?;
|
||||
craft_update::self_update::update(
|
||||
&craft_update::suite::EFFECTCRAFT,
|
||||
&install,
|
||||
&release,
|
||||
platform,
|
||||
&http,
|
||||
&paths,
|
||||
&craft_update::Stop::default(),
|
||||
&mut |_| {},
|
||||
&|| Ok(()),
|
||||
Some(env!("CARGO_PKG_VERSION")),
|
||||
)
|
||||
};
|
||||
let result = run();
|
||||
if let Err(e) = &result {
|
||||
log::warn!("self-update failed: {e}");
|
||||
}
|
||||
let _ = tx.send(result);
|
||||
ctx.request_repaint();
|
||||
});
|
||||
} else {
|
||||
prompt.status_message = Some("Cannot auto-update this installation type. Please download manually.".to_string());
|
||||
prompt.is_installing = false;
|
||||
}
|
||||
}
|
||||
craft_update::StartupPromptResponse::Download => {
|
||||
download_url = Some(prompt.release.html_url.clone());
|
||||
prompt.open = false;
|
||||
}
|
||||
craft_update::StartupPromptResponse::Later => {
|
||||
prompt.open = false;
|
||||
}
|
||||
}
|
||||
if !prompt.open {
|
||||
app.updates_rt.prompt = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(url) = download_url {
|
||||
app.updates_rt.prompt = None;
|
||||
ctx.open_url(egui::OpenUrl::new_tab(url));
|
||||
match app.updates_rt.updater.show(ctx) {
|
||||
Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)),
|
||||
Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
|
||||
Action::None => {}
|
||||
}
|
||||
// The window answers Help ▸ Check for Updates, so it follows the updater's status.
|
||||
let rt = &mut app.updates_rt;
|
||||
(rt.manual_window_open, rt.status_message) = match rt.updater.status() {
|
||||
Status::Idle => (false, None),
|
||||
Status::Checking => (true, None),
|
||||
Status::UpToDate => (true, Some(format!("EffectCraft {} is currently the newest version available.", env!("CARGO_PKG_VERSION")))),
|
||||
Status::Failed(e) => (true, Some(format!("Could not check for updates:\n{e}"))),
|
||||
};
|
||||
}
|
||||
|
||||
if app.updates_rt.manual_window_open {
|
||||
@@ -230,4 +111,10 @@ pub(crate) fn show(app: &mut EffectcraftApp, ctx: &egui::Context) {
|
||||
app.updates_rt.status_message = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Closing the window dismisses the outcome it showed.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if !app.updates_rt.manual_window_open {
|
||||
app.updates_rt.updater.dismiss_status();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user