Skip to content

Commit

Permalink
feat(updater): expose builder, allow setting a custom version checker (
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog committed Mar 28, 2022
1 parent f6e32ee commit c64268f
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .changes/updater-check-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"tauri": patch
---

Added `check_for_updates` method to `App` and `AppHandle`.
Added `updater` method to `App` and `AppHandle`, a builder to check for app updates.
5 changes: 5 additions & 0 deletions .changes/updater-custom-version-checker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Allow using a custom updater version checker via `App::updater().should_install()`.
23 changes: 19 additions & 4 deletions core/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,25 @@ macro_rules! shared_app_impl {
impl<R: Runtime> $app {
#[cfg(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
/// Gets the updater builder to manually check if an update is available.
///
/// # Examples
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
#[cfg_attr(
any(feature = "updater", feature = "__updater-docs"),
doc = r#" let response = handle.updater().check().await;"#
)]
/// });
/// Ok(())
/// });
/// ```
pub fn updater(&self) -> updater::UpdateBuilder<R> {
updater::builder(self.app_handle())
}

/// Creates a new webview window.
Expand Down
55 changes: 36 additions & 19 deletions core/tauri/src/updater/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tauri_utils::{platform::current_exe, Env};
use std::io::Seek;
use std::{
collections::HashMap,
env,
env, fmt,
io::{Cursor, Read},
path::{Path, PathBuf},
str::from_utf8,
Expand Down Expand Up @@ -198,29 +198,42 @@ impl RemoteRelease {
}
}

#[derive(Debug)]
pub struct UpdateBuilder<'a, R: Runtime> {
pub struct UpdateBuilder<R: Runtime> {
/// Application handle.
pub app: AppHandle<R>,
/// Current version we are running to compare with announced version
pub current_version: &'a str,
pub current_version: String,
/// The URLs to checks updates. We suggest at least one fallback on a different domain.
pub urls: Vec<String>,
/// The platform the updater will check and install the update. Default is from `get_updater_target`
pub target: Option<String>,
/// The current executable path. Default is automatically extracted.
pub executable_path: Option<PathBuf>,
should_install: Option<Box<dyn FnOnce(&str, &str) -> bool + Send>>,
}

impl<R: Runtime> fmt::Debug for UpdateBuilder<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UpdateBuilder")
.field("app", &self.app)
.field("current_version", &self.current_version)
.field("urls", &self.urls)
.field("target", &self.target)
.field("executable_path", &self.executable_path)
.finish()
}
}

