Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cortex-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
2 changes: 1 addition & 1 deletion src/cortex-tui/src/runner/app_runner/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
61 changes: 61 additions & 0 deletions src/cortex-update/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<OsString>);

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"));
}
}
31 changes: 27 additions & 4 deletions src/cortex-update/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,34 @@ impl UpdateManager {
}

/// Check if an update is available (uses cache if valid).
pub async fn check_update(&self) -> UpdateResult<Option<UpdateInfo>> {
pub async fn check_update(&mut self) -> UpdateResult<Option<UpdateInfo>> {
// 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).
Expand Down Expand Up @@ -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),
};
Expand Down Expand Up @@ -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)]
Expand Down