From 70fdce40c8305ff14986c835629d08c745d4a7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Misty=20De=20M=C3=A9o?= Date: Wed, 6 Mar 2024 14:55:04 +1100 Subject: [PATCH] feat: use async APIs by default Turns run() and is_update_needed() into asynchronous APIs by default, with synchronous APIs provided as alternatives. --- README.md | 18 ++++-- axoupdater-cli/Cargo.toml | 2 +- axoupdater-cli/src/bin/axoupdater/main.rs | 2 +- axoupdater/Cargo.toml | 2 +- axoupdater/src/lib.rs | 79 ++++++++++++++++------- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ad22789..bd62949 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ axoupdater can also be used as a library within your own applications in order t To check for updates and notify the user: ```rust -if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed()? { +if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed_sync()? { eprintln!("axolotlsay is outdated; please upgrade!"); } ``` @@ -25,7 +25,17 @@ if AxoUpdater::new_for("axolotlsay").load_receipt()?.is_update_needed()? { To automatically perform an update if the program isn't up to date: ```rust -if AxoUpdater::new_for("axolotlsay").load_receipt()?.run()? { +if AxoUpdater::new_for("axolotlsay").load_receipt()?.run_sync()? { + eprintln!("Update installed!"); +} else { + eprintln!("axolotlsay already up to date"); +} +``` + +Asynchronous versions of `is_update_needed()` and `run()` are also provided: + +```rust +if AxoUpdater::new_for("axolotlsay").load_receipt()?.run().await? { eprintln!("Update installed!"); } else { eprintln!("axolotlsay already up to date"); @@ -47,7 +57,7 @@ To build as a standalone binary, follow these steps: Licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)) +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)) at your option. diff --git a/axoupdater-cli/Cargo.toml b/axoupdater-cli/Cargo.toml index f42ed5a..570680f 100644 --- a/axoupdater-cli/Cargo.toml +++ b/axoupdater-cli/Cargo.toml @@ -12,7 +12,7 @@ readme = "../README.md" [dependencies] axocli = "0.2.0" -axoupdater = { version = "=0.1.0", path = "../axoupdater" } +axoupdater = { version = "=0.1.0", path = "../axoupdater", features = ["blocking"] } # errors miette = "7.1.0" diff --git a/axoupdater-cli/src/bin/axoupdater/main.rs b/axoupdater-cli/src/bin/axoupdater/main.rs index 8bd4316..94546f9 100644 --- a/axoupdater-cli/src/bin/axoupdater/main.rs +++ b/axoupdater-cli/src/bin/axoupdater/main.rs @@ -8,7 +8,7 @@ fn real_main(_cli: &CliApp) -> Result<(), miette::Report> { if AxoUpdater::new_for_updater_executable()? .load_receipt()? - .run()? + .run_sync()? { eprintln!("New release installed!") } else { diff --git a/axoupdater/Cargo.toml b/axoupdater/Cargo.toml index 4268a71..0f66e0b 100644 --- a/axoupdater/Cargo.toml +++ b/axoupdater/Cargo.toml @@ -17,6 +17,7 @@ path = "src/lib.rs" [features] default = ["axo_releases", "github_releases"] axo_releases = ["gazenot", "tokio"] +blocking = ["tokio"] github_releases = ["reqwest"] [dependencies] @@ -35,7 +36,6 @@ tokio = { version = "1.36.0", features = ["full"], optional = true } # github releases reqwest = { version = "0.11", default-features = false, features = [ - "blocking", "rustls-tls", "json", ], optional = true } diff --git a/axoupdater/src/lib.rs b/axoupdater/src/lib.rs index bdd298a..a726647 100644 --- a/axoupdater/src/lib.rs +++ b/axoupdater/src/lib.rs @@ -118,7 +118,7 @@ impl AxoUpdater { /// This can only be performed if the `current_version` field has been /// set, either by loading the install receipt or by specifying it using /// `set_current_version`. - pub fn is_update_needed(&mut self) -> AxoupdateResult { + pub async fn is_update_needed(&mut self) -> AxoupdateResult { let Some(current_version) = self.current_version.to_owned() else { return Err(AxoupdateError::NotConfigured { missing_field: "current_version".to_owned(), @@ -128,7 +128,7 @@ impl AxoUpdater { let release = match &self.latest_release { Some(r) => r, None => { - self.fetch_latest_release()?; + self.fetch_latest_release().await?; self.latest_release.as_ref().unwrap() } }; @@ -136,19 +136,30 @@ impl AxoUpdater { Ok(current_version != release.version()) } + /// Identical to Axoupdater::is_update_needed(), but performed synchronously. + pub fn is_update_needed_sync(&mut self) -> AxoupdateResult { + tokio::runtime::Builder::new_current_thread() + .worker_threads(1) + .max_blocking_threads(128) + .enable_all() + .build() + .expect("Initializing tokio runtime failed") + .block_on(self.is_update_needed()) + } + /// Attempts to perform an update. The return value specifies whether an /// update was actually performed or not; false indicates "no update was /// needed", while an error indicates that an update couldn't be performed /// due to an error. - pub fn run(&mut self) -> AxoupdateResult { - if !self.is_update_needed()? { + pub async fn run(&mut self) -> AxoupdateResult { + if !self.is_update_needed().await? { return Ok(false); } let release = match &self.latest_release { Some(r) => r, None => { - self.fetch_latest_release()?; + self.fetch_latest_release().await?; self.latest_release.as_ref().unwrap() } }; @@ -184,12 +195,14 @@ impl AxoUpdater { installer_file.set_permissions(perms)?; } - let client = reqwest::blocking::Client::new(); + let client = reqwest::Client::new(); let download = client .get(&installer_url.browser_download_url) .header(ACCEPT, "application/octet-stream") - .send()? - .text()?; + .send() + .await? + .text() + .await?; LocalAsset::write_new_all(&download, &installer_path)?; @@ -207,7 +220,18 @@ impl AxoUpdater { Ok(true) } - fn fetch_latest_release(&mut self) -> AxoupdateResult<()> { + /// Identical to Axoupdater::run(), but performed synchronously. + pub fn run_sync(&mut self) -> AxoupdateResult { + tokio::runtime::Builder::new_current_thread() + .worker_threads(1) + .max_blocking_threads(128) + .enable_all() + .build() + .expect("Initializing tokio runtime failed") + .block_on(self.run()) + } + + async fn fetch_latest_release(&mut self) -> AxoupdateResult<()> { let Some(app_name) = &self.name else { return Err(AxoupdateError::NotConfigured { missing_field: "app_name".to_owned(), @@ -224,7 +248,8 @@ impl AxoUpdater { &source.owner, &source.app_name, &source.release_type, - )? + ) + .await? else { return Err(AxoupdateError::NoStableReleases { app_name: app_name.to_owned(), @@ -451,8 +476,12 @@ pub struct InstallReceipt { } #[cfg(feature = "github_releases")] -fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult> { - let client = reqwest::blocking::Client::new(); +async fn get_github_releases( + name: &str, + owner: &str, + app_name: &str, +) -> AxoupdateResult> { + let client = reqwest::Client::new(); let resp: Vec = client .get(format!("{GITHUB_API}/repos/{owner}/{name}/releases")) .header(ACCEPT, "application/json") @@ -460,8 +489,10 @@ fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResu USER_AGENT, format!("axoupdate/{}", env!("CARGO_PKG_VERSION")), ) - .send()? - .json()?; + .send() + .await? + .json() + .await?; Ok(resp .into_iter() @@ -474,15 +505,13 @@ fn get_github_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResu } #[cfg(feature = "axo_releases")] -fn get_axo_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult> { +async fn get_axo_releases( + name: &str, + owner: &str, + app_name: &str, +) -> AxoupdateResult> { let abyss = Gazenot::new_unauthed("github".to_string(), owner)?; - let release_lists = tokio::runtime::Builder::new_current_thread() - .worker_threads(1) - .max_blocking_threads(128) - .enable_all() - .build() - .expect("Initializing tokio runtime failed") - .block_on(abyss.list_releases_many(vec![app_name.to_owned()]))?; + let release_lists = abyss.list_releases_many(vec![app_name.to_owned()]).await?; let Some(our_release) = release_lists.iter().find(|rl| rl.package_name == app_name) else { return Err(AxoupdateError::ReleaseNotFound { name: name.to_owned(), @@ -501,7 +530,7 @@ fn get_axo_releases(name: &str, owner: &str, app_name: &str) -> AxoupdateResult< Ok(releases) } -fn get_latest_stable_release( +async fn get_latest_stable_release( name: &str, owner: &str, app_name: &str, @@ -509,7 +538,7 @@ fn get_latest_stable_release( ) -> AxoupdateResult> { let releases = match release_type { #[cfg(feature = "github_releases")] - ReleaseSourceType::GitHub => get_github_releases(name, owner, app_name)?, + ReleaseSourceType::GitHub => get_github_releases(name, owner, app_name).await?, #[cfg(not(feature = "github_releases"))] ReleaseSourceType::GitHub => { return Err(AxoupdateError::BackendDisabled { @@ -517,7 +546,7 @@ fn get_latest_stable_release( }) } #[cfg(feature = "axo_releases")] - ReleaseSourceType::Axo => get_axo_releases(name, owner, app_name)?, + ReleaseSourceType::Axo => get_axo_releases(name, owner, app_name).await?, #[cfg(not(feature = "axo_releases"))] ReleaseSourceType::Axo => { return Err(AxoupdateError::BackendDisabled {