From 0ddf51f4bdb281be37cdf3dbfca833ee4b53934b Mon Sep 17 00:00:00 2001 From: wuyangji <694410194@qq.com> Date: Thu, 14 May 2026 11:02:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(update):=20=E9=81=BF=E5=85=8D=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E6=8F=90=E7=A4=BA=E5=90=8C=E7=89=88=E6=9C=AC=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cortex-cli/src/main.rs | 2 +- .../src/runner/app_runner/runner.rs | 2 +- src/cortex-update/src/config.rs | 61 +++++++++++++++++++ src/cortex-update/src/manager.rs | 31 ++++++++-- 4 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/cortex-cli/src/main.rs b/src/cortex-cli/src/main.rs index fce9e7216..08b2627b5 100644 --- a/src/cortex-cli/src/main.rs +++ b/src/cortex-cli/src/main.rs @@ -110,7 +110,7 @@ fn check_cortex_home_writable() -> Result<()> { async fn check_for_updates_background() { // Use cortex_update crate for update checking // This runs asynchronously and doesn't block the main command - if let Ok(manager) = cortex_update::UpdateManager::new() + if let Ok(mut manager) = cortex_update::UpdateManager::new() && let Ok(Some(update_info)) = manager.check_update().await { eprintln!( diff --git a/src/cortex-tui/src/runner/app_runner/runner.rs b/src/cortex-tui/src/runner/app_runner/runner.rs index e79ac3746..c5c6c28a8 100644 --- a/src/cortex-tui/src/runner/app_runner/runner.rs +++ b/src/cortex-tui/src/runner/app_runner/runner.rs @@ -557,7 +557,7 @@ impl AppRunner { // 2. Background update check task - check for new versions without blocking startup let update_check_task = tokio::spawn(async move { match UpdateManager::new() { - Ok(manager) => match manager.check_update().await { + Ok(mut manager) => match manager.check_update().await { Ok(info) => info, Err(e) => { tracing::debug!("Update check failed: {}", e); diff --git a/src/cortex-update/src/config.rs b/src/cortex-update/src/config.rs index b352ae68a..7de66091a 100644 --- a/src/cortex-update/src/config.rs +++ b/src/cortex-update/src/config.rs @@ -145,4 +145,65 @@ impl UpdateConfig { pub fn is_version_skipped(&self, version: &str) -> bool { self.skip_version.as_deref() == Some(version) } + + /// Check if this version has already been surfaced to the user. + pub fn is_version_notified(&self, version: &str) -> bool { + self.last_notified_version.as_deref() == Some(version) + } + + /// Persist the latest version that has already been surfaced to the user. + pub fn mark_version_notified(&mut self, version: &str) -> Result<(), std::io::Error> { + if self.is_version_notified(version) { + return Ok(()); + } + + self.last_notified_version = Some(version.to_string()); + self.save() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::OsString; + use std::sync::Mutex; + + static HOME_LOCK: Mutex<()> = Mutex::new(()); + + struct HomeGuard(Option); + + impl Drop for HomeGuard { + fn drop(&mut self) { + if let Some(original) = self.0.take() { + unsafe { std::env::set_var("HOME", original) }; + } else { + unsafe { std::env::remove_var("HOME") }; + } + } + } + + #[test] + fn test_is_version_notified() { + let config = UpdateConfig { + last_notified_version: Some("0.9.0".to_string()), + ..UpdateConfig::default() + }; + + assert!(config.is_version_notified("0.9.0")); + assert!(!config.is_version_notified("0.9.1")); + } + + #[test] + fn test_mark_version_notified_persists() { + let _lock = HOME_LOCK.lock().unwrap(); + let _guard = HomeGuard(std::env::var_os("HOME")); + let temp_home = tempfile::tempdir().unwrap(); + unsafe { std::env::set_var("HOME", temp_home.path()) }; + + let mut config = UpdateConfig::default(); + config.mark_version_notified("1.2.3").unwrap(); + + let loaded = UpdateConfig::load(); + assert_eq!(loaded.last_notified_version.as_deref(), Some("1.2.3")); + } } diff --git a/src/cortex-update/src/manager.rs b/src/cortex-update/src/manager.rs index 9b7f2a034..3b96665d5 100644 --- a/src/cortex-update/src/manager.rs +++ b/src/cortex-update/src/manager.rs @@ -84,21 +84,34 @@ impl UpdateManager { } /// Check if an update is available (uses cache if valid). - pub async fn check_update(&self) -> UpdateResult> { + pub async fn check_update(&mut self) -> UpdateResult> { // Try to use cache first if let Some(cache) = VersionCache::load() { if cache.is_valid(&self.config) && cache.has_update() && !self.config.is_version_skipped(&cache.latest.version) + && !self.config.is_version_notified(&cache.latest.version) { - return Ok(Some(self.build_update_info(&cache.latest)?)); + let info = self.build_update_info(&cache.latest)?; + self.record_notification(&info.latest_version); + return Ok(Some(info)); } else if cache.is_valid(&self.config) { return Ok(None); } } // Cache invalid or missing, check server - self.check_update_forced().await + let info = self.check_update_forced().await?; + if let Some(info) = info { + if self.config.is_version_notified(&info.latest_version) { + return Ok(None); + } + + self.record_notification(&info.latest_version); + Ok(Some(info)) + } else { + Ok(None) + } } /// Force check for updates (bypass cache). @@ -188,7 +201,7 @@ impl UpdateManager { F: FnMut(DownloadProgress), { // Check for update - let info = match self.check_update().await? { + let info = match self.check_update_forced().await? { Some(info) => info, None => return Ok(UpdateOutcome::AlreadyLatest), }; @@ -220,6 +233,16 @@ impl UpdateManager { })?; Ok(()) } + + fn record_notification(&mut self, version: &str) { + if let Err(e) = self.config.mark_version_notified(version) { + tracing::warn!( + "Failed to persist last_notified_version for {}: {}", + version, + e + ); + } + } } #[cfg(test)]