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