use anyhow::Result;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::CodeGraph;
pub const STALE_THRESHOLD_SECS: i64 = 300;
#[derive(Debug, Clone)]
pub struct FreshnessStatus {
is_stale: bool,
seconds_since_index: i64,
file_count: usize,
}
impl FreshnessStatus {
pub fn new(is_stale: bool, seconds_since_index: i64, file_count: usize) -> Self {
Self {
is_stale,
seconds_since_index,
file_count,
}
}
pub fn is_stale(&self) -> bool {
self.is_stale
}
pub fn minutes_since_index(&self) -> i64 {
self.seconds_since_index / 60
}
pub fn seconds_since_index(&self) -> i64 {
self.seconds_since_index
}
pub fn file_count(&self) -> usize {
self.file_count
}
pub fn warning_message(
&self,
db_path: std::path::PathBuf,
root_path: std::path::PathBuf,
) -> String {
let mins = self.minutes_since_index();
let db_str = db_path.to_string_lossy();
let root_str = root_path.to_string_lossy();
format!(
"WARNING: Database may be stale (last indexed {} minutes ago)\n Run 'magellan verify --db {} --root {}' to check\n Consider running 'magellan watch' for automatic updates",
mins, db_str, root_str
)
}
}
fn now_secs() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64
}
pub fn check_freshness(graph: &CodeGraph) -> Result<FreshnessStatus> {
let file_nodes = graph.files.all_file_nodes_readonly()?;
let file_count = file_nodes.len();
if file_count == 0 {
return Ok(FreshnessStatus::new(false, 0, 0));
}
let mut max_indexed_at: i64 = 0;
for file_node in file_nodes.values() {
if file_node.last_indexed_at > max_indexed_at {
max_indexed_at = file_node.last_indexed_at;
}
}
let now = now_secs();
let seconds_since = if max_indexed_at > 0 {
now.saturating_sub(max_indexed_at)
} else {
i64::MAX
};
let is_stale = seconds_since >= STALE_THRESHOLD_SECS;
Ok(FreshnessStatus::new(is_stale, seconds_since, file_count))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_freshness_status_constants() {
assert_eq!(STALE_THRESHOLD_SECS, 300);
}
#[test]
fn test_freshness_status_methods() {
let status = FreshnessStatus::new(true, 600, 10);
assert!(status.is_stale());
assert_eq!(status.minutes_since_index(), 10);
assert_eq!(status.seconds_since_index(), 600);
assert_eq!(status.file_count(), 10);
}
#[test]
fn test_warning_message_format() {
let status = FreshnessStatus::new(true, 600, 10);
let msg = status.warning_message(
std::path::PathBuf::from("/path/to/db"),
std::path::PathBuf::from("/path/to/root"),
);
assert!(msg.contains("WARNING"));
assert!(msg.contains("10 minutes"));
assert!(msg.contains("magellan verify"));
assert!(msg.contains("magellan watch"));
}
}