use std::io::Cursor;
use std::path::{Path, PathBuf};
use crate::errors::{JreError, JreResult};
use path_absolutize::Absolutize;
use tokio::fs;
use lighty_core::system::{OperatingSystem, OS};
use lighty_core::download::download_file;
use lighty_core::extract::{tar_gz_extract, zip_extract};
use super::JavaDistribution;
#[cfg(feature = "events")]
use lighty_event::{EventBus, Event, JavaEvent};
pub async fn find_java_binary(
runtimes_folder: &Path,
distribution: &JavaDistribution,
version: &u8,
) -> JreResult<PathBuf> {
let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
let binary_path = locate_binary_in_directory(&runtime_dir).await?;
#[cfg(unix)]
ensure_executable_permissions(&binary_path).await?;
Ok(binary_path.absolutize()?.to_path_buf())
}
#[cfg(feature = "events")]
pub async fn jre_download<F>(
runtimes_folder: &Path,
distribution: &JavaDistribution,
version: &u8,
on_progress: F,
event_bus: Option<&EventBus>,
) -> JreResult<PathBuf>
where
F: Fn(u64, u64),
{
let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
prepare_installation_directory(&runtime_dir).await?;
let download_url = distribution
.get_download_url(version)
.await
.map_err(|e| JreError::Download(format!("Failed to get download URL: {}", e)))?;
if let Some(bus) = event_bus {
let response = lighty_core::hosts::HTTP_CLIENT
.get(&download_url)
.send()
.await
.map_err(|e| JreError::Download(format!("Failed to check file size: {}", e)))?;
let total_bytes = response.content_length().unwrap_or(0);
bus.emit(Event::Java(JavaEvent::JavaDownloadStarted {
distribution: distribution.get_name().to_string(),
version: *version,
total_bytes,
}));
}
let archive_bytes = {
let event_bus_ref = event_bus;
download_file(&download_url, |current, _total| {
on_progress(current, _total);
if let Some(bus) = event_bus_ref {
if current > 0 {
bus.emit(Event::Java(JavaEvent::JavaDownloadProgress {
bytes: current,
}));
}
}
})
.await
.map_err(|e| JreError::Download(format!("Download failed: {}", e)))?
};
if let Some(bus) = event_bus {
bus.emit(Event::Java(JavaEvent::JavaDownloadCompleted {
distribution: distribution.get_name().to_string(),
version: *version,
}));
}
if let Some(bus) = event_bus {
bus.emit(Event::Java(JavaEvent::JavaExtractionStarted {
distribution: distribution.get_name().to_string(),
version: *version,
}));
}
extract_archive(
&archive_bytes,
&runtime_dir,
event_bus,
).await?;
let binary_path = find_java_binary(runtimes_folder, distribution, version).await?;
if let Some(bus) = event_bus {
bus.emit(Event::Java(JavaEvent::JavaExtractionCompleted {
distribution: distribution.get_name().to_string(),
version: *version,
binary_path: binary_path.to_string_lossy().to_string(),
}));
}
Ok(binary_path)
}
#[cfg(not(feature = "events"))]
pub async fn jre_download<F>(
runtimes_folder: &Path,
distribution: &JavaDistribution,
version: &u8,
on_progress: F,
) -> JreResult<PathBuf>
where
F: Fn(u64, u64),
{
let runtime_dir = build_runtime_path(runtimes_folder, distribution, version);
prepare_installation_directory(&runtime_dir).await?;
let download_url = distribution
.get_download_url(version)
.await
.map_err(|e| JreError::Download(format!("Failed to get download URL: {}", e)))?;
let archive_bytes = download_file(&download_url, on_progress)
.await
.map_err(|e| JreError::Download(format!("Download failed: {}", e)))?;
extract_archive(&archive_bytes, &runtime_dir).await?;
find_java_binary(runtimes_folder, distribution, version).await
}
fn build_runtime_path(
runtimes_folder: &Path,
distribution: &JavaDistribution,
version: &u8,
) -> PathBuf {
let mut path = runtimes_folder.to_path_buf();
path.push(format!("{}_{}", distribution.get_name(), version));
path
}
async fn prepare_installation_directory(runtime_dir: &Path) -> JreResult<()> {
if runtime_dir.exists() {
fs::remove_dir_all(runtime_dir).await?;
}
fs::create_dir_all(runtime_dir).await?;
Ok(())
}
#[cfg(feature = "events")]
async fn extract_archive(
archive_bytes: &[u8],
destination: &Path,
event_bus: Option<&EventBus>,
) -> JreResult<()> {
let cursor = Cursor::new(archive_bytes);
match OS {
OperatingSystem::WINDOWS => {
zip_extract(cursor, destination, event_bus)
.await
.map_err(|e| JreError::Extraction(format!("ZIP extraction failed: {}", e)))?;
}
OperatingSystem::LINUX | OperatingSystem::OSX => {
tar_gz_extract(cursor, destination, event_bus)
.await
.map_err(|e| JreError::Extraction(format!("TAR.GZ extraction failed: {}", e)))?;
}
OperatingSystem::UNKNOWN => {
return Err(JreError::UnsupportedOS);
}
}
Ok(())
}
#[cfg(not(feature = "events"))]
async fn extract_archive(archive_bytes: &[u8], destination: &Path) -> JreResult<()> {
let cursor = Cursor::new(archive_bytes);
match OS {
OperatingSystem::WINDOWS => {
zip_extract(cursor, destination)
.await
.map_err(|e| JreError::Extraction(format!("ZIP extraction failed: {}", e)))?;
}
OperatingSystem::LINUX | OperatingSystem::OSX => {
tar_gz_extract(cursor, destination)
.await
.map_err(|e| JreError::Extraction(format!("TAR.GZ extraction failed: {}", e)))?;
}
OperatingSystem::UNKNOWN => {
return Err(JreError::UnsupportedOS);
}
}
Ok(())
}
async fn locate_binary_in_directory(runtime_dir: &Path) -> JreResult<PathBuf> {
let mut entries = fs::read_dir(runtime_dir).await?;
let jre_root = entries
.next_entry()
.await?
.ok_or_else(|| JreError::NotFound {
path: runtime_dir.to_path_buf(),
})?
.path();
let java_binary = match OS {
OperatingSystem::WINDOWS => jre_root.join("bin").join("java.exe"),
OperatingSystem::OSX => jre_root.join("Contents").join("Home").join("bin").join("java"),
_ => jre_root.join("bin").join("java"),
};
if !java_binary.exists() {
return Err(JreError::NotFound {
path: java_binary.clone(),
});
}
Ok(java_binary)
}
#[cfg(unix)]
async fn ensure_executable_permissions(binary_path: &Path) -> JreResult<()> {
use std::os::unix::fs::PermissionsExt;
let metadata = fs::metadata(binary_path).await?;
let current_permissions = metadata.permissions();
if current_permissions.mode() & 0o111 == 0 {
let mut new_permissions = current_permissions;
new_permissions.set_mode(0o755);
fs::set_permissions(binary_path, new_permissions).await?;
}
Ok(())
}