1#![doc = include_str!("../.wiki/Java.md")]
2
3use simple_download_utility::{FileDownloadArguments, MultiDownloadProgress, download_multiple_files};
4use anyhow::{Result, anyhow};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fmt::Display;
8use std::path::Path;
9
10const PISTON_URL: &str = "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
11
12#[derive(Debug, Serialize, Deserialize)]
13pub struct JavaManifest {
14 pub linux: Runtimes,
15 #[serde(rename = "linux-i386")]
16 pub linux_i386: Runtimes,
17 #[serde(rename = "mac-os")]
18 pub macos: Runtimes,
19 #[serde(rename = "mac-os-arm64")]
20 pub macos_arm64: Runtimes,
21 #[serde(rename = "windows-arm64")]
22 pub windows_arm64: Runtimes,
23 #[serde(rename = "windows-x64")]
24 pub windows_x64: Runtimes,
25 #[serde(rename = "windows-x86")]
26 pub windows_x86: Runtimes,
27}
28
29#[derive(Debug, Serialize, Deserialize)]
30pub struct Runtimes {
31 #[serde(rename = "java-runtime-alpha")]
32 pub alpha: Vec<JavaRuntime>,
33 #[serde(rename = "java-runtime-beta")]
34 pub beta: Vec<JavaRuntime>,
35 #[serde(rename = "java-runtime-gamma")]
36 pub gamma: Vec<JavaRuntime>,
37 #[serde(rename = "java-runtime-delta")]
38 pub delta: Vec<JavaRuntime>,
39 #[serde(rename = "java-runtime-gamma-snapshot")]
40 pub gamma_snapshot: Vec<JavaRuntime>,
41 #[serde(rename = "java-runtime-epsilon")]
42 pub epsilon: Vec<JavaRuntime>,
43 #[serde(rename = "jre-legacy")]
44 pub legacy: Vec<JavaRuntime>,
45 #[serde(rename = "minecraft-java-exe")]
46 pub minecraft_java_exe: serde_json::Value,
47}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct JavaRuntime {
51 version: Version,
52 manifest: Manifest,
53 availability: Availability,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57pub struct Manifest {
58 pub sha1: String,
59 pub size: usize,
60 pub url: String,
61}
62
63#[derive(Debug, Serialize, Deserialize)]
64pub struct Version {
65 pub name: String,
66 pub released: chrono::DateTime<chrono::Utc>,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70pub struct Availability {
71 pub group: u32,
72 pub progress: u32,
73}
74
75#[derive(Debug, Serialize, Deserialize)]
76pub struct JavaInstallationFile {
77 #[serde(skip)]
78 pub name: String,
79 #[serde(rename = "type")]
80 pub file_type: Option<FileType>,
81 pub executable: Option<bool>,
82 pub downloads: Option<Downloads>,
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86pub struct Downloads {
87 pub lzma: Option<DownloadItem>,
88 pub raw: DownloadItem,
89}
90#[derive(Debug, Serialize, Deserialize)]
91pub struct DownloadItem {
92 pub sha1: String,
93 pub size: usize,
94 pub url: String,
95}
96
97#[derive(Debug, Serialize, Deserialize)]
98pub enum FileType {
99 #[serde(rename = "file")]
100 File,
101 #[serde(rename = "directory")]
102 Directory,
103 #[serde(rename = "link")]
104 Link,
105}
106
107impl JavaManifest {
108 pub async fn fetch() -> Result<Self> {
109 let response = reqwest::get(PISTON_URL).await?;
110 let text = response.text().await?;
111 let json_result = serde_json::from_str::<Self>(&text);
112 #[cfg(feature = "log")]
113 if let Err(ref e) = json_result {
114 let line = e.line();
115 let column = e.column();
116 error!("Failed to deserialize VersionManifest from {}: {}", PISTON_URL, e);
117 error!("Error at line {}, column {}", line, column);
118
119 let error_offset = text.lines().take(line - 1).map(|l| l.len() + 1).sum::<usize>() + column - 1;
121 let start = error_offset.saturating_sub(60);
122 let end = (error_offset + 60).min(text.len());
123 let context = &text[start..end];
124
125 error!("Context around error: {}", context);
126 }
127 Ok(json_result?)
128 }
129}
130
131impl JavaRuntime {
132 pub async fn get_installation_files(&self) -> Result<Vec<JavaInstallationFile>> {
133 let url = self.manifest.url.clone();
134 let response = reqwest::get(&url).await?;
135 let files: serde_json::Value = response.json().await?;
136 let files = files.get("files").ok_or_else(|| anyhow!("Missing 'files' field in response"))?;
137 let json_result = serde_json::from_value::<HashMap<String, JavaInstallationFile>>(files.clone());
138 #[cfg(feature = "log")]
139 if let Err(ref e) = json_result {
140 let line = e.line();
141 let column = e.column();
142 error!("Failed to deserialize VersionManifest from {}: {}", url, e);
143 error!("Error at line {}, column {}", line, column);
144 }
145 let map = json_result?;
146 Ok(map
147 .into_iter()
148 .map(|(name, mut file)| {
149 file.name = name;
150 file
151 })
152 .collect())
153 }
154
155 pub async fn install(
156 &self,
157 directory: impl AsRef<Path>,
158 parallel: u16,
159 sender: Option<tokio::sync::mpsc::Sender<MultiDownloadProgress>>,
160 ) -> Result<()> {
161 let directory = directory.as_ref();
162 let installation_files = self.get_installation_files().await?;
163
164 let args: Vec<FileDownloadArguments> = installation_files
165 .iter()
166 .filter_map(|item| {
167 item.downloads.as_ref().map(|download| FileDownloadArguments {
168 url: download.raw.url.clone(),
169 path: directory.join(&item.name).to_string_lossy().to_string(),
170 sender: None,
171 sha1: Some(download.raw.sha1.clone()),
172 })
173 })
174 .collect();
175
176 #[cfg(feature = "log")]
177 info!("Downloading files: {:?}", installation_files);
178
179 download_multiple_files(args, parallel, sender).await?;
180
181 Ok(())
182 }
183}
184
185impl Display for JavaRuntime {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 write!(f, "{}", self.version.name)
188 }
189}
190
191#[cfg(test)]
192mod test {
193 use crate::java::JavaManifest;
194 #[cfg(feature = "log")]
195 use crate::setup_logging;
196 use futures_util::{StreamExt, stream};
197
198 #[tokio::test]
199 async fn fetch() {
200 #[cfg(feature = "log")]
201 setup_logging();
202 let manifest = JavaManifest::fetch().await.unwrap();
203 info!("{:?}", manifest);
204 }
205 #[tokio::test]
206 async fn get_installation_files() {
207 #[cfg(feature = "log")]
208 setup_logging();
209 let manifest = JavaManifest::fetch().await.unwrap();
210 let runtimes = [
211 &manifest.linux.alpha,
212 &manifest.linux.beta,
213 &manifest.linux.gamma,
214 &manifest.linux.delta,
215 &manifest.linux.gamma_snapshot,
216 &manifest.linux.epsilon,
217 &manifest.linux.legacy,
218 &manifest.linux_i386.alpha,
219 &manifest.linux_i386.beta,
220 &manifest.linux_i386.gamma,
221 &manifest.linux_i386.delta,
222 &manifest.linux_i386.gamma_snapshot,
223 &manifest.linux_i386.epsilon,
224 &manifest.linux_i386.legacy,
225 &manifest.macos.alpha,
226 &manifest.macos.beta,
227 &manifest.macos.gamma,
228 &manifest.macos.delta,
229 &manifest.macos.gamma_snapshot,
230 &manifest.macos.epsilon,
231 &manifest.macos.legacy,
232 &manifest.macos_arm64.alpha,
233 &manifest.macos_arm64.beta,
234 &manifest.macos_arm64.gamma,
235 &manifest.macos_arm64.delta,
236 &manifest.macos_arm64.gamma_snapshot,
237 &manifest.macos_arm64.epsilon,
238 &manifest.macos_arm64.legacy,
239 &manifest.windows_arm64.alpha,
240 &manifest.windows_arm64.beta,
241 &manifest.windows_arm64.gamma,
242 &manifest.windows_arm64.delta,
243 &manifest.windows_arm64.gamma_snapshot,
244 &manifest.windows_arm64.epsilon,
245 &manifest.windows_arm64.legacy,
246 &manifest.windows_x64.alpha,
247 &manifest.windows_x64.beta,
248 &manifest.windows_x64.gamma,
249 &manifest.windows_x64.delta,
250 &manifest.windows_x64.gamma_snapshot,
251 &manifest.windows_x64.epsilon,
252 &manifest.windows_x64.legacy,
253 &manifest.windows_x86.alpha,
254 &manifest.windows_x86.beta,
255 &manifest.windows_x86.gamma,
256 &manifest.windows_x86.delta,
257 &manifest.windows_x86.gamma_snapshot,
258 &manifest.windows_x86.epsilon,
259 &manifest.windows_x86.legacy,
260 ];
261
262 let results: Vec<_> = stream::iter(runtimes)
263 .enumerate()
264 .map(|(idx, runtime_vec)| {
265 let runtime = runtime_vec.first();
266 async move {
267 if let Some(runtime) = runtime {
268 let files_result = runtime.get_installation_files().await;
269 #[cfg(feature = "log")]
270 if let Ok(ref files) = files_result {
271 info!("Runtime {}: {} - found {} installation files", idx, runtime.version.name, files.len());
272 }
273 files_result
274 } else {
275 #[cfg(feature = "log")]
276 info!("Runtime {}: empty runtime vector", idx);
277 Ok(vec![])
278 }
279 }
280 })
281 .buffer_unordered(10usize)
282 .collect()
283 .await;
284
285 for result in &results {
286 if let Err(e) = result {
287 #[cfg(feature = "log")]
288 error!("Failed to get installation files: {}", e);
289 }
290 assert!(result.is_ok());
291 }
292 }
293
294 #[tokio::test]
295 async fn install() {
296 #[cfg(feature = "log")]
297 setup_logging();
298 let manifest = JavaManifest::fetch().await.unwrap();
299 let directory = "target/test/";
300 let runtimes = [
301 &manifest.linux.alpha,
302 &manifest.linux.beta,
303 &manifest.linux.gamma,
304 &manifest.linux.delta,
305 &manifest.linux.gamma_snapshot,
306 &manifest.linux.epsilon,
307 &manifest.linux.legacy,
308 &manifest.linux_i386.alpha,
309 &manifest.linux_i386.beta,
310 &manifest.linux_i386.gamma,
311 &manifest.linux_i386.delta,
312 &manifest.linux_i386.gamma_snapshot,
313 &manifest.linux_i386.epsilon,
314 &manifest.linux_i386.legacy,
315 &manifest.macos.alpha,
316 &manifest.macos.beta,
317 &manifest.macos.gamma,
318 &manifest.macos.delta,
319 &manifest.macos.gamma_snapshot,
320 &manifest.macos.epsilon,
321 &manifest.macos.legacy,
322 &manifest.macos_arm64.alpha,
323 &manifest.macos_arm64.beta,
324 &manifest.macos_arm64.gamma,
325 &manifest.macos_arm64.delta,
326 &manifest.macos_arm64.gamma_snapshot,
327 &manifest.macos_arm64.epsilon,
328 &manifest.macos_arm64.legacy,
329 &manifest.windows_arm64.alpha,
330 &manifest.windows_arm64.beta,
331 &manifest.windows_arm64.gamma,
332 &manifest.windows_arm64.delta,
333 &manifest.windows_arm64.gamma_snapshot,
334 &manifest.windows_arm64.epsilon,
335 &manifest.windows_arm64.legacy,
336 &manifest.windows_x64.alpha,
337 &manifest.windows_x64.beta,
338 &manifest.windows_x64.gamma,
339 &manifest.windows_x64.delta,
340 &manifest.windows_x64.gamma_snapshot,
341 &manifest.windows_x64.epsilon,
342 &manifest.windows_x64.legacy,
343 &manifest.windows_x86.alpha,
344 &manifest.windows_x86.beta,
345 &manifest.windows_x86.gamma,
346 &manifest.windows_x86.delta,
347 &manifest.windows_x86.gamma_snapshot,
348 &manifest.windows_x86.epsilon,
349 &manifest.windows_x86.legacy,
350 ];
351
352 let results: Vec<_> = stream::iter(runtimes)
353 .map(|runtime| async move {
354 if let Some(runtime) = runtime.first() {
355 let directory = std::path::Path::new(directory).join(format!("{}-{}", runtime, runtime.manifest.sha1));
356 info!("Installing java {} to {}...", runtime, directory.display());
357 runtime.install(&directory, 20, None).await
358 } else {
359 warn!("No runtime specified");
360 Ok(())
361 }
362 })
363 .buffer_unordered(27)
364 .collect()
365 .await;
366
367 for result in results {
368 result.unwrap();
369 }
370 }
371}