java-manager 0.4.0-2b

A Rust library for managing and locating Java installations
Documentation
//! Archive extraction for downloaded JDK distributions.
//!
//! Supports ZIP archives on Windows and tar.gz archives on Linux/macOS.
//! Magic-byte detection selects the correct extractor automatically.

use std::fs;
use std::io::BufReader;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

#[cfg(not(target_os = "windows"))]
use {flate2::read::GzDecoder, tar::Archive};

#[cfg(target_os = "windows")]
use zip::ZipArchive;

/// Extracts a downloaded archive into `temp_dir`.
///
/// The archive format is detected from the first two magic bytes:
/// - `PK` → ZIP (Windows only)
/// - `0x1f 0x8b` → gzip-compressed tar (Linux / macOS only)
///
/// # Errors
///
/// Returns `Err(String)` if the archive cannot be opened, parsed, or
/// extracted, or if extraction is cancelled via `cancel_flag`.
pub(super) fn extract_downloaded_archive(
    temp_file_path: &Path,
    temp_dir: &Path,
    read_len: usize,
    magic: [u8; 2],
    cancel_flag: &Arc<AtomicBool>,
    on_progress: &mut dyn FnMut(u64, u64),
) -> Result<(), String> {
    #[cfg(target_os = "windows")]
    {
        if read_len >= 2 && &magic == b"PK" {
            return extract_zip(temp_file_path, temp_dir, cancel_flag, on_progress);
        }
        Err("not a valid ZIP archive".to_string())
    }

    #[cfg(not(target_os = "windows"))]
    {
        if read_len >= 2 && magic[0] == 0x1f && magic[1] == 0x8b {
            return extract_tar_gz(temp_file_path, temp_dir, cancel_flag);
        }
        Err("not a valid tar.gz archive".to_string())
    }
}

#[cfg(target_os = "windows")]
fn extract_zip(
    zip_path: &Path,
    target_dir: &Path,
    cancel_flag: &Arc<AtomicBool>,
    on_progress: &mut dyn FnMut(u64, u64),
) -> Result<(), String> {
    let file = fs::File::open(zip_path).map_err(|e| format!("open zip file: {e}"))?;
    let reader = BufReader::new(file);
    let mut archive = ZipArchive::new(reader).map_err(|e| format!("parse zip: {e}"))?;

    let total = archive.len() as u64;

    for i in 0..total as usize {
        if cancel_flag.load(Ordering::Relaxed) {
            return Err("cancelled by user".to_string());
        }
        let mut file = archive
            .by_index(i)
            .map_err(|e| format!("read zip entry: {e}"))?;
        let outpath = target_dir.join(file.mangled_name());

        if file.name().ends_with('/') {
            fs::create_dir_all(&outpath).map_err(|e| format!("create dir: {e}"))?;
        } else if let Some(p) = outpath.parent() {
            if !p.exists() {
                fs::create_dir_all(p).map_err(|e| format!("create parent dir: {e}"))?;
            }
            let mut outfile =
                fs::File::create(&outpath).map_err(|e| format!("create file: {e}"))?;
            std::io::copy(&mut file, &mut outfile).map_err(|e| format!("write file: {e}"))?;
        }

        on_progress(i as u64 + 1, total);
    }
    Ok(())
}

#[cfg(not(target_os = "windows"))]
fn extract_tar_gz(
    archive_path: &Path,
    target_dir: &Path,
    cancel_flag: &Arc<AtomicBool>,
) -> Result<(), String> {
    let file = fs::File::open(archive_path).map_err(|e| format!("open archive: {e}"))?;
    let buf_reader = BufReader::new(file);
    let tar = GzDecoder::new(buf_reader);
    let mut archive = Archive::new(tar);

    if cancel_flag.load(Ordering::Relaxed) {
        return Err("cancelled by user".to_string());
    }
    archive
        .unpack(target_dir)
        .map_err(|e| format!("extract: {e}"))?;
    Ok(())
}