mirror of https://github.com/tauri-apps/tauri
feat(core): add API to manually trigger updater check (#3712)
This commit is contained in:
parent
137c9c7d4c
commit
4094494a1b
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Added `check_for_updates` method to `App` and `AppHandle`.
|
|
@ -388,6 +388,14 @@ impl<R: Runtime> ManagerBase<R> for App<R> {
|
|||
macro_rules! shared_app_impl {
|
||||
($app: ty) => {
|
||||
impl<R: Runtime> $app {
|
||||
#[cfg(feature = "updater")]
|
||||
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
|
||||
/// Runs the updater to check if there is a new app version.
|
||||
/// It is the same as triggering the `tauri://update` event.
|
||||
pub async fn check_for_updates(&self) -> updater::Result<updater::UpdateResponse<R>> {
|
||||
updater::check(self.app_handle()).await
|
||||
}
|
||||
|
||||
/// Creates a new webview window.
|
||||
///
|
||||
/// Data URLs are only supported with the `window-data-url` feature flag.
|
||||
|
@ -580,44 +588,40 @@ impl<R: Runtime> App<R> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Listen updater events when dialog are disabled.
|
||||
fn listen_updater_events(&self, handle: AppHandle<R>) {
|
||||
let updater_config = self.manager.config().tauri.updater.clone();
|
||||
updater::listener(updater_config, self.manager.package_info().clone(), &handle);
|
||||
}
|
||||
|
||||
fn run_updater(&self) {
|
||||
let handle = self.handle();
|
||||
let handle_ = handle.clone();
|
||||
let updater_config = self.manager.config().tauri.updater.clone();
|
||||
// check if updater is active or not
|
||||
if updater_config.dialog && updater_config.active {
|
||||
// if updater dialog is enabled spawn a new task
|
||||
self.run_updater_dialog();
|
||||
let config = self.manager.config().tauri.updater.clone();
|
||||
let package_info = self.manager.package_info().clone();
|
||||
// When dialog is enabled, if user want to recheck
|
||||
// if an update is available after first start
|
||||
// invoke the Event `tauri://update` from JS or rust side.
|
||||
handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
|
||||
let handle = handle_.clone();
|
||||
let package_info = package_info.clone();
|
||||
let config = config.clone();
|
||||
// re-spawn task inside tokyo to launch the download
|
||||
// we don't need to emit anything as everything is handled
|
||||
// by the process (user is asked to restart at the end)
|
||||
// and it's handled by the updater
|
||||
crate::async_runtime::spawn(async move {
|
||||
updater::check_update_with_dialog(config, package_info, handle).await
|
||||
if updater_config.active {
|
||||
if updater_config.dialog {
|
||||
// if updater dialog is enabled spawn a new task
|
||||
self.run_updater_dialog();
|
||||
let config = self.manager.config().tauri.updater.clone();
|
||||
let package_info = self.manager.package_info().clone();
|
||||
// When dialog is enabled, if user want to recheck
|
||||
// if an update is available after first start
|
||||
// invoke the Event `tauri://update` from JS or rust side.
|
||||
handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
|
||||
let handle = handle_.clone();
|
||||
let package_info = package_info.clone();
|
||||
let config = config.clone();
|
||||
// re-spawn task inside tokyo to launch the download
|
||||
// we don't need to emit anything as everything is handled
|
||||
// by the process (user is asked to restart at the end)
|
||||
// and it's handled by the updater
|
||||
crate::async_runtime::spawn(async move {
|
||||
updater::check_update_with_dialog(config, package_info, handle).await
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if updater_config.active {
|
||||
// we only listen for `tauri://update`
|
||||
// once we receive the call, we check if an update is available or not
|
||||
// if there is a new update we emit `tauri://update-available` with details
|
||||
// this is the user responsabilities to display dialog and ask if user want to install
|
||||
// to install the update you need to invoke the Event `tauri://update-install`
|
||||
self.listen_updater_events(handle);
|
||||
} else {
|
||||
// we only listen for `tauri://update`
|
||||
// once we receive the call, we check if an update is available or not
|
||||
// if there is a new update we emit `tauri://update-available` with details
|
||||
// this is the user responsabilities to display dialog and ask if user want to install
|
||||
// to install the update you need to invoke the Event `tauri://update-install`
|
||||
updater::listener(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -331,6 +331,8 @@ mod core;
|
|||
mod error;
|
||||
|
||||
pub use self::error::Error;
|
||||
/// Alias for [`std::result::Result`] using our own [`Error`].
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
use crate::{
|
||||
api::dialog::blocking::ask, runtime::EventLoopProxy, utils::config::UpdaterConfig, AppHandle,
|
||||
|
@ -370,6 +372,43 @@ struct UpdateManifest {
|
|||
body: String,
|
||||
}
|
||||
|
||||
/// The response of an updater [`check`].
|
||||
pub struct UpdateResponse<R: Runtime> {
|
||||
update: core::Update,
|
||||
handle: AppHandle<R>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Clone for UpdateResponse<R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
update: self.update.clone(),
|
||||
handle: self.handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> UpdateResponse<R> {
|
||||
/// Whether the updater found a newer release or not.
|
||||
pub fn is_update_available(&self) -> bool {
|
||||
self.update.should_update
|
||||
}
|
||||
|
||||
/// The current version of the application as read by the updater.
|
||||
pub fn current_version(&self) -> &str {
|
||||
&self.update.current_version
|
||||
}
|
||||
|
||||
/// The latest version of the application found by the updater.
|
||||
pub fn latest_version(&self) -> &str {
|
||||
&self.update.version
|
||||
}
|
||||
|
||||
/// Downloads and installs the update.
|
||||
pub async fn download_and_install(self) -> Result<()> {
|
||||
download_and_install(self.handle, self.update).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if there is any new update with builtin dialog.
|
||||
pub(crate) async fn check_update_with_dialog<R: Runtime>(
|
||||
updater_config: UpdaterConfig,
|
||||
|
@ -417,103 +456,112 @@ pub(crate) async fn check_update_with_dialog<R: Runtime>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Experimental listener
|
||||
/// Updater listener
|
||||
/// This function should be run on the main thread once.
|
||||
pub(crate) fn listener<R: Runtime>(
|
||||
updater_config: UpdaterConfig,
|
||||
package_info: crate::PackageInfo,
|
||||
handle: &AppHandle<R>,
|
||||
) {
|
||||
let handle_ = handle.clone();
|
||||
|
||||
pub(crate) fn listener<R: Runtime>(handle: AppHandle<R>) {
|
||||
// Wait to receive the event `"tauri://update"`
|
||||
let handle_ = handle.clone();
|
||||
handle.listen_global(EVENT_CHECK_UPDATE, move |_msg| {
|
||||
let handle = handle_.clone();
|
||||
let package_info = package_info.clone();
|
||||
|
||||
// prepare our endpoints
|
||||
let endpoints = updater_config
|
||||
.endpoints
|
||||
.as_ref()
|
||||
.expect("Something wrong with endpoints")
|
||||
.iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let pubkey = updater_config.pubkey.clone();
|
||||
|
||||
// check updates
|
||||
let handle_ = handle_.clone();
|
||||
crate::async_runtime::spawn(async move {
|
||||
let handle = handle.clone();
|
||||
let handle_ = handle.clone();
|
||||
let pubkey = pubkey.clone();
|
||||
let env = handle.state::<Env>().inner().clone();
|
||||
|
||||
match self::core::builder(env)
|
||||
.urls(&endpoints[..])
|
||||
.current_version(&package_info.version)
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(updater) => {
|
||||
// send notification if we need to update
|
||||
if updater.should_update {
|
||||
let body = updater.body.clone().unwrap_or_else(|| String::from(""));
|
||||
|
||||
// Emit `tauri://update-available`
|
||||
let _ = handle.emit_all(
|
||||
EVENT_UPDATE_AVAILABLE,
|
||||
UpdateManifest {
|
||||
body: body.clone(),
|
||||
date: updater.date.clone(),
|
||||
version: updater.version.clone(),
|
||||
},
|
||||
);
|
||||
let _ = handle.create_proxy().send_event(EventLoopMessage::Updater(
|
||||
UpdaterEvent::UpdateAvailable {
|
||||
body,
|
||||
date: updater.date.clone(),
|
||||
version: updater.version.clone(),
|
||||
},
|
||||
));
|
||||
|
||||
// Listen for `tauri://update-install`
|
||||
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
|
||||
let handle = handle_.clone();
|
||||
let updater = updater.clone();
|
||||
|
||||
// Start installation
|
||||
crate::async_runtime::spawn(async move {
|
||||
// emit {"status": "PENDING"}
|
||||
send_status_update(&handle, UpdaterEvent::Pending);
|
||||
|
||||
// Launch updater download process
|
||||
// macOS we display the `Ready to restart dialog` asking to restart
|
||||
// Windows is closing the current App and launch the downloaded MSI when ready (the process stop here)
|
||||
// Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here)
|
||||
let update_result = updater.clone().download_and_install(pubkey.clone()).await;
|
||||
|
||||
if let Err(err) = update_result {
|
||||
// emit {"status": "ERROR", "error": "The error message"}
|
||||
send_status_update(&handle, UpdaterEvent::Error(err.to_string()));
|
||||
} else {
|
||||
// emit {"status": "DONE"}
|
||||
send_status_update(&handle, UpdaterEvent::Updated);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
|
||||
}
|
||||
}
|
||||
let _ = check(handle_.clone()).await;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn download_and_install<R: Runtime>(
|
||||
handle: AppHandle<R>,
|
||||
update: core::Update,
|
||||
) -> Result<()> {
|
||||
let update = update.clone();
|
||||
|
||||
// Start installation
|
||||
// emit {"status": "PENDING"}
|
||||
send_status_update(&handle, UpdaterEvent::Pending);
|
||||
|
||||
// Launch updater download process
|
||||
// macOS we display the `Ready to restart dialog` asking to restart
|
||||
// Windows is closing the current App and launch the downloaded MSI when ready (the process stop here)
|
||||
// Linux we replace the AppImage by launching a new install, it start a new AppImage instance, so we're closing the previous. (the process stop here)
|
||||
let update_result = update
|
||||
.clone()
|
||||
.download_and_install(handle.config().tauri.updater.pubkey.clone())
|
||||
.await;
|
||||
|
||||
if let Err(err) = &update_result {
|
||||
// emit {"status": "ERROR", "error": "The error message"}
|
||||
send_status_update(&handle, UpdaterEvent::Error(err.to_string()));
|
||||
} else {
|
||||
// emit {"status": "DONE"}
|
||||
send_status_update(&handle, UpdaterEvent::Updated);
|
||||
}
|
||||
update_result
|
||||
}
|
||||
|
||||
pub(crate) async fn check<R: Runtime>(handle: AppHandle<R>) -> Result<UpdateResponse<R>> {
|
||||
let updater_config = &handle.config().tauri.updater;
|
||||
let package_info = handle.package_info().clone();
|
||||
|
||||
// prepare our endpoints
|
||||
let endpoints = updater_config
|
||||
.endpoints
|
||||
.as_ref()
|
||||
.expect("Something wrong with endpoints")
|
||||
.iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// check updates
|
||||
let env = handle.state::<Env>().inner().clone();
|
||||
|
||||
match self::core::builder(env)
|
||||
.urls(&endpoints[..])
|
||||
.current_version(&package_info.version)
|
||||
.build()
|
||||
.await
|
||||
{
|
||||
Ok(update) => {
|
||||
// send notification if we need to update
|
||||
if update.should_update {
|
||||
let body = update.body.clone().unwrap_or_else(|| String::from(""));
|
||||
|
||||
// Emit `tauri://update-available`
|
||||
let _ = handle.emit_all(
|
||||
EVENT_UPDATE_AVAILABLE,
|
||||
UpdateManifest {
|
||||
body: body.clone(),
|
||||
date: update.date.clone(),
|
||||
version: update.version.clone(),
|
||||
},
|
||||
);
|
||||
let _ = handle.create_proxy().send_event(EventLoopMessage::Updater(
|
||||
UpdaterEvent::UpdateAvailable {
|
||||
body,
|
||||
date: update.date.clone(),
|
||||
version: update.version.clone(),
|
||||
},
|
||||
));
|
||||
|
||||
// Listen for `tauri://update-install`
|
||||
let handle_ = handle.clone();
|
||||
let update_ = update.clone();
|
||||
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
|
||||
crate::async_runtime::spawn(async move {
|
||||
let _ = download_and_install(handle_, update_).await;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
|
||||
}
|
||||
Ok(UpdateResponse { update, handle })
|
||||
}
|
||||
Err(e) => {
|
||||
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a status update via `tauri://update-status` event.
|
||||
fn send_status_update<R: Runtime>(handle: &AppHandle<R>, message: UpdaterEvent) {
|
||||
let _ = handle.emit_all(
|
||||
|
@ -543,7 +591,7 @@ async fn prompt_for_install<R: Runtime>(
|
|||
app_name: &str,
|
||||
body: &str,
|
||||
pubkey: String,
|
||||
) -> crate::Result<()> {
|
||||
) -> Result<()> {
|
||||
// remove single & double quote
|
||||
let escaped_body = body.replace(&['\"', '\''][..], "");
|
||||
let windows = handle.windows();
|
||||
|
|
Loading…
Reference in New Issue