Implement updating at next exit

This commit is contained in:
Josiah Glosson committed 2025-07-09 20:41:11 -05:00
1 parent 7b73aa2908
commit 9b103e063a
9 files changed
+287 -90

No files matched your search

+8 -3
View File
@@ -45,8 +45,12 @@ pub enum TheseusSerializableError {
Tauri(#[from] tauri::Error),
#[cfg(feature = "updater")]
#[error("Tauri updater error: {0}")]
TauriUpdater(#[from] tauri_plugin_updater::Error),
#[error("Updater error: {0}")]
Updater(#[from] tauri_plugin_updater::Error),
#[cfg(feature = "updater")]
#[error("HTTP error: {0}")]
Http(#[from] tauri_plugin_http::reqwest::Error),
}
// Generic implementation of From<T> for ErrorTypeA
@@ -104,5 +108,6 @@ impl_serialize! {
impl_serialize! {
IO,
Tauri,
TauriUpdater,
Updater,
Http,
}
+30 -8
View File
@@ -15,7 +15,9 @@ mod error;
mod macos;
#[cfg(feature = "updater")]
mod update_size_checker;
mod updater_impl;
#[cfg(not(feature = "updater"))]
mod updater_impl_noop;
// Should be called in launcher initialization
#[tracing::instrument(skip_all)]
@@ -68,13 +70,10 @@ fn are_updates_enabled() -> bool {
}
#[cfg(feature = "updater")]
pub use update_size_checker::get_update_size;
pub use updater_impl::*;
#[cfg(not(feature = "updater"))]
#[tauri::command]
fn get_update_size() -> theseus::Result<()> {
Err(theseus::ErrorKind::OtherError("Updates are disabled in this build.".to_string()).into())
}
pub use updater_impl_noop::*;
// Toggles decorations
#[tauri::command]
@@ -212,11 +211,14 @@ fn main() {
.plugin(api::ads::init())
.plugin(api::friends::init())
.plugin(api::worlds::init())
.manage(PendingUpdateData::default())
.invoke_handler(tauri::generate_handler![
initialize_state,
is_dev,
are_updates_enabled,
get_update_size,
enqueue_update_for_installation,
remove_enqueued_update,
toggle_decorations,
show_window,
restart_app,
@@ -228,8 +230,9 @@ fn main() {
match app {
Ok(app) => {
app.run(|app, event| {
#[cfg(not(target_os = "macos"))]
#[cfg(not(any(target_os = "macos", feature = "updater")))]
drop((app, event));
#[cfg(target_os = "macos")]
if let tauri::RunEvent::Opened { urls } = event {
tracing::info!("Handling webview open {urls:?}");
@@ -254,9 +257,29 @@ fn main() {
});
}
}
#[cfg(feature = "updater")]
if matches!(event, tauri::RunEvent::Exit) {
let update_data = app.state::<PendingUpdateData>().inner();
if let Some((update, data)) = &*update_data.0.lock().unwrap() {
if let Err(e) = update.install(data) {
tracing::error!("Error while updating: {e}");
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();
}
}
}
});
}
Err(e) => {
tracing::error!("Error while running tauri application: {:?}", e);
#[cfg(target_os = "windows")]
{
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
@@ -285,7 +308,6 @@ fn main() {
.show()
.unwrap();
tracing::error!("Error while running tauri application: {:?}", e);
panic!("{1}: {:?}", e, "error while running tauri application")
}
}
-52
View File
@@ -1,52 +0,0 @@
use tauri::{Manager, ResourceId, Runtime, Webview};
use tauri::http::header::ACCEPT;
use tauri::http::HeaderValue;
use tauri_plugin_http::reqwest;
use tauri_plugin_http::reqwest::ClientBuilder;
use tauri_plugin_updater::Error;
use tauri_plugin_updater::Result;
const UPDATER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
// Reimplementation of Update::download mostly, minus the actual download part
#[tauri::command]
pub async fn get_update_size<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Result<Option<u64>> {
use tauri_plugin_updater::Update;
let update = webview.resources_table().get::<Update>(rid)?;
let mut headers = update.headers.clone();
if !headers.contains_key(ACCEPT) {
headers.insert(ACCEPT, HeaderValue::from_static("application/octet-stream"));
}
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if let Some(timeout) = update.timeout {
request = request.timeout(timeout);
}
if let Some(ref proxy) = update.proxy {
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
let response = request
.build()?
.get(update.download_url.clone())
.headers(headers)
.send()
.await?;
if !response.status().is_success() {
return Err(Error::Network(format!(
"Download request failed with status: {}",
response.status()
)).into());
}
let content_length = response
.headers()
.get("Content-Length")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse().ok());
Ok(content_length)
}
+122
View File
@@ -0,0 +1,122 @@
use crate::api::Result;
use std::sync::{Arc, Mutex};
use tauri::http::HeaderValue;
use tauri::http::header::ACCEPT;
use tauri::{Manager, ResourceId, Runtime, Webview};
use tauri_plugin_http::reqwest;
use tauri_plugin_http::reqwest::ClientBuilder;
use tauri_plugin_updater::Error;
use tauri_plugin_updater::Update;
use theseus::{LoadingBarType, emit_loading, init_loading};
use tokio::time::Instant;
const UPDATER_USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
#[derive(Default)]
pub struct PendingUpdateData(pub Mutex<Option<(Arc<Update>, Vec<u8>)>>);
// Reimplementation of Update::download mostly, minus the actual download part
#[tauri::command]
pub async fn get_update_size<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
) -> Result<Option<u64>> {
let update = webview.resources_table().get::<Update>(rid)?;
let mut headers = update.headers.clone();
if !headers.contains_key(ACCEPT) {
headers.insert(
ACCEPT,
HeaderValue::from_static("application/octet-stream"),
);
}
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if let Some(timeout) = update.timeout {
request = request.timeout(timeout);
}
if let Some(ref proxy) = update.proxy {
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
let response = request
.build()?
.get(update.download_url.clone())
.headers(headers)
.send()
.await?;
if !response.status().is_success() {
return Err(Error::Network(format!(
"Download request failed with status: {}",
response.status()
))
.into());
}
let content_length = response
.headers()
.get("Content-Length")
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse().ok());
Ok(content_length)
}
#[tauri::command]
pub async fn enqueue_update_for_installation<R: Runtime>(
webview: Webview<R>,
rid: ResourceId,
) -> Result<()> {
let pending_data = webview.state::<PendingUpdateData>().inner();
let update = webview.resources_table().get::<Update>(rid)?;
let progress = init_loading(
LoadingBarType::LauncherUpdate {
version: update.version.clone(),
current_version: update.current_version.clone(),
},
1.0,
"Downloading update...",
)
.await?;
let download_start = Instant::now();
let update_data = update
.download(
|chunk_size, total_size| {
let Some(total_size) = total_size else {
return;
};
if let Err(e) = emit_loading(
&progress,
chunk_size as f64 / total_size as f64,
None,
) {
tracing::error!(
"Failed to update download progress bar: {e}"
);
}
},
|| {},
)
.await?;
let download_duration = download_start.elapsed();
tracing::info!("Downloaded update in {download_duration:?}");
pending_data
.0
.lock()
.unwrap()
.replace((update, update_data));
Ok(())
}
#[tauri::command]
pub fn remove_enqueued_update<R: Runtime>(webview: Webview<R>) {
let pending_data = webview.state::<PendingUpdateData>().inner();
pending_data.0.lock().unwrap().take();
}
+26
View File
@@ -0,0 +1,26 @@
use crate::api::Result;
use theseus::ErrorKind;
#[derive(Default)]
pub struct PendingUpdateData;
#[tauri::command]
pub fn get_update_size() -> Result<()> {
updates_are_disabled()
}
#[tauri::command]
pub fn enqueue_update_for_installation() -> Result<()> {
updates_are_disabled()
}
fn updates_are_disabled() -> Result<()> {
let error: theseus::Error = ErrorKind::OtherError(
"Updates are disabled in this build.".to_string(),
)
.into();
Err(error.into())
}
#[tauri::command]
pub fn remove_enqueued_update() {}