uranium_rs/modpack_maker/
mod.rs

1#![allow(unused)]
2use std::collections::HashMap;
3use std::fs::read_dir;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use derive_more::Display;
8pub use maker::ModpackMaker;
9pub use maker::State;
10use mine_data_structs::minecraft::Profile;
11use mine_data_structs::rinth::{RinthModpack, RinthVersion, RinthVersionFile, RinthVersions};
12use reqwest::header::{CONTENT_TYPE, HeaderMap};
13use reqwest::{Body, ClientBuilder};
14use serde::{Deserialize, Serialize};
15use tokio::task::JoinHandle;
16use zip::ZipWriter;
17
18use crate::error::{Result, UraniumError};
19use crate::hashes::rinth_hash;
20use crate::searcher::rinth::{SearchBuilder, SearchType};
21
22mod maker;
23
24#[derive(Clone, Copy, Debug)]
25enum MakingProgress {
26    ReadingProfile,
27    RetrievingMods,
28    WritingModpack,
29    Finished,
30}
31
32enum InnerState {
33    ReadingMods {
34        data: HashMap<String, PathBuf>,
35        dir: std::fs::ReadDir,
36    },
37    SendingRequests {
38        data: HashMap<String, PathBuf>,
39    },
40    WritingModpack,
41    End,
42}
43
44#[derive(Display)]
45pub enum ModLoaders {
46    #[display("forge")]
47    Forge,
48    #[display("fabric")]
49    Fabric,
50    #[display("quilt")]
51    Quilt,
52}
53
54struct ModpackMaker2 {
55    mods: Vec<RinthVersionFile>,
56    client: reqwest::Client,
57    overrides: Vec<PathBuf>,
58    path: PathBuf,
59    state: MakingProgress,
60    inner: InnerState,
61    modpack: RinthModpack,
62}
63
64impl ModpackMaker2 {
65    pub fn new(profile: Profile) -> Result<Self> {
66        let path = profile
67            .game_dir
68            .clone()
69            .unwrap_or_else(|| PathBuf::new());
70        if !Path::exists(&path) {
71            log::error!("Path: {:?}, not found", &path);
72            return Err(UraniumError::FileNotFound(path.display().to_string()));
73        }
74
75        let client = ClientBuilder::new()
76            .user_agent("uranium-rs/ModpackMaker contact: sergious234@gmail.com")
77            .build()?;
78
79        let dir = read_dir(path.join("mods"))?;
80
81        Ok(Self {
82            mods: vec![],
83            path: path.to_path_buf(),
84            overrides: vec![],
85            client,
86            state: MakingProgress::ReadingProfile,
87            inner: InnerState::ReadingMods {
88                data: HashMap::new(),
89                dir: dir,
90            },
91            modpack: RinthModpack::new(),
92        })
93    }
94
95    pub async fn progress(&mut self) -> Result<MakingProgress> {
96        use InnerState as IS;
97        use MakingProgress as MP;
98
99        let mut next_state = None;
100        let current_state = match self.inner {
101            IS::ReadingMods {
102                ref mut data,
103                ref mut dir,
104            } => {
105                let mut i = 0;
106                for minecraft_mod in dir.take(16) {
107                    i += 1;
108                    let minecraft_mod = minecraft_mod?;
109                    let path = minecraft_mod.path();
110                    let hash = rinth_hash(&path);
111                    data.insert(hash, path);
112                }
113
114                // Go to the next state when there is no more files left.
115                if i != 16 {
116                    next_state = Some(IS::SendingRequests {
117                        data: std::mem::take(data),
118                    });
119                }
120                MP::ReadingProfile
121            }
122
123            IS::SendingRequests { ref mut data } => {
124                #[derive(Serialize, Debug)]
125                struct RequestBody<'a> {
126                    hashes: &'a [String],
127                    algorithm: String,
128                }
129
130                let url = "https://api.modrinth.com/v2/version_files";
131
132                let hashes: Vec<String> = data.keys().cloned().collect();
133
134                let x = self
135                    .client
136                    .post(url)
137                    .json(&RequestBody {
138                        hashes: &hashes,
139                        algorithm: "sha1".to_string(),
140                    })
141                    .send()
142                    .await?
143                    .json::<HashMap<String, RinthVersionFile>>()
144                    .await?;
145
146                for hash in &hashes {
147                    if !x.contains_key(hash) {
148                        let x = data.remove(hash).unwrap();
149                        self.overrides.push(x);
150                    }
151                }
152
153                self.mods
154                    .extend(x.into_values());
155
156                self.modpack.name = "New modpack".into();
157                self.modpack.files.extend(
158                    self.mods
159                        .drain(..)
160                        .map(Into::into),
161                );
162
163                // for x in self.mods {
164                //     self.modpack.files.push(x.into());
165                // }
166                // self.modpack.files = self.mods.iter().cloned().map(|m|
167                // m.into()).collect();
168
169                next_state = Some(IS::End);
170                MP::Finished
171            }
172
173            IS::WritingModpack { .. } => {
174                const OVERRIDES_FOLDERS: [&str; 2] = ["resourcepacks", "config"];
175                let mut zip = ZipWriter::new(std::fs::File::open("test")?);
176
177                for or_folder in OVERRIDES_FOLDERS {
178                    let or_path = self.path.join(or_folder);
179                    if or_path.exists() {
180                        println!("{:?} exists", or_path)
181                    }
182                }
183
184                MP::WritingModpack
185            }
186
187            IS::End => MP::Finished,
188        };
189
190        if let Some(next_state) = next_state {
191            self.inner = next_state;
192        }
193
194        Ok(current_state)
195    }
196
197    pub fn get_modpack(&mut self) -> Option<&mut RinthModpack> {
198        match self.inner {
199            InnerState::End => Some(&mut self.modpack),
200            _ => None,
201        }
202    }
203}
204
205#[cfg(test)]
206mod test {
207    use mine_data_structs::minecraft::Profile;
208
209    use crate::modpack_maker::MakingProgress;
210    use crate::modpack_maker::ModpackMaker2;
211
212    #[tokio::test]
213    async fn make_test() {
214        let path = "/home/sergio/.minecraft/Quilt1.19.2";
215        if !std::fs::exists(&path).unwrap() {
216            println!("F");
217            return;
218        }
219
220        let fake_profile = Profile::new(
221            "",
222            "quilt-loader-0.26.3-1.19.2",
223            "",
224            "",
225            Some(&std::path::PathBuf::from(
226                "/home/sergio/.minecraft/Quilt1.19.2",
227            )),
228        );
229        let mut mm3 = ModpackMaker2::new(fake_profile).unwrap();
230        //init_logger().unwrap();
231        loop {
232            let p = mm3.progress().await;
233
234            if let Ok(MakingProgress::Finished) = p {
235                println!("Done!");
236
237                let modpack = mm3.get_modpack().unwrap();
238
239                println!("{}", serde_json::to_string_pretty(modpack).unwrap());
240
241                return;
242            } else if let Err(e) = p {
243                println!("{}", e);
244                break;
245            } else {
246                println!("[{:?}] Requests left: 0", p);
247            }
248        }
249    }
250}
251
252/*
253
254    TODO:
255        - Estructura para analizar un profile (&Profile) y crear un modpack a partir
256        de ese profile.
257        - La estructura tiene que ser capaz de:
258            · Saber los mods del perfil
259            · Tener los mods cargados con la estructura de version_file (RinthVersionFile)
260              para saber datos de la versión especifica actual.
261                (https://api.modrinth.com/v2/version_file/619e250c133106bacc3e3b560839bd4b324dfda8)
262            · Tener los mods cargados con la estructura de project/{slug}/version (RinthVersions)
263                para saber los datos de las versiones mas nuevas del mod que sigan usando la version
264                de minecraft actual.
265                (https://api.modrinth.com/v2/project/Jw3Wx1KR/version)
266            · Poder mostrar la version mas actualizada del mod para la versión de minecraft.
267            · Usar la misma filosofia de progress() para facilitar la asincronicidad.
268            · enum MakingProgress {
269            ·   ReadingMods
270            ·   RetrievingMods
271            ·   LookingForUpdates
272            ·   Finished
273            · }
274
275        Ejemplo:
276
277            mods
278              | sodium.jar
279              | crate.jar
280              | fabric-api.jar
281              | minimap.jar
282
283           https://api.modrinth.com/v2/project/Jw3Wx1KR/version?game_versions=["1.19"]
284
285
286           {
287            "property1": {
288                "name": "Version 1.0.0",
289                "version_number": "1.0.0",
290            },
291
292            "property2": {
293                "name": "Version 1.0.0",
294                "version_number": "1.0.0",
295            }
296           }
297
298*/