diff options
Diffstat (limited to 'libs/nak/src/deps/tools.rs')
| -rw-r--r-- | libs/nak/src/deps/tools.rs | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/libs/nak/src/deps/tools.rs b/libs/nak/src/deps/tools.rs new file mode 100644 index 0000000..c97247c --- /dev/null +++ b/libs/nak/src/deps/tools.rs @@ -0,0 +1,174 @@ +//! Linux tool management (winetricks, cabextract) +//! +//! Handles downloading and managing Linux CLI tools. +//! Tools are stored in ~/.var/app/com.fluorine.manager/bin/ for Fluorine Manager. + +use std::error::Error; +use std::fs; +use std::io::Read; +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; +use std::process::Command; + +use crate::logging::{log_error, log_info, log_warning}; + +// ============================================================================ +// NaK Bin Directory (~/.var/app/com.fluorine.manager/bin/) +// ============================================================================ + +/// Get the tool bin directory path (~/.var/app/com.fluorine.manager/bin/) +/// This is accessible from both native and Flatpak environments. +pub fn get_nak_bin_path() -> PathBuf { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join(".var/app/com.fluorine.manager/bin") +} + +/// Check if a command exists (either in system PATH or tool bin) +pub fn check_command_available(cmd: &str) -> bool { + // Check system PATH first + if Command::new("which") + .arg(cmd) + .output() + .map(|o| o.status.success()) + .unwrap_or(false) + { + return true; + } + + // Check tool bin directory + let nak_bin = get_nak_bin_path().join(cmd); + nak_bin.exists() +} + +// ============================================================================ +// Winetricks +// ============================================================================ + +const WINETRICKS_URL: &str = + "https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks"; + +/// Get the path to winetricks (without downloading) +pub fn get_winetricks_path() -> PathBuf { + get_nak_bin_path().join("winetricks") +} + +/// Ensures winetricks is downloaded and up-to-date. +pub fn ensure_winetricks() -> Result<PathBuf, Box<dyn Error>> { + let bin_dir = get_nak_bin_path(); + let winetricks_path = bin_dir.join("winetricks"); + + fs::create_dir_all(&bin_dir)?; + + log_info("Checking for winetricks updates..."); + + match ureq::get(WINETRICKS_URL).call() { + Ok(response) => { + let mut new_content = Vec::new(); + response.into_reader().read_to_end(&mut new_content)?; + + let should_update = if winetricks_path.exists() { + let existing = fs::read(&winetricks_path).unwrap_or_default(); + existing != new_content + } else { + true + }; + + if should_update { + fs::write(&winetricks_path, &new_content)?; + + let mut perms = fs::metadata(&winetricks_path)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&winetricks_path, perms)?; + + if winetricks_path.exists() { + log_info("Winetricks updated to latest version"); + } else { + log_info(&format!("Winetricks downloaded to {:?}", winetricks_path)); + } + } + } + Err(e) => { + if winetricks_path.exists() { + log_warning(&format!("Failed to check winetricks updates: {}", e)); + } else { + return Err(format!("Failed to download winetricks: {}", e).into()); + } + } + } + + Ok(winetricks_path) +} + +// ============================================================================ +// Cabextract (required by winetricks for DirectX cabs) +// ============================================================================ + +const CABEXTRACT_URL: &str = + "https://github.com/SulfurNitride/NaK/releases/download/Cabextract/cabextract-linux-x86_64.zip"; + +/// Ensures cabextract is available (either system or downloaded). +pub fn ensure_cabextract() -> Result<PathBuf, Box<dyn Error>> { + // First check if system has cabextract + if Command::new("which") + .arg("cabextract") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) + { + return Ok(PathBuf::from("cabextract")); + } + + // Check if we already downloaded it + let bin_dir = get_nak_bin_path(); + let cabextract_path = bin_dir.join("cabextract"); + + if cabextract_path.exists() { + return Ok(cabextract_path); + } + + // Download cabextract zip + log_warning("System cabextract not found, downloading..."); + fs::create_dir_all(&bin_dir)?; + + let response = ureq::get(CABEXTRACT_URL).call().map_err(|e| { + format!( + "Failed to download cabextract: {}. Please install cabextract manually.", + e + ) + })?; + + let zip_path = bin_dir.join("cabextract.zip"); + let mut zip_file = fs::File::create(&zip_path)?; + std::io::copy(&mut response.into_reader(), &mut zip_file)?; + + let status = Command::new("unzip") + .arg("-o") + .arg(&zip_path) + .arg("-d") + .arg(&bin_dir) + .status()?; + + if !status.success() { + let _ = Command::new("python3") + .arg("-c") + .arg(format!( + "import zipfile; zipfile.ZipFile('{}').extractall('{}')", + zip_path.display(), + bin_dir.display() + )) + .status(); + } + + let _ = fs::remove_file(&zip_path); + + if cabextract_path.exists() { + let mut perms = fs::metadata(&cabextract_path)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&cabextract_path, perms)?; + log_info(&format!("cabextract downloaded to {:?}", cabextract_path)); + Ok(cabextract_path) + } else { + log_error("Failed to extract cabextract from zip"); + Err("Failed to extract cabextract from zip".into()) + } +} |
