lighty_java/
jre_downloader.rs1use std::io::Cursor;
10use std::path::{Path, PathBuf};
11use crate::errors::{JreError, JreResult};
12use path_absolutize::Absolutize;
13use tokio::fs;
14
15use lighty_core::system::{OperatingSystem, OS};
16use lighty_core::download::download_file;
17use lighty_core::extract::{tar_gz_extract, zip_extract};
18
19use super::JavaDistribution;
20
21#[cfg(feature = "events")]
22use lighty_event::{EventBus, Event, JavaEvent};
23
24pub async fn find_java_binary(
37 runtimes_folder: &Path,
38 distribution: &JavaDistribution,
39 version: &u8,
40) -> JreResult<PathBuf> {
41 let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
42
43 let binary_path = locate_binary_in_directory(&runtime_dir).await?;
44
45 #[cfg(unix)]
47 ensure_executable_permissions(&binary_path).await?;
48
49 Ok(binary_path.absolutize()?.to_path_buf())
50}
51
52#[cfg(feature = "events")]
64pub async fn jre_download<F>(
65 runtimes_folder: &Path,
66 distribution: &JavaDistribution,
67 version: &u8,
68 on_progress: F,
69 event_bus: Option<&EventBus>,
70) -> JreResult<PathBuf>
71where
72 F: Fn(u64, u64),
73{
74 let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
75
76 prepare_installation_directory(&runtime_dir).await?;
78
79 let download_url = distribution
81 .get_download_url(version)
82 .await
83 .map_err(|e| JreError::Download(format!("Failed to get download URL: {}", e)))?;
84
85 if let Some(bus) = event_bus {
87 let response = lighty_core::hosts::HTTP_CLIENT
89 .get(&download_url)
90 .send()
91 .await
92 .map_err(|e| JreError::Download(format!("Failed to check file size: {}", e)))?;
93
94 let total_bytes = response.content_length().unwrap_or(0);
95
96 bus.emit(Event::Java(JavaEvent::JavaDownloadStarted {
97 distribution: distribution.get_name().to_string(),
98 version: *version,
99 total_bytes,
100 }));
101 }
102
103 let archive_bytes = {
105 let event_bus_ref = event_bus;
106 download_file(&download_url, |current, _total| {
107 on_progress(current, _total);
108 if let Some(bus) = event_bus_ref {
109 if current > 0 {
111 bus.emit(Event::Java(JavaEvent::JavaDownloadProgress {
112 bytes: current,
113 }));
114 }
115 }
116 })
117 .await
118 .map_err(|e| JreError::Download(format!("Download failed: {}", e)))?
119 };
120
121 if let Some(bus) = event_bus {
123 bus.emit(Event::Java(JavaEvent::JavaDownloadCompleted {
124 distribution: distribution.get_name().to_string(),
125 version: *version,
126 }));
127 }
128
129 if let Some(bus) = event_bus {
131 bus.emit(Event::Java(JavaEvent::JavaExtractionStarted {
132 distribution: distribution.get_name().to_string(),
133 version: *version,
134 }));
135 }
136
137 extract_archive(
139 &archive_bytes,
140 &runtime_dir,
141 event_bus,
142 ).await?;
143
144 let binary_path = find_java_binary(runtimes_folder, distribution, version).await?;
146
147 if let Some(bus) = event_bus {
149 bus.emit(Event::Java(JavaEvent::JavaExtractionCompleted {
150 distribution: distribution.get_name().to_string(),
151 version: *version,
152 binary_path: binary_path.to_string_lossy().to_string(),
153 }));
154 }
155
156 Ok(binary_path)
157}
158
159#[cfg(not(feature = "events"))]
170pub async fn jre_download<F>(
171 runtimes_folder: &Path,
172 distribution: &JavaDistribution,
173 version: &u8,
174 on_progress: F,
175) -> JreResult<PathBuf>
176where
177 F: Fn(u64, u64),
178{
179 let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
180
181 prepare_installation_directory(&runtime_dir).await?;
183
184 let download_url = distribution
186 .get_download_url(version)
187 .await
188 .map_err(|e| JreError::Download(format!("Failed to get download URL: {}", e)))?;
189
190 let archive_bytes = download_file(&download_url, on_progress)
192 .await
193 .map_err(|e| JreError::Download(format!("Download failed: {}", e)))?;
194
195 extract_archive(&archive_bytes, &runtime_dir).await?;
197
198 find_java_binary(runtimes_folder, distribution, version).await
200}
201
202fn build_runtime_path(
208 runtimes_folder: &Path,
209 distribution: &JavaDistribution,
210 version: &u8,
211) -> PathBuf {
212 let mut path = runtimes_folder.to_path_buf();
214 path.push(format!("{}_{}", distribution.get_name(), version));
215 path
216}
217
218async fn prepare_installation_directory(runtime_dir: &Path) -> JreResult<()> {
220 if runtime_dir.exists() {
221 fs::remove_dir_all(runtime_dir).await?;
222 }
223 fs::create_dir_all(runtime_dir).await?;
224 Ok(())
225}
226
227#[cfg(feature = "events")]
229async fn extract_archive(
230 archive_bytes: &[u8],
231 destination: &Path,
232 event_bus: Option<&EventBus>,
233) -> JreResult<()> {
234 let cursor = Cursor::new(archive_bytes);
235
236 match OS {
237 OperatingSystem::WINDOWS => {
238 zip_extract(cursor, destination, event_bus)
239 .await
240 .map_err(|e| JreError::Extraction(format!("ZIP extraction failed: {}", e)))?;
241 }
242 OperatingSystem::LINUX | OperatingSystem::OSX => {
243 tar_gz_extract(cursor, destination, event_bus)
244 .await
245 .map_err(|e| JreError::Extraction(format!("TAR.GZ extraction failed: {}", e)))?;
246 }
247 OperatingSystem::UNKNOWN => {
248 return Err(JreError::UnsupportedOS);
249 }
250 }
251
252 Ok(())
253}
254
255#[cfg(not(feature = "events"))]
257async fn extract_archive(archive_bytes: &[u8], destination: &Path) -> JreResult<()> {
258 let cursor = Cursor::new(archive_bytes);
259
260 match OS {
261 OperatingSystem::WINDOWS => {
262 zip_extract(cursor, destination)
263 .await
264 .map_err(|e| JreError::Extraction(format!("ZIP extraction failed: {}", e)))?;
265 }
266 OperatingSystem::LINUX | OperatingSystem::OSX => {
267 tar_gz_extract(cursor, destination)
268 .await
269 .map_err(|e| JreError::Extraction(format!("TAR.GZ extraction failed: {}", e)))?;
270 }
271 OperatingSystem::UNKNOWN => {
272 return Err(JreError::UnsupportedOS);
273 }
274 }
275
276 Ok(())
277}
278
279async fn locate_binary_in_directory(runtime_dir: &Path) -> JreResult<PathBuf> {
286 let mut entries = fs::read_dir(runtime_dir).await?;
288
289 let jre_root = entries
290 .next_entry()
291 .await?
292 .ok_or_else(|| JreError::NotFound {
293 path: runtime_dir.to_path_buf(),
294 })?
295 .path();
296
297 let java_binary = match OS {
299 OperatingSystem::WINDOWS => jre_root.join("bin").join("java.exe"),
300 OperatingSystem::OSX => jre_root.join("Contents").join("Home").join("bin").join("java"),
301 _ => jre_root.join("bin").join("java"),
302 };
303
304 if !java_binary.exists() {
306 return Err(JreError::NotFound {
307 path: java_binary.clone(),
308 });
309 }
310
311 Ok(java_binary)
312}
313
314#[cfg(unix)]
316async fn ensure_executable_permissions(binary_path: &Path) -> JreResult<()> {
317 use std::os::unix::fs::PermissionsExt;
318
319 let metadata = fs::metadata(binary_path).await?;
320 let current_permissions = metadata.permissions();
321
322 if current_permissions.mode() & 0o111 == 0 {
324 let mut new_permissions = current_permissions;
326 new_permissions.set_mode(0o755);
327 fs::set_permissions(binary_path, new_permissions).await?;
328 }
329
330 Ok(())
331}