wows_data_mgr/
registry.rs1use std::collections::BTreeMap;
2use std::path::Path;
3use std::path::PathBuf;
4
5use rootcause::prelude::*;
6use serde::Deserialize;
7use serde::Serialize;
8
9#[derive(Debug, Default, Serialize, Deserialize)]
10pub struct LocalRegistry {
11 #[serde(skip_serializing_if = "Option::is_none")]
14 pub latest_path: Option<PathBuf>,
15 #[serde(default)]
16 pub builds: BTreeMap<String, LocalBuildEntry>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct LocalBuildEntry {
21 pub version: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub downloaded_at: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub registered_at: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub path: Option<PathBuf>,
28}
29
30impl LocalRegistry {
31 pub fn has_build(&self, build: u32) -> bool {
32 self.builds.contains_key(&build.to_string())
33 }
34
35 pub fn get(&self, build: u32) -> Option<&LocalBuildEntry> {
36 self.builds.get(&build.to_string())
37 }
38
39 pub fn set_downloaded(&mut self, build: u32, version: &str) {
40 let now = jiff::Zoned::now();
41 let timestamp = now.strftime("%Y-%m-%dT%H:%M:%S%:z").to_string();
42 self.builds.insert(
43 build.to_string(),
44 LocalBuildEntry {
45 version: version.to_string(),
46 downloaded_at: Some(timestamp),
47 registered_at: None,
48 path: None,
49 },
50 );
51 }
52
53 pub fn set_registered(&mut self, build: u32, version: &str, path: &Path) {
54 let now = jiff::Zoned::now();
55 let timestamp = now.strftime("%Y-%m-%dT%H:%M:%S%:z").to_string();
56 self.builds.insert(
57 build.to_string(),
58 LocalBuildEntry {
59 version: version.to_string(),
60 downloaded_at: None,
61 registered_at: Some(timestamp),
62 path: Some(path.to_path_buf()),
63 },
64 );
65 }
66
67 #[allow(dead_code)]
71 pub fn available_builds(&self) -> Vec<u32> {
72 let mut builds: Vec<u32> = self.builds.keys().filter_map(|k| k.parse::<u32>().ok()).collect();
73
74 if let Some(ref latest) = self.latest_path
75 && let Ok(latest_builds) = wowsunpack::game_data::list_available_builds(latest)
76 {
77 for b in latest_builds {
78 if !builds.contains(&b) {
79 builds.push(b);
80 }
81 }
82 }
83
84 builds.sort();
85 builds
86 }
87
88 #[allow(dead_code)]
91 pub fn game_dir_for_build(&self, build: u32, data_dir: &Path) -> Option<PathBuf> {
92 if let Some(entry) = self.get(build) {
94 if let Some(ref path) = entry.path
97 && path.exists()
98 {
99 return Some(path.clone());
100 }
101 let dir = data_dir.join("builds").join(build.to_string());
103 if dir.exists() {
104 return Some(dir);
105 }
106 }
107
108 if let Some(ref latest) = self.latest_path
110 && let Ok(builds) = wowsunpack::game_data::list_available_builds(latest)
111 && builds.contains(&build)
112 {
113 return Some(latest.clone());
114 }
115
116 let dir = data_dir.join("builds").join(build.to_string());
118 if dir.exists() { Some(dir) } else { None }
119 }
120}
121
122pub fn load_registry(path: &Path) -> LocalRegistry {
123 if !path.exists() {
124 return LocalRegistry::default();
125 }
126 let content = match std::fs::read_to_string(path) {
127 Ok(c) => c,
128 Err(_) => return LocalRegistry::default(),
129 };
130 toml::from_str(&content).unwrap_or_default()
131}
132
133pub fn save_registry(registry: &LocalRegistry, path: &Path) -> Result<(), Report> {
134 if let Some(parent) = path.parent() {
135 std::fs::create_dir_all(parent).attach_with(|| format!("Failed to create directory {}", parent.display()))?;
136 }
137 let content =
138 toml::to_string_pretty(registry).map_err(|e| rootcause::report!("Failed to serialize registry: {e}"))?;
139 std::fs::write(path, content).attach_with(|| format!("Failed to write {}", path.display()))?;
140 Ok(())
141}