use std::collections::HashMap;
use std::path::Path;
use std::sync::Mutex;
use std::time::SystemTime;
use crate::records::{ModelRecord, SourceKind};
use crate::resolution::identity::{IdentifiedModel, identify as run_identify};
struct Entry {
mtime: SystemTime,
size: i64,
identified: IdentifiedModel,
}
#[derive(Default)]
struct CacheState {
entries: HashMap<String, Entry>,
hits: usize,
}
#[derive(Default)]
pub struct IdentificationCache {
state: Mutex<CacheState>,
}
impl IdentificationCache {
pub fn new() -> Self {
Self::default()
}
pub fn hit_count(&self) -> usize {
self.state.lock().map_or(0, |state| state.hits)
}
pub fn identify(&self, record: &ModelRecord) -> IdentifiedModel {
if is_uncached(&record.source.kind) {
return run_identify(record);
}
let key = cache_key(record);
let Some((mtime, size)) = freshness_signature(Path::new(&record.source.path)) else {
return run_identify(record);
};
if let Ok(mut state) = self.state.lock()
&& let Some(entry) = state.entries.get(&key)
&& entry.mtime == mtime
&& entry.size == size
{
let identified = entry.identified.clone();
state.hits += 1;
return identified;
}
let identified = run_identify(record);
if let Ok(mut state) = self.state.lock() {
state.entries.insert(
key,
Entry {
mtime,
size,
identified: identified.clone(),
},
);
}
identified
}
}
fn is_uncached(kind: &SourceKind) -> bool {
matches!(kind.as_str(), "builtin" | "endpoint" | "ollama")
}
fn cache_key(record: &ModelRecord) -> String {
format!(
"{}\u{1f}{}\u{1f}{}",
record.source.path,
record.source.reference.as_deref().unwrap_or(""),
record.source.repo.as_deref().unwrap_or(""),
)
}
fn freshness_signature(path: &Path) -> Option<(SystemTime, i64)> {
let metadata = std::fs::metadata(path).ok()?;
let mtime = metadata.modified().ok()?;
let size = metadata.len() as i64;
if !metadata.is_dir() {
return Some((mtime, size));
}
let mut latest = mtime;
let mut total = size;
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
let Ok(child) = entry.metadata() else {
continue;
};
if let Ok(child_mtime) = child.modified()
&& child_mtime > latest
{
latest = child_mtime;
}
total = total.wrapping_add(child.len() as i64);
}
}
Some((latest, total))
}