// Create new updater instance and return an Update
impl<'a, R: Runtime> UpdateBuilder<'a, R> {
impl<R: Runtime> UpdateBuilder<R> {
pub fn new(app: AppHandle<R>) -> Self {
UpdateBuilder {
app,
urls: Vec::new(),
target: None,
executable_path: None,
current_version: env!("CARGO_PKG_VERSION"),
current_version: env!("CARGO_PKG_VERSION").into(),
should_install: None,
}
}

Expand Down Expand Up @@ -250,15 +263,14 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {

/// Set the current app version, used to compare against the latest available version.
/// The `cargo_crate_version!` macro can be used to pull the version from your `Cargo.toml`
pub fn current_version(mut self, ver: &'a str) -> Self {
self.current_version = ver;
pub fn current_version(mut self, ver: impl Into<String>) -> Self {
self.current_version = ver.into();
self
}

/// Set the target name. Represents the string that is looked up on the updater API or response JSON.
#[allow(dead_code)]
pub fn target(mut self, target: &str) -> Self {
self.target = Some(target.to_owned());
pub fn target(mut self, target: impl Into<String>) -> Self {
self.target.replace(target.into());
self
}

Expand All @@ -269,7 +281,12 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
self
}

pub async fn build(self) -> Result<Update<R>> {
pub fn should_install<F: FnOnce(&str, &str) -> bool + Send + 'static>(mut self, f: F) -> Self {
self.should_install.replace(Box::new(f));
self
}

pub async fn build(mut self) -> Result<Update<R>> {
let mut remote_release: Option<RemoteRelease> = None;

// make sure we have at least one url
Expand All @@ -279,9 +296,6 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
));
};

// set current version if not set
let current_version = self.current_version;

// If no executable path provided, we use current_exe from tauri_utils
let executable_path = self.executable_path.unwrap_or(current_exe()?);

Expand Down Expand Up @@ -324,7 +338,7 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
// The main objective is if the update URL is defined via the Cargo.toml
// the URL will be generated dynamicly
let fixed_link = url
.replace("{{current_version}}", current_version)
.replace("{{current_version}}", &self.current_version)
.replace("{{target}}", &target)
.replace("{{arch}}", arch);

Expand Down Expand Up @@ -383,8 +397,11 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
let final_release = remote_release.ok_or(Error::ReleaseNotFound)?;

// did the announced version is greated than our current one?
let should_update =
version::is_greater(current_version, &final_release.version).unwrap_or(false);
let should_update = if let Some(comparator) = self.should_install.take() {
comparator(&self.current_version, &final_release.version)
} else {
version::is_greater(&self.current_version, &final_release.version).unwrap_or(false)
};

// create our new updater
Ok(Update {
Expand All @@ -404,7 +421,7 @@ impl<'a, R: Runtime> UpdateBuilder<'a, R> {
}
}

pub fn builder<'a, R: Runtime>(app: AppHandle<R>) -> UpdateBuilder<'a, R> {
pub fn builder<R: Runtime>(app: AppHandle<R>) -> UpdateBuilder<R> {
UpdateBuilder::new(app)
}

Expand Down
162 changes: 117 additions & 45 deletions core/tauri/src/updater/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
//! .setup(|app| {
//! let handle = app.handle();
//! tauri::async_runtime::spawn(async move {
//! let response = handle.check_for_updates().await;
//! let response = handle.updater().check().await;
//! });
//! Ok(())
//! });
Expand Down Expand Up @@ -165,7 +165,7 @@
//! .setup(|app| {
//! let handle = app.handle();
//! tauri::async_runtime::spawn(async move {
//! match handle.check_for_updates().await {
//! match handle.updater().check().await {
//! Ok(update) => {
//! if update.is_update_available() {
//! update.download_and_install().await.unwrap();
Expand Down Expand Up @@ -499,6 +499,115 @@ struct UpdateManifest {
body: String,
}

/// An update check builder.
#[derive(Debug)]
pub struct UpdateBuilder<R: Runtime> {
inner: core::UpdateBuilder<R>,
events: bool,
}

impl<R: Runtime> UpdateBuilder<R> {
/// Do not use the event system to emit information or listen to install the update.
pub fn skip_events(mut self) -> Self {
self.events = false;
self
}

/// Set the target name. Represents the string that is looked up on the updater API or response JSON.
pub fn target(mut self, target: impl Into<String>) -> Self {
self.inner = self.inner.target(target);
self
}

/// Sets a closure that is invoked to compare the current version and the latest version returned by the updater server.
/// The first argument is the current version, and the second one is the latest version.
///
/// The closure must return `true` if the update should be installed.
///
/// # Examples
///
/// - Always install the version returned by the server:
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// tauri::updater::builder(app.handle()).should_install(|_current, _latest| true);
/// Ok(())
/// });
/// ```
pub fn should_install<F: FnOnce(&str, &str) -> bool + Send + 'static>(mut self, f: F) -> Self {
self.inner = self.inner.should_install(f);
self
}

/// Check if an update is available.
///
/// # Examples
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let handle = app.handle();
/// tauri::async_runtime::spawn(async move {
/// match tauri::updater::builder(handle).check().await {
/// Ok(update) => {}
/// Err(error) => {}
/// }
/// });
/// Ok(())
/// });
/// ```
pub async fn check(self) -> Result<UpdateResponse<R>> {
let handle = self.inner.app.clone();
let events = self.events;
// check updates
match self.inner.build().await {
Ok(update) => {
if events {
// 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 update_ = update.clone();
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
crate::async_runtime::spawn(async move {
let _ = download_and_install(update_).await;
});
});
} else {
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
}
}
Ok(UpdateResponse { update })
}
Err(e) => {
if self.events {
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
}
Err(e)
}
}
}
}

/// The response of an updater check.
pub struct UpdateResponse<R: Runtime> {
update: core::Update<R>,
Expand Down Expand Up @@ -582,7 +691,7 @@ pub(crate) fn listener<R: Runtime>(handle: AppHandle<R>) {
handle.listen_global(EVENT_CHECK_UPDATE, move |_msg| {
let handle_ = handle_.clone();
crate::async_runtime::spawn(async move {
let _ = check(handle_.clone()).await;
let _ = builder(handle_.clone()).check().await;
});
});
}
Expand Down Expand Up @@ -617,7 +726,8 @@ pub(crate) async fn download_and_install<R: Runtime>(update: core::Update<R>) ->
update_result
}

pub(crate) async fn check<R: Runtime>(handle: AppHandle<R>) -> Result<UpdateResponse<R>> {
/// Initializes the [`UpdateBuilder`] using the app configuration.
pub fn builder<R: Runtime>(handle: AppHandle<R>) -> UpdateBuilder<R> {
let updater_config = &handle.config().tauri.updater;
let package_info = handle.package_info().clone();

Expand All @@ -636,47 +746,9 @@ pub(crate) async fn check<R: Runtime>(handle: AppHandle<R>) -> Result<UpdateResp
if let Some(target) = &handle.updater_settings.target {
builder = builder.target(target);
}

// check updates
match builder.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 update_ = update.clone();
handle.once_global(EVENT_INSTALL_UPDATE, move |_msg| {
crate::async_runtime::spawn(async move {
let _ = download_and_install(update_).await;
});
});
} else {
send_status_update(&handle, UpdaterEvent::AlreadyUpToDate);
}
Ok(UpdateResponse { update })
}
Err(e) => {
send_status_update(&handle, UpdaterEvent::Error(e.to_string()));
Err(e)
}
UpdateBuilder {
inner: builder,
events: true,
}
}

Expand Down

0 comments on commit c64268f

Please sign in to comment.