modde_sources/nexus/
updates.rs1use std::collections::HashMap;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use tracing::{debug, info};
6
7use super::api::NexusApi;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ModUpdate {
12 pub mod_id: String,
14 pub nexus_mod_id: u64,
16 pub installed_version: Option<String>,
18 pub installed_timestamp: i64,
20 pub latest_file_update: u64,
22 pub latest_mod_activity: u64,
24}
25
26#[derive(Debug, Clone)]
28pub struct TrackedMod {
29 pub mod_id: String,
30 pub nexus_mod_id: u64,
31 pub nexus_game_domain: String,
32 pub installed_version: Option<String>,
33 pub installed_timestamp: i64,
34}
35
36pub async fn check_updates(
41 api: &NexusApi,
42 tracked: &[TrackedMod],
43 period: &str,
44) -> Result<Vec<ModUpdate>> {
45 if tracked.is_empty() {
46 return Ok(Vec::new());
47 }
48
49 let mut by_domain: HashMap<&str, Vec<&TrackedMod>> = HashMap::new();
51 for t in tracked {
52 by_domain.entry(t.nexus_game_domain.as_str()).or_default().push(t);
53 }
54
55 let mut updates = Vec::new();
56
57 for (domain, domain_mods) in &by_domain {
58 debug!(domain, mod_count = domain_mods.len(), "checking updates for domain");
59
60 let lookup: HashMap<u64, &&TrackedMod> = domain_mods
62 .iter()
63 .map(|m| (m.nexus_mod_id, m))
64 .collect();
65
66 let updated = api.updated_mods(domain, period).await?;
68
69 for nexus_mod in &updated {
70 if let Some(tracked_mod) = lookup.get(&nexus_mod.mod_id) {
71 let install_ts = tracked_mod.installed_timestamp as u64;
73 if nexus_mod.latest_file_update > install_ts {
74 updates.push(ModUpdate {
75 mod_id: tracked_mod.mod_id.clone(),
76 nexus_mod_id: nexus_mod.mod_id,
77 installed_version: tracked_mod.installed_version.clone(),
78 installed_timestamp: tracked_mod.installed_timestamp,
79 latest_file_update: nexus_mod.latest_file_update,
80 latest_mod_activity: nexus_mod.latest_mod_activity,
81 });
82 }
83 }
84 }
85 }
86
87 if updates.is_empty() {
88 info!("all tracked mods are up to date");
89 } else {
90 info!(count = updates.len(), "found mods with available updates");
91 }
92
93 Ok(updates)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_tracked_mod_grouping() {
102 let tracked = vec![
103 TrackedMod {
104 mod_id: "skyui".to_string(),
105 nexus_mod_id: 12604,
106 nexus_game_domain: "skyrimspecialedition".to_string(),
107 installed_version: Some("5.2".to_string()),
108 installed_timestamp: 1700000000,
109 },
110 TrackedMod {
111 mod_id: "ussep".to_string(),
112 nexus_mod_id: 266,
113 nexus_game_domain: "skyrimspecialedition".to_string(),
114 installed_version: Some("4.2.8".to_string()),
115 installed_timestamp: 1700000000,
116 },
117 TrackedMod {
118 mod_id: "fo4_patch".to_string(),
119 nexus_mod_id: 4598,
120 nexus_game_domain: "fallout4".to_string(),
121 installed_version: None,
122 installed_timestamp: 1700000000,
123 },
124 ];
125
126 let mut by_domain: HashMap<&str, Vec<&TrackedMod>> = HashMap::new();
127 for t in &tracked {
128 by_domain.entry(t.nexus_game_domain.as_str()).or_default().push(t);
129 }
130
131 assert_eq!(by_domain.len(), 2);
132 assert_eq!(by_domain["skyrimspecialedition"].len(), 2);
133 assert_eq!(by_domain["fallout4"].len(), 1);
134 }
135}