diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index 3bec57114..416f1b879 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -348,6 +348,7 @@ async function handleCommand(e) {
const availableUpdate = ref(null)
const updateSkipped = ref(false)
+const enqueuedUpdate = ref(null)
const updateModal = useTemplateRef('updateModal')
async function checkUpdates() {
if (!(await areUpdatesEnabled())) {
@@ -399,12 +400,18 @@ async function checkUpdates() {
}
async function skipUpdate(version) {
+ enqueuedUpdate.value = null
+
updateSkipped.value = true
let settings = await getSettings()
settings.skipped_update = version
await setSettings(settings)
}
+async function updateEnqueuedForLater(version) {
+ enqueuedUpdate.value = version
+}
+
async function forceOpenUpdateModal() {
if (updateSkipped.value) {
updateSkipped.value = false
@@ -456,7 +463,11 @@ function handleAuxClick(e) {
-
+
@@ -510,11 +521,14 @@ function handleAuxClick(e) {
-
-
+
diff --git a/apps/app-frontend/src/components/ui/UpdateModal.vue b/apps/app-frontend/src/components/ui/UpdateModal.vue
index 806be7dbd..3a710637c 100644
--- a/apps/app-frontend/src/components/ui/UpdateModal.vue
+++ b/apps/app-frontend/src/components/ui/UpdateModal.vue
@@ -3,7 +3,7 @@
ref="modal"
:header="formatMessage(messages.header)"
:on-hide="onHide"
- :closable="!updateInProgress"
+ :closable="!updatingImmediately"
>
{{ formatMessage(messages.bodyVersion, { version: update!.version }) }}
@@ -17,19 +17,22 @@
-
-
+
{{ formatMessage(messages.later) }}
-
+
{{ formatMessage(messages.skip) }}
@@ -44,14 +47,16 @@ import { defineMessages, useVIntl } from '@vintl/vintl'
import { useTemplateRef, ref } from 'vue'
import { ButtonStyled } from '@modrinth/ui'
import { RefreshCwIcon, XIcon, RightArrowIcon } from '@modrinth/assets'
-import { getUpdateSize } from '@/helpers/utils'
+import { enqueueUpdateForInstallation, getUpdateSize, removeEnqueuedUpdate } from '@/helpers/utils'
import { formatBytes } from '@modrinth/utils'
import { handleError } from '@/store/notifications'
import ProgressBar from '@/components/ui/ProgressBar.vue'
-import { Update } from '@tauri-apps/plugin-updater'
+import { loading_listener } from '@/helpers/events'
+import { getCurrentWindow } from '@tauri-apps/api/window'
const emit = defineEmits<{
- (e: 'updateSkipped', version: string): void
+ (e: 'updateSkipped', version: string): Promise
+ (e: 'updateEnqueuedForLater', version: string | null): Promise
}>()
const { formatMessage } = useVIntl()
@@ -98,15 +103,26 @@ type UpdateData = {
const update = ref()
const updateSize = ref()
-const updateInProgress = ref(false)
+
+const updatingImmediately = ref(false)
+const downloadInProgress = ref(false)
const downloadProgress = ref(0)
+const enqueuedUpdate = ref(null)
+
const modal = useTemplateRef('modal')
const isOpen = ref(false)
async function show(newUpdate: UpdateData) {
+ const oldVersion = update.value?.version
+
update.value = newUpdate
updateSize.value = await getUpdateSize(newUpdate.rid).catch(handleError)
+
+ if (oldVersion !== update.value?.version) {
+ downloadProgress.value = 0
+ }
+
modal.value!.show()
isOpen.value = true
}
@@ -121,25 +137,62 @@ function hide() {
defineExpose({ show, hide, isOpen })
-function installUpdateNow() {
- updateInProgress.value = true
- let totalSize = 0
- let totalDownloaded = 0
- new Update(update.value!).downloadAndInstall((event) => {
- if (event.event === 'Started') {
- totalSize = event.data.contentLength!
- } else if (event.event === 'Progress') {
- totalDownloaded += event.data.chunkLength
- } else if (event.event === 'Finished') {
- totalDownloaded = totalSize
+loading_listener((event) => {
+ if (event.event.type === 'launcher_update') {
+ if (event.event.version === update.value!.version) {
+ downloadProgress.value = (event.fraction ?? 1.0) * 100
}
- downloadProgress.value = (totalDownloaded / totalSize) * 100
- })
+ }
+})
+
+function installUpdateNow() {
+ updatingImmediately.value = true
+
+ if (enqueuedUpdate.value !== update.value!.version) {
+ downloadUpdate()
+ } else if (!downloadInProgress.value) {
+ // Update already downloaded. Simply close the app
+ getCurrentWindow().close()
+ }
+}
+
+function updateAtNextExit() {
+ enqueuedUpdate.value = update.value!.version
+ emit('updateEnqueuedForLater', update.value!.version)
+
+ downloadUpdate()
+ hide()
+}
+
+async function downloadUpdate() {
+ const versionToDownload = update.value!.version
+
+ downloadInProgress.value = true
+ try {
+ await enqueueUpdateForInstallation(update.value!.rid)
+ } catch (e) {
+ downloadInProgress.value = false
+
+ handleError(e)
+
+ enqueuedUpdate.value = null
+ updatingImmediately.value = false
+ await emit('updateEnqueuedForLater', null)
+ return
+ }
+ downloadInProgress.value = false
+
+ if (updatingImmediately.value && update.value!.version === versionToDownload) {
+ await getCurrentWindow().close()
+ }
}
function skipUpdate() {
- hide()
+ enqueuedUpdate.value = null
emit('updateSkipped', update.value!.version)
+
+ removeEnqueuedUpdate()
+ hide()
}
diff --git a/apps/app-frontend/src/helpers/utils.js b/apps/app-frontend/src/helpers/utils.js
index 1cda120de..4e46e792f 100644
--- a/apps/app-frontend/src/helpers/utils.js
+++ b/apps/app-frontend/src/helpers/utils.js
@@ -13,6 +13,14 @@ export async function getUpdateSize(updateRid) {
return await invoke('get_update_size', { rid: updateRid })
}
+export async function enqueueUpdateForInstallation(updateRid) {
+ return await invoke('enqueue_update_for_installation', { rid: updateRid })
+}
+
+export async function removeEnqueuedUpdate() {
+ return await invoke('remove_enqueued_update')
+}
+
// One of 'Windows', 'Linux', 'MacOS'
export async function getOS() {
return await invoke('plugin:utils|get_os')
diff --git a/apps/app/src/api/mod.rs b/apps/app/src/api/mod.rs
index 294e784f6..55acfe843 100644
--- a/apps/app/src/api/mod.rs
+++ b/apps/app/src/api/mod.rs
@@ -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 for ErrorTypeA
@@ -104,5 +108,6 @@ impl_serialize! {
impl_serialize! {
IO,
Tauri,
- TauriUpdater,
+ Updater,
+ Http,
}
diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs
index da83ced08..5d901fe9d 100644
--- a/apps/app/src/main.rs
+++ b/apps/app/src/main.rs
@@ -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::().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")
}
}
diff --git a/apps/app/src/update_size_checker.rs b/apps/app/src/update_size_checker.rs
deleted file mode 100644
index b62ebec8c..000000000
--- a/apps/app/src/update_size_checker.rs
+++ /dev/null
@@ -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(webview: Webview, rid: ResourceId) -> Result