Skip to main content

hexomc_lib/java/
installer.rs

1use 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
11/// Download an Adoptium Temurin JRE to `{base_dir}/java/{version}/`.
12/// Returns the full path to the java executable.
13pub 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}