Skip to main content

uranium_rs/
installation_fixer.rs

1use std::path::Path;
2
3use crate::downloaders::{Downloader, FileDownloader, get_lib_path};
4use crate::error::Result;
5
6use crate::downloaders::get_index_path;
7use crate::{downloaders::DownloadableObject, version_checker::VersionCheckResult};
8
9pub struct InstallationFixer {
10    data: Vec<DownloadableObject>,
11}
12
13impl InstallationFixer {
14    pub fn new(check_result: VersionCheckResult, installation_path: impl AsRef<Path>) -> Self {
15        let installation_path = installation_path.as_ref();
16        let mut fixer = Self { data: vec![] };
17        fixer.add_objects(&check_result, installation_path);
18        fixer.add_libs(&check_result, installation_path);
19        fixer.add_index(&check_result, installation_path);
20        fixer
21    }
22
23    pub async fn fix_installation(&mut self) -> Result<()> {
24        let files = std::mem::take(&mut self.data);
25
26        let mut downloader = Downloader::new(files);
27        downloader.complete().await
28    }
29
30    fn add_objects(&mut self, check_result: &VersionCheckResult, installation_path: &Path) {
31        self.data.extend(
32            check_result
33                .objects
34                .iter()
35                .map(|&f| DownloadableObject::from(f))
36                .map(|mut f| {
37                    f.path = installation_path.join(f.path);
38                    f
39                }),
40        );
41    }
42
43    fn add_libs(&mut self, check_result: &VersionCheckResult, installation_path: &Path) {
44        self.data.extend(
45            check_result
46                .libs
47                .iter()
48                .map(|&f| DownloadableObject::from(f))
49                .map(|mut f| {
50                    f.path = get_lib_path(installation_path, &f.path);
51                    f
52                }),
53        );
54    }
55
56    fn add_index(&mut self, check_result: &VersionCheckResult, installation_path: &Path) {
57        if let Some(mut idx) = check_result.index.map(DownloadableObject::from) {
58            idx.path = get_index_path(installation_path, &idx.path);
59            self.data.push(idx);
60        }
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::mine_data_structs::minecraft::{Artifact, AssetIndex, Library, LibraryDownloads, ObjectData};
68
69    fn make_library(path: &str, sha1: &str) -> Library {
70        Library {
71            name: "test:lib:1.0".into(),
72            downloads: Some(LibraryDownloads {
73                artifact: Artifact {
74                    path: path.into(),
75                    sha1: sha1.into(),
76                    size: 1024,
77                    url: "https://example.com/lib.jar".into(),
78                },
79            }),
80            rules: None,
81        }
82    }
83
84    fn make_object(hash: &str) -> ObjectData {
85        ObjectData { hash: hash.into(), size: 512 }
86    }
87
88    fn make_index(id: &str, sha1: &str) -> AssetIndex {
89        AssetIndex {
90            id: id.into(),
91            sha1: sha1.into(),
92            size: 4096,
93            total_size: 100_000,
94            url: "https://example.com/index.json".into(),
95        }
96    }
97
98    #[test]
99    fn adds_objects() {
100        let obj = make_object("a1b2c3d4e5f6");
101        let result = VersionCheckResult {
102            objects: Box::new([&obj]),
103            libs: Box::new([]),
104            index: None,
105            client: None,
106        };
107        let fixer = InstallationFixer::new(result, "/tmp/test");
108
109        assert_eq!(fixer.data.len(), 1);
110        let entry = &fixer.data[0];
111        assert!(
112            entry.path.starts_with("/tmp/test"),
113            "expected path to start with installation path, got: {:?}",
114            entry.path,
115        );
116        assert!(
117            entry.path.to_string_lossy().contains("a1b2c3d4e5f6"),
118            "expected path to contain the object hash, got: {:?}",
119            entry.path,
120        );
121        assert!(
122            entry.url.contains("resources.download.minecraft.net"),
123            "expected URL to be a Minecraft resource URL, got: {}",
124            entry.url,
125        );
126        assert!(entry.hash.is_some());
127    }
128
129    #[test]
130    fn adds_libs() {
131        let lib = make_library("net/minecraft/client/1.21/client.jar", "ffgg1122");
132        let result = VersionCheckResult {
133            objects: Box::new([]),
134            libs: Box::new([&lib]),
135            index: None,
136            client: None,
137        };
138        let fixer = InstallationFixer::new(result, "/tmp/test");
139
140        assert_eq!(fixer.data.len(), 1);
141        let entry = &fixer.data[0];
142        assert!(
143            entry.path.starts_with("/tmp/test/libraries"),
144            "expected path under libraries/, got: {:?}",
145            entry.path,
146        );
147        assert!(
148            entry.path.to_string_lossy().contains("net/minecraft/client/1.21/client.jar"),
149            "expected path to contain artifact path, got: {:?}",
150            entry.path,
151        );
152        assert_eq!(entry.url, "https://example.com/lib.jar");
153        assert!(entry.hash.is_some());
154    }
155
156    #[test]
157    fn all_sources_accumulate() {
158        let obj = make_object("aaa111");
159        let lib = make_library("org/example/foo/1.0/foo.jar", "bbb222");
160        let idx = make_index("1.21", "ccc333");
161        let result = VersionCheckResult {
162            objects: Box::new([&obj]),
163            libs: Box::new([&lib]),
164            index: Some(&idx),
165            client: None,
166        };
167        let fixer = InstallationFixer::new(result, "/tmp/test");
168
169        assert_eq!(fixer.data.len(), 3);
170        let paths: Vec<_> = fixer.data.iter().map(|d| d.path.to_string_lossy().to_string()).collect();
171        assert!(paths.iter().any(|p| p.contains("aaa111")),     "object missing");
172        assert!(paths.iter().any(|p| p.contains("org/example")), "library missing");
173        assert!(paths.iter().any(|p| p.contains("1.21")),       "index id missing");
174    }
175
176    #[tokio::test]
177    async fn fix_installation_empty_returns_ok() {
178        let result = VersionCheckResult {
179            objects: Box::new([]),
180            libs: Box::new([]),
181            index: None,
182            client: None,
183        };
184        let mut fixer = InstallationFixer::new(result, "/tmp/test");
185
186        let outcome = fixer.fix_installation().await;
187
188        assert!(outcome.is_ok(), "expected Ok(()) with no downloads, got: {:?}", outcome);
189        assert!(fixer.data.is_empty());
190    }
191}