use std::collections::BTreeMap;
use crate::download::hf::HfClient;
use crate::registry::{ModelEntry, ModelId};
use crate::Result;
#[derive(Debug, Clone, Default)]
pub struct UpdateOptions {
pub endpoint: Option<String>,
}
#[derive(Debug, Clone)]
pub struct UpdateInfo {
pub id: ModelId,
pub display_name: String,
pub repo: String,
pub filename: String,
pub latest_revision: String,
pub new_sha256: Option<String>,
pub new_size: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct UpdateCheckError {
pub repo: String,
pub reason: String,
}
#[derive(Debug, Clone, Default)]
pub struct UpdateReport {
pub updates: Vec<UpdateInfo>,
pub errors: Vec<UpdateCheckError>,
}
pub(crate) fn check_updates(client: &HfClient, models: &[ModelEntry]) -> Result<UpdateReport> {
let mut by_repo: BTreeMap<String, Vec<&ModelEntry>> = BTreeMap::new();
for m in models {
if let Some(src) = &m.source {
if src.kind == "huggingface" {
by_repo.entry(src.repo.clone()).or_default().push(m);
}
}
}
let mut report = UpdateReport::default();
for (repo, entries) in by_repo {
let info = match client.repo_info(&repo) {
Ok(i) => i,
Err(e) => {
report.errors.push(UpdateCheckError {
repo,
reason: e.to_string(),
});
continue;
}
};
for m in entries {
let src = m.source.as_ref().expect("filtered above");
let Some(remote) = info.file(&src.filename) else {
continue;
};
let remote_sha = remote.sha256();
let local_sha = m.id.sha256_hex().map(str::to_lowercase);
let changed = match (&remote_sha, &local_sha) {
(Some(r), Some(l)) => r != l,
_ => src
.revision
.as_ref()
.is_some_and(|local_rev| local_rev != &info.sha && remote_sha.is_none()),
};
if changed {
report.updates.push(UpdateInfo {
id: m.id.clone(),
display_name: m.display_name.clone(),
repo: src.repo.clone(),
filename: src.filename.clone(),
latest_revision: info.sha.clone(),
new_sha256: remote_sha.clone(),
new_size: remote.lfs.as_ref().and_then(|l| l.size).or(remote.size),
});
}
}
}
Ok(report)
}