use std::collections::HashMap;
use anyhow::Result;
use modde_core::NexusModId;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use super::api::NexusApi;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModUpdate {
pub mod_id: String,
pub nexus_mod_id: NexusModId,
pub installed_version: Option<String>,
pub installed_timestamp: i64,
pub latest_file_update: u64,
pub latest_mod_activity: u64,
}
#[derive(Debug, Clone)]
pub struct TrackedMod {
pub mod_id: String,
pub nexus_mod_id: NexusModId,
pub nexus_game_domain: String,
pub installed_version: Option<String>,
pub installed_timestamp: i64,
}
pub async fn check_updates(
api: &NexusApi,
tracked: &[TrackedMod],
period: &str,
) -> Result<Vec<ModUpdate>> {
if tracked.is_empty() {
return Ok(Vec::new());
}
let mut by_domain: HashMap<&str, Vec<&TrackedMod>> = HashMap::new();
for t in tracked {
by_domain
.entry(t.nexus_game_domain.as_str())
.or_default()
.push(t);
}
let mut updates = Vec::new();
for (domain, domain_mods) in &by_domain {
debug!(
domain,
mod_count = domain_mods.len(),
"checking updates for domain"
);
let lookup: HashMap<NexusModId, &&TrackedMod> =
domain_mods.iter().map(|m| (m.nexus_mod_id, m)).collect();
let updated = api.updated_mods(domain, period).await?;
for nexus_mod in &updated {
if let Some(tracked_mod) = lookup.get(&nexus_mod.mod_id) {
let install_ts = tracked_mod.installed_timestamp as u64;
if nexus_mod.latest_file_update > install_ts {
updates.push(ModUpdate {
mod_id: tracked_mod.mod_id.clone(),
nexus_mod_id: nexus_mod.mod_id,
installed_version: tracked_mod.installed_version.clone(),
installed_timestamp: tracked_mod.installed_timestamp,
latest_file_update: nexus_mod.latest_file_update,
latest_mod_activity: nexus_mod.latest_mod_activity,
});
}
}
}
}
if updates.is_empty() {
info!("all tracked mods are up to date");
} else {
info!(count = updates.len(), "found mods with available updates");
}
Ok(updates)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tracked_mod_grouping() {
let tracked = vec![
TrackedMod {
mod_id: "skyui".to_string(),
nexus_mod_id: NexusModId::from(12604),
nexus_game_domain: "skyrimspecialedition".to_string(),
installed_version: Some("5.2".to_string()),
installed_timestamp: 1700000000,
},
TrackedMod {
mod_id: "ussep".to_string(),
nexus_mod_id: NexusModId::from(266),
nexus_game_domain: "skyrimspecialedition".to_string(),
installed_version: Some("4.2.8".to_string()),
installed_timestamp: 1700000000,
},
TrackedMod {
mod_id: "fo4_patch".to_string(),
nexus_mod_id: NexusModId::from(4598),
nexus_game_domain: "fallout4".to_string(),
installed_version: None,
installed_timestamp: 1700000000,
},
];
let mut by_domain: HashMap<&str, Vec<&TrackedMod>> = HashMap::new();
for t in &tracked {
by_domain
.entry(t.nexus_game_domain.as_str())
.or_default()
.push(t);
}
assert_eq!(by_domain.len(), 2);
assert_eq!(by_domain["skyrimspecialedition"].len(), 2);
assert_eq!(by_domain["fallout4"].len(), 1);
}
}