use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::SystemTime;
use crate::core::graph_index::ProjectIndex;
#[derive(Clone, Copy, PartialEq, Eq, Default)]
struct Fingerprint {
mtime: Option<SystemTime>,
size: u64,
}
struct Entry {
index: Arc<ProjectIndex>,
fingerprint: Fingerprint,
}
static CACHE: OnceLock<Mutex<HashMap<String, Entry>>> = OnceLock::new();
fn cache() -> &'static Mutex<HashMap<String, Entry>> {
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
fn index_fingerprint(project_root: &str) -> Fingerprint {
let Some(dir) = ProjectIndex::index_dir(project_root) else {
return Fingerprint::default();
};
for name in ["index.json.zst", "index.json"] {
if let Ok(meta) = std::fs::metadata(dir.join(name)) {
return Fingerprint {
mtime: meta.modified().ok(),
size: meta.len(),
};
}
}
Fingerprint::default()
}
pub fn get_cached(project_root: &str) -> Option<Arc<ProjectIndex>> {
let fingerprint = index_fingerprint(project_root);
{
let map = cache()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(entry) = map.get(project_root) {
if entry.fingerprint == fingerprint {
return Some(Arc::clone(&entry.index));
}
}
}
let idx = ProjectIndex::load(project_root).filter(|i| !i.files.is_empty())?;
let arc = Arc::new(idx);
let mut map = cache()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
map.insert(
project_root.to_string(),
Entry {
index: Arc::clone(&arc),
fingerprint,
},
);
Some(arc)
}
pub fn invalidate(project_root: Option<&str>) {
let mut map = cache()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
match project_root {
Some(root) => {
map.remove(root);
}
None => map.clear(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_none_without_index() {
let tmp = tempfile::tempdir().unwrap();
invalidate(None);
assert!(get_cached(tmp.path().to_str().unwrap()).is_none());
}
}