lighty_java/
jre_downloader.rs1use std::io::Cursor;
7use std::path::{Path, PathBuf};
8use crate::errors::{JreError, JreResult};
9use lighty_core::errors::DownloadError;
10use path_absolutize::Absolutize;
11use tokio::fs;
12
13use lighty_core::system::{OperatingSystem, OS};
14use lighty_core::download::download_file;
15use lighty_core::extract::{tar_gz_extract, zip_extract};
16
17use super::JavaDistribution;
18
19#[cfg(feature = "events")]
20use lighty_event::{EventBus, Event, JavaEvent};
21
22pub async fn find_java_binary(
27 runtimes_folder: &Path,
28 distribution: &JavaDistribution,
29 version: &u8,
30) -> JreResult<PathBuf> {
31 let effective_distribution = distribution
32 .get_fallback(*version)
33 .unwrap_or_else(|| distribution.clone());
34
35 let runtime_dir = build_runtime_path(runtimes_folder, &effective_distribution, version);
36
37 let binary_path = locate_binary_in_directory(&runtime_dir).await?;
38
39 #[cfg(unix)]
40 ensure_executable_permissions(&binary_path).await?;
41
42 Ok(binary_path.absolutize()?.to_path_buf())
43}
44
45#[cfg(feature = "events")]
47pub async fn jre_download<F>(
48 runtimes_folder: &Path,
49 distribution: &JavaDistribution,
50 version: &u8,
51 on_progress: F,
52 event_bus: Option<&EventBus>,
53) -> JreResult<PathBuf>
54where
55 F: Fn(u64, u64),
56{
57 let effective_distribution = distribution
58 .get_fallback(*version)
59 .unwrap_or_else(|| distribution.clone());
60
61 let runtime_dir = build_runtime_path(runtimes_folder, &effective_distribution, version);
62
63 prepare_installation_directory(&runtime_dir).await?;
64
65 let download_url = effective_distribution.get_download_url(version).await?;
66
67 if let Some(bus) = event_bus {
68 let response = lighty_core::hosts::HTTP_CLIENT
69 .get(&download_url)
70 .send()
71 .await
72 .map_err(DownloadError::from)?;
73
74 let total_bytes = response.content_length().unwrap_or(0);
75
76 bus.emit(Event::Java(JavaEvent::JavaDownloadStarted {
77 distribution: effective_distribution.get_name().to_string(),
78 version: *version,
79 total_bytes,
80 }));
81 }
82
83 let archive_bytes = {
84 let event_bus_ref = event_bus;
85 download_file(&download_url, |current, _total| {
86 on_progress(current, _total);
87 if let Some(bus) = event_bus_ref {
88 if current > 0 {
90 bus.emit(Event::Java(JavaEvent::JavaDownloadProgress {
91 bytes: current,
92 }));
93 }
94 }
95 })
96 .await
97?
98 };
99
100 if let Some(bus) = event_bus {
101 bus.emit(Event::Java(JavaEvent::JavaDownloadCompleted {
102 distribution: effective_distribution.get_name().to_string(),
103 version: *version,
104 }));
105 }
106
107 if let Some(bus) = event_bus {
108 bus.emit(Event::Java(JavaEvent::JavaExtractionStarted {
109 distribution: effective_distribution.get_name().to_string(),
110 version: *version,
111 }));
112 }
113
114 extract_archive(
115 &archive_bytes,
116 &runtime_dir,
117 event_bus,
118 ).await?;
119
120 let binary_path = find_java_binary(runtimes_folder, &effective_distribution, version).await?;
121
122 if let Some(bus) = event_bus {
123 bus.emit(Event::Java(JavaEvent::JavaExtractionCompleted {
124 distribution: effective_distribution.get_name().to_string(),
125 version: *version,
126 binary_path: binary_path.to_string_lossy().to_string(),
127 }));
128 }
129
130 Ok(binary_path)
131}
132
133#[cfg(not(feature = "events"))]
135pub async fn jre_download<F>(
136 runtimes_folder: &Path,
137 distribution: &JavaDistribution,
138 version: &u8,
139 on_progress: F,
140) -> JreResult<PathBuf>
141where
142 F: Fn(u64, u64),
143{
144 let effective_distribution = distribution
145 .get_fallback(*version)
146 .unwrap_or_else(|| distribution.clone());
147
148 let runtime_dir = build_runtime_path(runtimes_folder, &effective_distribution, version);
149
150 prepare_installation_directory(&runtime_dir).await?;
151
152 let download_url = effective_distribution.get_download_url(version).await?;
153
154 let archive_bytes = download_file(&download_url, on_progress)
155 .await
156?;
157
158 extract_archive(&archive_bytes, &runtime_dir).await?;
159
160 find_java_binary(runtimes_folder, &effective_distribution, version).await
161}
162
163fn build_runtime_path(
165 runtimes_folder: &Path,
166 distribution: &JavaDistribution,
167 version: &u8,
168) -> PathBuf {
169 let mut path = runtimes_folder.to_path_buf();
170 path.push(format!("{}_{}", distribution.get_name(), version));
171 path
172}
173
174async fn prepare_installation_directory(runtime_dir: &Path) -> JreResult<()> {
176 if runtime_dir.exists() {
177 fs::remove_dir_all(runtime_dir).await?;
178 }
179 fs::create_dir_all(runtime_dir).await?;
180 Ok(())
181}
182
183#[cfg(feature = "events")]
185async fn extract_archive(
186 archive_bytes: &[u8],
187 destination: &Path,
188 event_bus: Option<&EventBus>,
189) -> JreResult<()> {
190 let cursor = Cursor::new(archive_bytes);
191
192 match OS {
193 OperatingSystem::WINDOWS => {
194 zip_extract(cursor, destination, event_bus)
195 .await
196?;
197 }
198 OperatingSystem::LINUX | OperatingSystem::OSX => {
199 tar_gz_extract(cursor, destination, event_bus)
200 .await
201?;
202 }
203 OperatingSystem::UNKNOWN => {
204 return Err(JreError::UnsupportedOS);
205 }
206 }
207
208 Ok(())
209}
210
211#[cfg(not(feature = "events"))]
213async fn extract_archive(archive_bytes: &[u8], destination: &Path) -> JreResult<()> {
214 let cursor = Cursor::new(archive_bytes);
215
216 match OS {
217 OperatingSystem::WINDOWS => {
218 zip_extract(cursor, destination)
219 .await
220?;
221 }
222 OperatingSystem::LINUX | OperatingSystem::OSX => {
223 tar_gz_extract(cursor, destination)
224 .await
225?;
226 }
227 OperatingSystem::UNKNOWN => {
228 return Err(JreError::UnsupportedOS);
229 }
230 }
231
232 Ok(())
233}
234
235async fn locate_binary_in_directory(runtime_dir: &Path) -> JreResult<PathBuf> {
244 let mut entries = fs::read_dir(runtime_dir).await?;
245
246 let jre_root = entries
247 .next_entry()
248 .await?
249 .ok_or_else(|| JreError::NotFound {
250 path: runtime_dir.to_path_buf(),
251 })?
252 .path();
253
254 let java_binary = match OS {
255 OperatingSystem::WINDOWS => jre_root.join("bin").join("java.exe"),
256 OperatingSystem::OSX => {
257 let bundle_path = jre_root.join("Contents").join("Home").join("bin").join("java");
259 if bundle_path.exists() {
260 bundle_path
261 }
262 else if let Some(nested) = find_nested_jre_bundle(&jre_root).await {
263 nested
264 }
265 else {
266 jre_root.join("bin").join("java")
267 }
268 }
269 _ => jre_root.join("bin").join("java"),
270 };
271
272 if !java_binary.exists() {
273 return Err(JreError::NotFound {
274 path: java_binary.clone(),
275 });
276 }
277
278 Ok(java_binary)
279}
280
281#[cfg(target_os = "macos")]
283async fn find_nested_jre_bundle(jre_root: &Path) -> Option<PathBuf> {
284 let mut entries = fs::read_dir(jre_root).await.ok()?;
285
286 while let Ok(Some(entry)) = entries.next_entry().await {
287 let path = entry.path();
288 if path.is_dir() {
289 let name = path.file_name()?.to_str()?;
290 if name.ends_with(".jre") {
291 let java_path = path.join("Contents").join("Home").join("bin").join("java");
292 if java_path.exists() {
293 return Some(java_path);
294 }
295 }
296 }
297 }
298 None
299}
300
301#[cfg(not(target_os = "macos"))]
302async fn find_nested_jre_bundle(_jre_root: &Path) -> Option<PathBuf> {
303 None
304}
305
306#[cfg(unix)]
308async fn ensure_executable_permissions(binary_path: &Path) -> JreResult<()> {
309 use std::os::unix::fs::PermissionsExt;
310
311 let metadata = fs::metadata(binary_path).await?;
312 let current_permissions = metadata.permissions();
313
314 if current_permissions.mode() & 0o111 == 0 {
315 let mut new_permissions = current_permissions;
316 new_permissions.set_mode(0o755);
317 fs::set_permissions(binary_path, new_permissions).await?;
318 }
319
320 Ok(())
321}