feat(core): add API to manually trigger updater check (#3712)

This commit is contained in:
Lucas Fernandes Nogueira 2022-03-17 10:04:13 -03:00 committed by GitHub
parent 137c9c7d4c
commit 4094494a1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 179 additions and 122 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Added `check_for_updates` method to `App` and `AppHandle`.

View File

@ -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);
}
}
}
}

View File

@ -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();