From 19f35d9bf104874d23cbcefd460fb7148a5e74ad Mon Sep 17 00:00:00 2001 From: mtkennerly Date: Wed, 31 Jul 2024 23:19:33 -0400 Subject: [PATCH] #361: Add API message to check for app update --- CHANGELOG.md | 1 + src/cli/api.rs | 36 ++++++++++++++++++++++++++++++++++++ src/gui/app.rs | 6 ++---- src/metadata.rs | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fad7e..201346a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and you can click the icon to display the notes. The primary manifest does not (yet) contain any notes, so this mainly applies to secondary manifest authors. + * CLI: The `api` command now supports a `checkAppUpdate` message. * Fixed: * CLI: Some commands would fail with relative path arguments. * Changed: diff --git a/src/cli/api.rs b/src/cli/api.rs index e1cdf3a..86506a6 100644 --- a/src/cli/api.rs +++ b/src/cli/api.rs @@ -46,6 +46,7 @@ pub enum Output { #[serde(rename_all = "camelCase")] pub enum Request { FindTitle(request::FindTitle), + CheckAppUpdate(request::CheckAppUpdate), } /// A response to an individual request. @@ -54,6 +55,7 @@ pub enum Request { pub enum Response { Error(response::Error), FindTitle(response::FindTitle), + CheckAppUpdate(response::CheckAppUpdate), } pub mod request { @@ -91,6 +93,11 @@ pub mod request { /// With multiple values, they will be checked in the order given. pub names: Vec, } + + /// Check whether an application update is available. + #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + #[serde(default, rename_all = "camelCase")] + pub struct CheckAppUpdate {} } pub mod response { @@ -109,6 +116,22 @@ pub mod response { /// Any matching titles found. pub titles: BTreeSet, } + + #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + #[serde(default, rename_all = "camelCase")] + pub struct CheckAppUpdate { + /// An available update. + pub update: Option, + } + + #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] + #[serde(default, rename_all = "camelCase")] + pub struct AppUpdate { + /// New version number. + pub version: String, + /// Release URL to open in browser. + pub url: String, + } } fn parse_input(input: Option) -> Result { @@ -186,6 +209,19 @@ pub fn process(input: Option, config: &Config, manifest: &Manifest) -> R responses.push(Response::FindTitle(response::FindTitle { titles })); } + Request::CheckAppUpdate(request::CheckAppUpdate {}) => match crate::metadata::Release::fetch_sync() { + Ok(release) => { + let update = release.is_update().then(|| response::AppUpdate { + version: release.version.to_string(), + url: release.url, + }); + + responses.push(Response::CheckAppUpdate(response::CheckAppUpdate { update })); + } + Err(e) => { + responses.push(Response::Error(response::Error { message: e.to_string() })); + } + }, } } diff --git a/src/gui/app.rs b/src/gui/app.rs index d04cc44..69f62a3 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -1362,10 +1362,8 @@ impl Application for App { if previous_latest.as_ref() != Some(&release.version) { // The latest available version has changed (or this is our first time checking) - if let Ok(current) = semver::Version::parse(*crate::VERSION) { - if release.version > current { - return self.show_modal(Modal::AppUpdate { release }); - } + if release.is_update() { + return self.show_modal(Modal::AppUpdate { release }); } } } diff --git a/src/metadata.rs b/src/metadata.rs index ce70092..0016fe7 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -33,4 +33,39 @@ impl Release { code => Err(format!("status code: {code:?}").into()), } } + + pub fn fetch_sync() -> Result { + #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] + pub struct Response { + pub html_url: String, + pub tag_name: String, + } + + let req = reqwest::blocking::Client::new() + .get(Self::URL) + .header(reqwest::header::USER_AGENT, &*crate::prelude::USER_AGENT); + let res = req.send()?; + + match res.status() { + reqwest::StatusCode::OK => { + let bytes = res.bytes()?.to_vec(); + let raw = String::from_utf8(bytes)?; + let parsed = serde_json::from_str::(&raw)?; + + Ok(Self { + version: semver::Version::parse(parsed.tag_name.trim_start_matches('v'))?, + url: parsed.html_url, + }) + } + code => Err(format!("status code: {code:?}").into()), + } + } + + pub fn is_update(&self) -> bool { + if let Ok(current) = semver::Version::parse(*crate::VERSION) { + self.version > current + } else { + false + } + } }