uranium_rs/modpack_maker/
mod.rs1#![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 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 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 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