diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 8fe93c3bc..1939fa878 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -109,6 +109,7 @@ import { getUpdateSize, isDev, isNetworkMetered, + setRestartAfterPendingUpdate, } from '@/helpers/utils.js' import i18n from '@/i18n.config' import { createContentInstall, provideContentInstall } from '@/providers/content-install' @@ -1025,6 +1026,13 @@ async function downloadUpdate(versionToDownload) { async function installUpdate() { restarting.value = true + try { + await setRestartAfterPendingUpdate(true) + } catch (e) { + restarting.value = false + handleError(e) + return + } setTimeout(async () => { await handleClose() }, 250) diff --git a/apps/app-frontend/src/helpers/utils.js b/apps/app-frontend/src/helpers/utils.js index c9873b3e0..d0dd6797c 100644 --- a/apps/app-frontend/src/helpers/utils.js +++ b/apps/app-frontend/src/helpers/utils.js @@ -22,6 +22,10 @@ export async function removeEnqueuedUpdate() { return await invoke('remove_enqueued_update') } +export async function setRestartAfterPendingUpdate(should_restart) { + return await invoke('set_restart_after_pending_update', { shouldRestart: should_restart }) +} + // One of 'Windows', 'Linux', 'MacOS' export async function getOS() { return await invoke('plugin:utils|get_os') diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index a78c26010..c77a1ac73 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -6,6 +6,7 @@ use native_dialog::{DialogBuilder, MessageLevel}; use std::env; +use std::sync::atomic::Ordering; use tauri::{Listener, Manager}; use tauri_plugin_fs::FsExt; use theseus::prelude::*; @@ -96,6 +97,17 @@ fn restart_app(app: tauri::AppHandle) { app.restart(); } +#[tauri::command] +async fn set_restart_after_pending_update( + should_restart: bool, +) -> api::Result<()> { + let state = State::get().await?; + state + .restart_after_pending_update + .store(should_restart, Ordering::Relaxed); + Ok(()) +} + // if Tauri app is called with arguments, then those arguments will be treated as commands // ie: deep links or filepaths for .mrpacks fn main() { @@ -245,6 +257,7 @@ fn main() { get_update_size, enqueue_update_for_installation, remove_enqueued_update, + set_restart_after_pending_update, toggle_decorations, show_window, restart_app, @@ -262,7 +275,13 @@ fn main() { #[cfg(feature = "updater")] if matches!(event, tauri::RunEvent::Exit) { let update_data = app.state::().inner(); - if let Some((update, data)) = &*update_data.0.lock().unwrap() { + let should_restart = State::get_if_initialized() + .map(|s| { + s.restart_after_pending_update.load(Ordering::Relaxed) + }) + .unwrap_or(false); + if let Some((update, data)) = &*update_data.0.lock().unwrap() + { fn set_changelog_toast(version: Option) { let toast_result: theseus::Result<()> = tauri::async_runtime::block_on(async move { let mut settings = settings::get().await?; @@ -271,27 +290,46 @@ fn main() { Ok(()) }); if let Err(e) = toast_result { - tracing::warn!("Failed to set pending_update_toast: {e}") + tracing::warn!( + "Failed to set pending_update_toast: {e}" + ) } } set_changelog_toast(Some(update.version.clone())); - if let Err(e) = update.install(data) { - tracing::error!("Error while updating: {e}"); - set_changelog_toast(None); + match update.install(data) { + Ok(()) => { + if should_restart { + tracing::info!( + "Pending update installed successfully (version {}); restarting because user requested reload", + update.version + ); + app.restart(); + } else { + tracing::info!( + "Pending update installed successfully (version {}); exiting without relaunch (user did not request reload)", + update.version + ); + } + } + Err(e) => { + tracing::error!( + "Pending update install failed (version {}): {e}", + update.version + ); + set_changelog_toast(None); - DialogBuilder::message() - .set_level(MessageLevel::Error) - .set_title("Update error") - .set_text(format!("Failed to install update due to an error:\n{e}")) - .alert() - .show() - .unwrap(); + DialogBuilder::message() + .set_level(MessageLevel::Error) + .set_title("Update error") + .set_text(format!("Failed to install update due to an error:\n{e}")) + .alert() + .show() + .unwrap(); + } } - app.restart(); } } - #[cfg(target_os = "macos")] if let tauri::RunEvent::Opened { urls } = event { tracing::info!("Handling webview open {urls:?}"); diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index 6249d8746..2b8ddf73a 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -1,6 +1,7 @@ //! Theseus state management system use crate::util::fetch::{FetchSemaphore, IoSemaphore}; use std::sync::Arc; +use std::sync::atomic::AtomicBool; use tokio::sync::{OnceCell, Semaphore}; use crate::state::fs_watcher::FileWatcher; @@ -83,6 +84,8 @@ pub struct State { /// Friends socket pub friends_socket: FriendsSocket, + pub restart_after_pending_update: AtomicBool, + pub(crate) pool: SqlitePool, pub(crate) file_watcher: FileWatcher, @@ -146,6 +149,10 @@ impl State { LAUNCHER_STATE.initialized() } + pub fn get_if_initialized() -> Option> { + LAUNCHER_STATE.get().map(Arc::clone) + } + #[tracing::instrument] async fn initialize_state( app_identifier: String, @@ -194,6 +201,7 @@ impl State { discord_rpc, process_manager, friends_socket, + restart_after_pending_update: AtomicBool::new(false), pool, file_watcher, // app_identifier,