diff --git a/Cargo.lock b/Cargo.lock index 0fdbff28..b9af6a3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,6 +957,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2561,7 +2571,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] @@ -3240,6 +3250,7 @@ dependencies = [ "tracing-subscriber", "tui-big-text", "unicode-segmentation", + "update-informer", "url", ] @@ -3831,6 +3842,39 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "update-informer" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8811797a24ff123db3c6e1087aa42551d03d772b3724be421ad063da1f5f3f" +dependencies = [ + "directories", + "reqwest", + "semver", + "serde", + "serde_json", + "ureq", +] + +[[package]] +name = "ureq" +version = "2.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls 0.22.2", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.1", +] + [[package]] name = "url" version = "2.5.0" @@ -4002,6 +4046,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" diff --git a/crates/synd_term/Cargo.toml b/crates/synd_term/Cargo.toml index 7d03df0a..a86a9f22 100644 --- a/crates/synd_term/Cargo.toml +++ b/crates/synd_term/Cargo.toml @@ -50,6 +50,7 @@ tracing-appender = "0.2.3" tracing-subscriber = { workspace = true } tui-big-text = "0.4.3" unicode-segmentation = "1.10.1" +update-informer = { version = "1.1.0", default-features = false, features = ["crates", "reqwest", "rustls-tls"] } url = { workspace = true } # https://github.com/arkbig/throbber-widgets-tui/pull/5 # throbber-widgets-tui = "0.3.0" diff --git a/crates/synd_term/src/application/mod.rs b/crates/synd_term/src/application/mod.rs index cd80d198..ae01c07a 100644 --- a/crates/synd_term/src/application/mod.rs +++ b/crates/synd_term/src/application/mod.rs @@ -14,6 +14,7 @@ use synd_auth::device_flow::{ }; use synd_feed::types::FeedUrl; use tokio::time::{Instant, Sleep}; +use update_informer::Version; use crate::{ application::event::KeyEventResult, @@ -101,6 +102,7 @@ pub struct Application { config: Config, key_handlers: event::KeyHandlers, categories: Categories, + latest_release: Option, screen: Screen, flags: Should, @@ -139,6 +141,7 @@ impl Application { config, key_handlers, categories, + latest_release: None, flags: Should::empty(), } } @@ -175,6 +178,7 @@ impl Application { pub fn handle_initial_credential(&mut self, cred: Credential) { self.set_credential(cred); self.initial_fetch(); + self.check_latest_release(); self.components.auth.authenticated(); self.keymaps().disable(KeymapId::Login); self.keymaps().enable(KeymapId::Tabs); @@ -212,6 +216,8 @@ impl Application { self.terminal.restore()?; + self.inform_latest_release(); + Ok(()) } @@ -274,6 +280,7 @@ impl Application { // should detect infinite loop ? while let Some(command) = next.take() { match command { + Command::Nop => {} Command::Quit => self.flags.insert(Should::Quit), Command::ResizeTerminal { .. } => { self.should_render(); @@ -564,6 +571,9 @@ impl Application { self.rotate_theme(); self.should_render(); } + Command::InformLatestRelease(version) => { + self.latest_release = Some(version); + } Command::HandleError { message, request_seq, @@ -953,6 +963,42 @@ impl Application { } } +impl Application { + fn check_latest_release(&mut self) { + use update_informer::{registry, Check}; + + // update informer use reqwest::blocking + let check = tokio::task::spawn_blocking(|| { + let name = env!("CARGO_PKG_NAME"); + let version = env!("CARGO_PKG_VERSION"); + #[cfg(not(test))] + let informer = update_informer::new(registry::Crates, name, version) + .interval(Duration::from_secs(60 * 60 * 24)) + .timeout(Duration::from_secs(5)); + + #[cfg(test)] + let informer = update_informer::fake(registry::Crates, name, version, "v1.0.0"); + + informer.check_version().ok().flatten() + }); + let fut = async move { + match check.await { + Ok(Some(version)) => Ok(Command::InformLatestRelease(version)), + _ => Ok(Command::Nop), + } + } + .boxed(); + self.jobs.futures.push(fut); + } + + fn inform_latest_release(&self) { + let current_version = env!("CARGO_PKG_VERSION"); + if let Some(new_version) = &self.latest_release { + println!("A new release of synd is available: v{current_version} -> {new_version}"); + } + } +} + impl Application { fn handle_idle(&mut self) { self.clear_idle_timer(); diff --git a/crates/synd_term/src/command.rs b/crates/synd_term/src/command.rs index d35bb2e1..11f60701 100644 --- a/crates/synd_term/src/command.rs +++ b/crates/synd_term/src/command.rs @@ -14,6 +14,7 @@ use crate::{ #[derive(Debug, Clone)] pub(crate) enum Command { + Nop, Quit, ResizeTerminal { _columns: u16, @@ -103,6 +104,9 @@ pub(crate) enum Command { // Theme RotateTheme, + // Latest release check + InformLatestRelease(update_informer::Version), + HandleError { message: String, request_seq: Option,