hexomc_lib/java/
installer.rs1use std::path::{Path, PathBuf};
2use tokio::fs;
3
4use crate::{
5 download::{download_file, DownloadTask},
6 error::{HexoError, Result},
7};
8
9const ADOPTIUM_API: &str = "https://api.adoptium.net/v3/assets/latest";
10
11pub async fn download_java(version: u32, base_dir: &Path) -> Result<PathBuf> {
14 let java_dir = base_dir.join("java").join(version.to_string());
15 fs::create_dir_all(&java_dir).await?;
16
17 let (os, arch, archive_type) = platform_info();
18 let url = format!(
19 "{}/{}/hotspot?image_type=jre&os={}&architecture={}&vendor=eclipse",
20 ADOPTIUM_API, version, os, arch
21 );
22
23 let client = reqwest::Client::new();
24 let releases: Vec<AdoptiumRelease> = client.get(&url).send().await?.json().await?;
25
26 let release = releases
27 .into_iter()
28 .next()
29 .ok_or_else(|| HexoError::JavaNotFound { required: version })?;
30
31 let binary = &release.binary;
32 let pkg = &binary.package;
33
34 let archive_path = java_dir.join(&pkg.name);
35 download_file(&DownloadTask::new(&pkg.link, &archive_path).with_sha1(pkg.checksum.clone()))
36 .await?;
37
38 let extract_dir = java_dir.join("jre");
39 fs::create_dir_all(&extract_dir).await?;
40
41 if archive_type == "zip" {
42 extract_zip(&archive_path, &extract_dir).await?;
43 } else {
44 extract_tar_gz(&archive_path, &extract_dir).await?;
45 }
46
47 let java_bin =
48 find_java_bin_in(&extract_dir).ok_or(HexoError::JavaNotFound { required: version })?;
49 Ok(java_bin)
50}
51
52fn platform_info() -> (&'static str, &'static str, &'static str) {
53 let os = if cfg!(target_os = "windows") {
54 "windows"
55 } else if cfg!(target_os = "macos") {
56 "mac"
57 } else {
58 "linux"
59 };
60
61 let arch = if cfg!(target_arch = "x86_64") {
62 "x64"
63 } else if cfg!(target_arch = "aarch64") {
64 "aarch64"
65 } else {
66 "x32"
67 };
68
69 let archive = if cfg!(target_os = "windows") {
70 "zip"
71 } else {
72 "tar.gz"
73 };
74
75 (os, arch, archive)
76}
77
78#[derive(serde::Deserialize)]
79struct AdoptiumRelease {
80 binary: AdoptiumBinary,
81}
82
83#[derive(serde::Deserialize)]
84struct AdoptiumBinary {
85 package: AdoptiumPackage,
86}
87
88#[derive(serde::Deserialize)]
89struct AdoptiumPackage {
90 name: String,
91 link: String,
92 checksum: String,
93}
94
95async fn extract_zip(archive: &Path, dest: &Path) -> Result<()> {
96 let archive = archive.to_owned();
97 let dest = dest.to_owned();
98 tokio::task::spawn_blocking(move || {
99 let file = std::fs::File::open(&archive)?;
100 let mut zip = zip::ZipArchive::new(file)?;
101 zip.extract(&dest)?;
102 Ok(())
103 })
104 .await
105 .map_err(|e| HexoError::Other(e.to_string()))?
106}
107
108async fn extract_tar_gz(archive: &Path, dest: &Path) -> Result<()> {
109 let archive = archive.to_owned();
110 let dest = dest.to_owned();
111 tokio::task::spawn_blocking(move || {
112 let file = std::fs::File::open(&archive)?;
113 let gz = flate2::read::GzDecoder::new(file);
114 let mut tar = tar::Archive::new(gz);
115 tar.unpack(&dest)?;
116 Ok(())
117 })
118 .await
119 .map_err(|e| HexoError::Other(e.to_string()))?
120}
121
122fn find_java_bin_in(dir: &Path) -> Option<PathBuf> {
123 let java_name = if cfg!(target_os = "windows") {
124 "java.exe"
125 } else {
126 "java"
127 };
128
129 if let Ok(entries) = std::fs::read_dir(dir) {
130 for entry in entries.flatten() {
131 let candidate = entry.path().join("bin").join(java_name);
132 if candidate.exists() {
133 return Some(candidate);
134 }
135 }
136 }
137
138 let direct = dir.join("bin").join(java_name);
139 if direct.exists() {
140 return Some(direct);
141 }
142 None
143}