Implement updating at next exit
This commit is contained in:
parent
7b73aa2908
commit
9b103e063a
@ -348,6 +348,7 @@ async function handleCommand(e) {
|
|||||||
|
|
||||||
const availableUpdate = ref(null)
|
const availableUpdate = ref(null)
|
||||||
const updateSkipped = ref(false)
|
const updateSkipped = ref(false)
|
||||||
|
const enqueuedUpdate = ref(null)
|
||||||
const updateModal = useTemplateRef('updateModal')
|
const updateModal = useTemplateRef('updateModal')
|
||||||
async function checkUpdates() {
|
async function checkUpdates() {
|
||||||
if (!(await areUpdatesEnabled())) {
|
if (!(await areUpdatesEnabled())) {
|
||||||
@ -399,12 +400,18 @@ async function checkUpdates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function skipUpdate(version) {
|
async function skipUpdate(version) {
|
||||||
|
enqueuedUpdate.value = null
|
||||||
|
|
||||||
updateSkipped.value = true
|
updateSkipped.value = true
|
||||||
let settings = await getSettings()
|
let settings = await getSettings()
|
||||||
settings.skipped_update = version
|
settings.skipped_update = version
|
||||||
await setSettings(settings)
|
await setSettings(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateEnqueuedForLater(version) {
|
||||||
|
enqueuedUpdate.value = version
|
||||||
|
}
|
||||||
|
|
||||||
async function forceOpenUpdateModal() {
|
async function forceOpenUpdateModal() {
|
||||||
if (updateSkipped.value) {
|
if (updateSkipped.value) {
|
||||||
updateSkipped.value = false
|
updateSkipped.value = false
|
||||||
@ -456,7 +463,11 @@ function handleAuxClick(e) {
|
|||||||
<div id="teleports"></div>
|
<div id="teleports"></div>
|
||||||
<div v-if="stateInitialized" class="app-grid-layout experimental-styles-within relative">
|
<div v-if="stateInitialized" class="app-grid-layout experimental-styles-within relative">
|
||||||
<Suspense @resolve="checkUpdates">
|
<Suspense @resolve="checkUpdates">
|
||||||
<UpdateModal ref="updateModal" @update-skipped="skipUpdate" />
|
<UpdateModal
|
||||||
|
ref="updateModal"
|
||||||
|
@update-skipped="skipUpdate"
|
||||||
|
@update-enqueued-for-later="updateEnqueuedForLater"
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<AppSettingsModal ref="settingsModal" />
|
<AppSettingsModal ref="settingsModal" />
|
||||||
@ -510,11 +521,14 @@ function handleAuxClick(e) {
|
|||||||
<div class="flex flex-grow"></div>
|
<div class="flex flex-grow"></div>
|
||||||
<NavButton
|
<NavButton
|
||||||
v-if="!!availableUpdate"
|
v-if="!!availableUpdate"
|
||||||
v-tooltip.right="'Update available'"
|
v-tooltip.right="
|
||||||
|
enqueuedUpdate === availableUpdate?.version
|
||||||
|
? 'Update installation queued for next restart'
|
||||||
|
: 'Update available'
|
||||||
|
"
|
||||||
:to="forceOpenUpdateModal"
|
:to="forceOpenUpdateModal"
|
||||||
>
|
>
|
||||||
<!-- TODO: Also gray if updating on next restart -->
|
<DownloadIcon v-if="updateSkipped || enqueuedUpdate === availableUpdate?.version" />
|
||||||
<DownloadIcon v-if="updateSkipped" />
|
|
||||||
<DownloadIcon v-else class="text-brand-green" />
|
<DownloadIcon v-else class="text-brand-green" />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
ref="modal"
|
ref="modal"
|
||||||
:header="formatMessage(messages.header)"
|
:header="formatMessage(messages.header)"
|
||||||
:on-hide="onHide"
|
:on-hide="onHide"
|
||||||
:closable="!updateInProgress"
|
:closable="!updatingImmediately"
|
||||||
>
|
>
|
||||||
<div>{{ formatMessage(messages.bodyVersion, { version: update!.version }) }}</div>
|
<div>{{ formatMessage(messages.bodyVersion, { version: update!.version }) }}</div>
|
||||||
<div v-if="updateSize">
|
<div v-if="updateSize">
|
||||||
@ -17,19 +17,22 @@
|
|||||||
<ProgressBar class="mt-4" :progress="downloadProgress" />
|
<ProgressBar class="mt-4" :progress="downloadProgress" />
|
||||||
<div class="mt-4 flex flex-wrap gap-2">
|
<div class="mt-4 flex flex-wrap gap-2">
|
||||||
<ButtonStyled color="green">
|
<ButtonStyled color="green">
|
||||||
<button :disabled="updateInProgress" @click="installUpdateNow">
|
<button :disabled="updatingImmediately" @click="installUpdateNow">
|
||||||
<RefreshCwIcon />
|
<RefreshCwIcon />
|
||||||
{{ formatMessage(messages.restartNow) }}
|
{{ formatMessage(messages.restartNow) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled>
|
<ButtonStyled>
|
||||||
<button :disabled="updateInProgress">
|
<button
|
||||||
|
:disabled="updatingImmediately || downloadInProgress || update!.version == enqueuedUpdate"
|
||||||
|
@click="updateAtNextExit"
|
||||||
|
>
|
||||||
<RightArrowIcon />
|
<RightArrowIcon />
|
||||||
{{ formatMessage(messages.later) }}
|
{{ formatMessage(messages.later) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
<ButtonStyled color="red">
|
<ButtonStyled color="red">
|
||||||
<button :disabled="updateInProgress" @click="skipUpdate">
|
<button :disabled="updatingImmediately || downloadInProgress" @click="skipUpdate">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
{{ formatMessage(messages.skip) }}
|
{{ formatMessage(messages.skip) }}
|
||||||
</button>
|
</button>
|
||||||
@ -44,14 +47,16 @@ import { defineMessages, useVIntl } from '@vintl/vintl'
|
|||||||
import { useTemplateRef, ref } from 'vue'
|
import { useTemplateRef, ref } from 'vue'
|
||||||
import { ButtonStyled } from '@modrinth/ui'
|
import { ButtonStyled } from '@modrinth/ui'
|
||||||
import { RefreshCwIcon, XIcon, RightArrowIcon } from '@modrinth/assets'
|
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 { formatBytes } from '@modrinth/utils'
|
||||||
import { handleError } from '@/store/notifications'
|
import { handleError } from '@/store/notifications'
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
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<{
|
const emit = defineEmits<{
|
||||||
(e: 'updateSkipped', version: string): void
|
(e: 'updateSkipped', version: string): Promise<void>
|
||||||
|
(e: 'updateEnqueuedForLater', version: string | null): Promise<void>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
@ -98,15 +103,26 @@ type UpdateData = {
|
|||||||
|
|
||||||
const update = ref<UpdateData>()
|
const update = ref<UpdateData>()
|
||||||
const updateSize = ref<number>()
|
const updateSize = ref<number>()
|
||||||
const updateInProgress = ref(false)
|
|
||||||
|
const updatingImmediately = ref(false)
|
||||||
|
const downloadInProgress = ref(false)
|
||||||
const downloadProgress = ref(0)
|
const downloadProgress = ref(0)
|
||||||
|
|
||||||
|
const enqueuedUpdate = ref<string | null>(null)
|
||||||
|
|
||||||
const modal = useTemplateRef('modal')
|
const modal = useTemplateRef('modal')
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
|
|
||||||
async function show(newUpdate: UpdateData) {
|
async function show(newUpdate: UpdateData) {
|
||||||
|
const oldVersion = update.value?.version
|
||||||
|
|
||||||
update.value = newUpdate
|
update.value = newUpdate
|
||||||
updateSize.value = await getUpdateSize(newUpdate.rid).catch(handleError)
|
updateSize.value = await getUpdateSize(newUpdate.rid).catch(handleError)
|
||||||
|
|
||||||
|
if (oldVersion !== update.value?.version) {
|
||||||
|
downloadProgress.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
modal.value!.show()
|
modal.value!.show()
|
||||||
isOpen.value = true
|
isOpen.value = true
|
||||||
}
|
}
|
||||||
@ -121,25 +137,62 @@ function hide() {
|
|||||||
|
|
||||||
defineExpose({ show, hide, isOpen })
|
defineExpose({ show, hide, isOpen })
|
||||||
|
|
||||||
function installUpdateNow() {
|
loading_listener((event) => {
|
||||||
updateInProgress.value = true
|
if (event.event.type === 'launcher_update') {
|
||||||
let totalSize = 0
|
if (event.event.version === update.value!.version) {
|
||||||
let totalDownloaded = 0
|
downloadProgress.value = (event.fraction ?? 1.0) * 100
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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() {
|
function skipUpdate() {
|
||||||
hide()
|
enqueuedUpdate.value = null
|
||||||
emit('updateSkipped', update.value!.version)
|
emit('updateSkipped', update.value!.version)
|
||||||
|
|
||||||
|
removeEnqueuedUpdate()
|
||||||
|
hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,14 @@ export async function getUpdateSize(updateRid) {
|
|||||||
return await invoke('get_update_size', { rid: 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'
|
// One of 'Windows', 'Linux', 'MacOS'
|
||||||
export async function getOS() {
|
export async function getOS() {
|
||||||
return await invoke('plugin:utils|get_os')
|
return await invoke('plugin:utils|get_os')
|
||||||
|
|||||||
@ -45,8 +45,12 @@ pub enum TheseusSerializableError {
|
|||||||
Tauri(#[from] tauri::Error),
|
Tauri(#[from] tauri::Error),
|
||||||
|
|
||||||
#[cfg(feature = "updater")]
|
#[cfg(feature = "updater")]
|
||||||
#[error("Tauri updater error: {0}")]
|
#[error("Updater error: {0}")]
|
||||||
TauriUpdater(#[from] tauri_plugin_updater::Error),
|
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
|
// Generic implementation of From<T> for ErrorTypeA
|
||||||
@ -104,5 +108,6 @@ impl_serialize! {
|
|||||||
impl_serialize! {
|
impl_serialize! {
|
||||||
IO,
|
IO,
|
||||||
Tauri,
|
Tauri,
|
||||||
TauriUpdater,
|
Updater,
|
||||||
|
Http,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,9 @@ mod error;
|
|||||||
mod macos;
|
mod macos;
|
||||||
|
|
||||||
#[cfg(feature = "updater")]
|
#[cfg(feature = "updater")]
|
||||||
mod update_size_checker;
|
mod updater_impl;
|
||||||
|
#[cfg(not(feature = "updater"))]
|
||||||
|
mod updater_impl_noop;
|
||||||
|
|
||||||
// Should be called in launcher initialization
|
// Should be called in launcher initialization
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
@ -68,13 +70,10 @@ fn are_updates_enabled() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "updater")]
|
#[cfg(feature = "updater")]
|
||||||
pub use update_size_checker::get_update_size;
|
pub use updater_impl::*;
|
||||||
|
|
||||||
#[cfg(not(feature = "updater"))]
|
#[cfg(not(feature = "updater"))]
|
||||||
#[tauri::command]
|
pub use updater_impl_noop::*;
|
||||||
fn get_update_size() -> theseus::Result<()> {
|
|
||||||
Err(theseus::ErrorKind::OtherError("Updates are disabled in this build.".to_string()).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggles decorations
|
// Toggles decorations
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -212,11 +211,14 @@ fn main() {
|
|||||||
.plugin(api::ads::init())
|
.plugin(api::ads::init())
|
||||||
.plugin(api::friends::init())
|
.plugin(api::friends::init())
|
||||||
.plugin(api::worlds::init())
|
.plugin(api::worlds::init())
|
||||||
|
.manage(PendingUpdateData::default())
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
initialize_state,
|
initialize_state,
|
||||||
is_dev,
|
is_dev,
|
||||||
are_updates_enabled,
|
are_updates_enabled,
|
||||||
get_update_size,
|
get_update_size,
|
||||||
|
enqueue_update_for_installation,
|
||||||
|
remove_enqueued_update,
|
||||||
toggle_decorations,
|
toggle_decorations,
|
||||||
show_window,
|
show_window,
|
||||||
restart_app,
|
restart_app,
|
||||||
@ -228,8 +230,9 @@ fn main() {
|
|||||||
match app {
|
match app {
|
||||||
Ok(app) => {
|
Ok(app) => {
|
||||||
app.run(|app, event| {
|
app.run(|app, event| {
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(any(target_os = "macos", feature = "updater")))]
|
||||||
drop((app, event));
|
drop((app, event));
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if let tauri::RunEvent::Opened { urls } = event {
|
if let tauri::RunEvent::Opened { urls } = event {
|
||||||
tracing::info!("Handling webview open {urls:?}");
|
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) => {
|
Err(e) => {
|
||||||
|
tracing::error!("Error while running tauri application: {:?}", e);
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
|
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
|
||||||
@ -285,7 +308,6 @@ fn main() {
|
|||||||
.show()
|
.show()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
tracing::error!("Error while running tauri application: {:?}", e);
|
|
||||||
panic!("{1}: {:?}", e, "error while running tauri application")
|
panic!("{1}: {:?}", e, "error while running tauri application")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
apps/app/src/updater_impl.rs
Normal file
122
apps/app/src/updater_impl.rs
Normal 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
apps/app/src/updater_impl_noop.rs
Normal file
26
apps/app/src/updater_impl_noop.rs
Normal 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() {}
|
||||||
@ -176,7 +176,6 @@ pub enum LoadingBarType {
|
|||||||
import_location: PathBuf,
|
import_location: PathBuf,
|
||||||
profile_name: String,
|
profile_name: String,
|
||||||
},
|
},
|
||||||
CheckingForUpdates,
|
|
||||||
LauncherUpdate {
|
LauncherUpdate {
|
||||||
version: String,
|
version: String,
|
||||||
current_version: String,
|
current_version: String,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user