Skip to content

Commit 4094494

Browse files
authored
feat(core): add API to manually trigger updater check (#3712)
1 parent 137c9c7 commit 4094494

File tree

3 files changed

+179
-122
lines changed

3 files changed

+179
-122
lines changed

.changes/updater-check-api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added `check_for_updates` method to `App` and `AppHandle`.

core/tauri/src/app.rs

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,14 @@ impl<R: Runtime> ManagerBase<R> for App<R> {
388388
macro_rules! shared_app_impl {
389389
($app: ty) => {
390390
impl<R: Runtime> $app {
391+
#[cfg(feature = "updater")]
392+
#[cfg_attr(doc_cfg, doc(cfg(feature = "updater")))]
393+
/// Runs the updater to check if there is a new app version.
394+
/// It is the same as triggering the `tauri://update` event.
395+
pub async fn check_for_updates(&self) -> updater::Result<updater::UpdateResponse<R>> {
396+
updater::check(self.app_handle()).await
397+
}
398+
391399
/// Creates a new webview window.
392400
///
393401
/// Data URLs are only supported with the `window-data-url` feature flag.
@@ -580,44 +588,40 @@ impl<R: Runtime> App<R> {
580588
});
581589
}
582590

583-
/// Listen updater events when dialog are disabled.
584-
fn listen_updater_events(&self, handle: AppHandle<R>) {
585-
let updater_config = self.manager.config().tauri.updater.clone();
586-
updater::listener(updater_config, self.manager.package_info().clone(), &handle);
587-
}
588-
589591
fn run_updater(&self) {
590592
let handle = self.handle();
591593
let handle_ = handle.clone();
592594
let updater_config = self.manager.config().tauri.updater.clone();
593595
// check if updater is active or not
594-
if updater_config.dialog && updater_config.active {
595-
// if updater dialog is enabled spawn a new task
596-
self.run_updater_dialog();
597-
let config = self.manager.config().tauri.updater.clone();
598-
let package_info = self.manager.package_info().clone();
599-
// When dialog is enabled, if user want to recheck
600-
// if an update is available after first start
601-
// invoke the Event `tauri://update` from JS or rust side.
602-
handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
603-
let handle = handle_.clone();
604-
let package_info = package_info.clone();
605-
let config = config.clone();
606-
// re-spawn task inside tokyo to launch the download
607-
// we don't need to emit anything as everything is handled
608-
// by the process (user is asked to restart at the end)
609-
// and it's handled by the updater
610-
crate::async_runtime::spawn(async move {
611-
updater::check_update_with_dialog(config, package_info, handle).await
596+
if updater_config.active {
597+
if updater_config.dialog {
598+
// if updater dialog is enabled spawn a new task
599+
self.run_updater_dialog();
600+
let config = self.manager.config().tauri.updater.clone();
601+
let package_info = self.manager.package_info().clone();
602+
// When dialog is enabled, if user want to recheck
603+
// if an update is available after first start
604+
// invoke the Event `tauri://update` from JS or rust side.
605+
handle.listen_global(updater::EVENT_CHECK_UPDATE, move |_msg| {
606+
let handle = handle_.clone();
607+
let package_info = package_info.clone();
608+
let config = config.clone();
609+
// re-spawn task inside tokyo to launch the download
610+
// we don't need to emit anything as everything is handled
611+
// by the process (user is asked to restart at the end)
612+
// and it's handled by the updater
613+
crate::async_runtime::spawn(async move {
614+
updater::check_update_with_dialog(config, package_info, handle).await
615+
});
612616
});
613-
});
614-
} else if updater_config.active {
615-
// we only listen for `tauri://update`
616-
// once we receive the call, we check if an update is available or not
617-
// if there is a new update we emit `tauri://update-available` with details
618-
// this is the user responsabilities to display dialog and ask if user want to install
619-
// to install the update you need to invoke the Event `tauri://update-install`
620-
self.listen_updater_events(handle);
617+
} else {
618+
// we only listen for `tauri://update`
619+
// once we receive the call, we check if an update is available or not
620+
// if there is a new update we emit `tauri://update-available` with details
621+
// this is the user responsabilities to display dialog and ask if user want to install
622+
// to install the update you need to invoke the Event `tauri://update-install`
623+
updater::listener(handle);
624+
}
621625
}
622626
}
623627
}

core/tauri/src/updater/mod.rs

Lines changed: 138 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ mod core;
331331
mod error;
332332

333333
pub use self::error::Error;
334+
/// Alias for [`std::result::Result`] using our own [`Error`].
335+
pub type Result<T> = std::result::Result<T, Error>;
334336

335337
use crate::{
336338
api::dialog::blocking::ask, runtime::EventLoopProxy, utils::config::UpdaterConfig, AppHandle,
@@ -370,6 +372,43 @@ struct UpdateManifest {
370372
body: String,
371373
}
372374

375+
/// The response of an updater [`check`].
376+
pub struct UpdateResponse<R: Runtime> {
377+
update: core::Update,
378+
handle: AppHandle<R>,
379+
}
380+
381+
impl<R: Runtime> Clone for UpdateResponse<R> {
382+
fn clone(&self) -> Self {
383+
Self {
384+
update: self.update.clone(),
385+
handle: self.handle.clone(),
386+
}
387+
}
388+
}
389+
390+
impl<R: Runtime> UpdateResponse<R> {
391+
/// Whether the updater found a newer release or not.
392+
pub fn is_update_available(&self) -> bool {
393+
self.update.should_update
394+
}
395+
396+
/// The current version of the application as read by the updater.
397+
pub fn current_version(&self) -> &str {
398+
&self.update.current_version
399+
}
400+
401+
/// The latest version of the application found by the updater.
402+
pub fn latest_version(&self) -> &str {
403+
&self.update.version
404+
}
405+
406+
/// Downloads and installs the update.
407+
pub async fn download_and_install(self) -> Result<()> {
408+
download_and_install(self.handle, self.update).await
409+
}
410+
}
411+
373412
/// Check if there is any new update with builtin dialog.
374413
pub(crate) async fn check_update_with_dialog<R: Runtime>(
375414
updater_config: UpdaterConfig,
@@ -417,103 +456,112 @@ pub(crate) async fn check_update_with_dialog<R: Runtime>(
417456
}
418457
}
419458

420-
/// Experimental listener
459+
/// Updater listener
421460
/// This function should be run on the main thread once.
422-
pub(crate) fn listener<R: Runtime>(
423-
updater_config: UpdaterConfig,
424-
package_info: crate::PackageInfo,
425-
handle: &AppHandle<R>,
426-
) {
427-
let handle_ = handle.clone();
428-
461+
pub(crate) fn listener<R: Runtime>(handle: AppHandle<R>) {
429462
// Wait to receive the event `"tauri://update"`
463+
let handle_ = handle.clone();
430464
handle.listen_global(EVENT_CHECK_UPDATE, move |_msg| {
431-
let handle = handle_.clone();
432-
let package_info = package_info.clone();
433-
434-
// prepare our endpoints
435-
let endpoints = updater_config
436-
.endpoints
437-
.as_ref()
438-
.expect("Something wrong with endpoints")
439-
.iter()
440-
.map(|e| e.to_string())
441-
.collect::<Vec<String>>();
442-
443-
let pubkey = updater_config.pubkey.clone();
444-
445-
// check updates
465+
let handle_ = handle_.clone();
446466
crate::async_runtime::spawn(async move {
447-
let handle = handle.clone();
448-
let handle_ = handle.clone();
449-
let pubkey = pubkey.clone();
450-
let env = handle.state::<Env>().inner().clone();
451-
452-
match self::core::builder(env)
453-
.urls(&endpoints[..])
454-
.current_version(&package_info.version)
455-
.build()
456-
.await
457-
{
458-
Ok(updater) => {
459-
// send notification if we need to update
460-
if updater.should_update {
461-
let body = updater.body.clone().unwrap_or_else(|| String::from(""));
462-
463-
// Emit `tauri://update-available`
464-
let _ = handle.emit_all(
465-
EVENT_UPDATE_AVAILABLE,
466-
UpdateManifest {
467-
body: body.clone(),
468-
date: updater.date.clone(),
469-
version: updater.version.clone(),
470-
},
471-
);
472-
let _ = handle.create_proxy().send_event(EventLoopMessage::Updater(
473-
UpdaterEvent::UpdateAvailable {
474-
body,
475-
date: updater.date.clone(),
476-
version: updater.version.clone(),
477-
},
478-
));
479-
480-
// Listen for `tauri://update-install`
481-
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
482-
let handle = handle_.clone();
483-
let updater = updater.clone();
484-
485-
// Start installation
486-
crate::async_runtime::spawn(async move {
487-
// emit {"status": "PENDING"}
488-
send_status_update(&handle, UpdaterEvent::Pending);
489-
490-
// Launch updater download process
491-
// macOS we display the `Ready to restart dialog` asking to restart
492-
// Windows is closing the current App and launch the downloaded MSI when ready (the process stop here)
493-
// 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)
494-
let update_result = updater.clone().download_and_install(pubkey.clone()).await;
495-
496-
if let Err(err) = update_result {
497-
// emit {"status": "ERROR", "error": "The error message"}
498-
send_status_update(&handle, UpdaterEvent::Error(err.to_string()));
499-
} else {
500-
// emit {"status": "DONE"}
501-
send_status_update(&handle, UpdaterEvent::Updated);
502-
}
503-
});
504-
});
505-
} else {
506-
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
507-
}
508-
}
509-
Err(e) => {
510-
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
511-
}
512-
}
467+
let _ = check(handle_.clone()).await;
513468
});
514469
});
515470
}
516471

472+
pub(crate) async fn download_and_install<R: Runtime>(
473+
handle: AppHandle<R>,
474+
update: core::Update,
475+
) -> Result<()> {
476+
let update = update.clone();
477+
478+
// Start installation
479+
// emit {"status": "PENDING"}
480+
send_status_update(&handle, UpdaterEvent::Pending);
481+
482+
// Launch updater download process
483+
// macOS we display the `Ready to restart dialog` asking to restart
484+
// Windows is closing the current App and launch the downloaded MSI when ready (the process stop here)
485+
// 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)
486+
let update_result = update
487+
.clone()
488+
.download_and_install(handle.config().tauri.updater.pubkey.clone())
489+
.await;
490+
491+
if let Err(err) = &update_result {
492+
// emit {"status": "ERROR", "error": "The error message"}
493+
send_status_update(&handle, UpdaterEvent::Error(err.to_string()));
494+
} else {
495+
// emit {"status": "DONE"}
496+
send_status_update(&handle, UpdaterEvent::Updated);
497+
}
498+
update_result
499+
}
500+
501+
pub(crate) async fn check<R: Runtime>(handle: AppHandle<R>) -> Result<UpdateResponse<R>> {
502+
let updater_config = &handle.config().tauri.updater;
503+
let package_info = handle.package_info().clone();
504+
505+
// prepare our endpoints
506+
let endpoints = updater_config
507+
.endpoints
508+
.as_ref()
509+
.expect("Something wrong with endpoints")
510+
.iter()
511+
.map(|e| e.to_string())
512+
.collect::<Vec<String>>();
513+
514+
// check updates
515+
let env = handle.state::<Env>().inner().clone();
516+
517+
match self::core::builder(env)
518+
.urls(&endpoints[..])
519+
.current_version(&package_info.version)
520+
.build()
521+
.await
522+
{
523+
Ok(update) => {
524+
// send notification if we need to update
525+
if update.should_update {
526+
let body = update.body.clone().unwrap_or_else(|| String::from(""));
527+
528+
// Emit `tauri://update-available`
529+
let _ = handle.emit_all(
530+
EVENT_UPDATE_AVAILABLE,
531+
UpdateManifest {
532+
body: body.clone(),
533+
date: update.date.clone(),
534+
version: update.version.clone(),
535+
},
536+
);
537+
let _ = handle.create_proxy().send_event(EventLoopMessage::Updater(
538+
UpdaterEvent::UpdateAvailable {
539+
body,
540+
date: update.date.clone(),
541+
version: update.version.clone(),
542+
},
543+
));
544+
545+
// Listen for `tauri://update-install`
546+
let handle_ = handle.clone();
547+
let update_ = update.clone();
548+
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
549+
crate::async_runtime::spawn(async move {
550+
let _ = download_and_install(handle_, update_).await;
551+
});
552+
});
553+
} else {
554+
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
555+
}
556+
Ok(UpdateResponse { update, handle })
557+
}
558+
Err(e) => {
559+
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
560+
Err(e)
561+
}
562+
}
563+
}
564+
517565
// Send a status update via `tauri://update-status` event.
518566
fn send_status_update<R: Runtime>(handle: &AppHandle<R>, message: UpdaterEvent) {
519567
let _ = handle.emit_all(
@@ -543,7 +591,7 @@ async fn prompt_for_install<R: Runtime>(
543591
app_name: &str,
544592
body: &str,
545593
pubkey: String,
546-
) -> crate::Result<()> {
594+
) -> Result<()> {
547595
// remove single & double quote
548596
let escaped_body = body.replace(&['\"', '\''][..], "");
549597
let windows = handle.windows();

0 commit comments

Comments
 (0)