// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ use std::collections::HashMap; use std::fs::File; use std::path::Path; use bzip2::read::BzDecoder; use flate2::read::GzDecoder; use tar::Archive as TarArchive; use tauri::async_runtime::spawn_blocking; use xz2::read::XzDecoder; use zip::ZipArchive; /// Strip a single top-level directory if the target directory contains only one subdirectory. /// This is useful after extracting archives that create an unnecessary parent folder. /// Returns true if flattening was performed, false otherwise. fn strip_single_top_level_directory(target_dir: &Path) -> Result { use std::fs; let entries: Vec<_> = fs::read_dir(target_dir)? .filter_map(|entry| entry.ok()) .collect(); // If there's exactly one entry and it's a directory, flatten it if entries.len() == 1 { let entry = &entries[0]; if entry.path().is_dir() { let subdir_path = entry.path(); let subdir_name = entry.file_name(); // Move all items from subdirectory to target directory for sub_entry in fs::read_dir(&subdir_path)? { let sub_entry = sub_entry?; let source_path = sub_entry.path(); let dest_path = target_dir.join(sub_entry.file_name()); fs::rename(&source_path, &dest_path)?; } // Remove the now-empty subdirectory fs::remove_dir(&subdir_path)?; println!( "Flattened directory structure: removed {}/", subdir_name.to_string_lossy() ); return Ok(true); } } Ok(false) } #[tauri::command] fn launch_binary( path: String, args: Vec, env: Option>, ) -> Result { use std::process::{Command, Stdio}; let mut command = Command::new(path); command .args(args) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); if let Some(env_vars) = env { command.envs(env_vars); } command .spawn() .map(|_| "Launched".into()) .map_err(|e| e.to_string()) } /// Extract a ZIP archive to a target directory #[tauri::command] fn extract_zip(archive_path: String, target_dir: String) -> Result { let archive_file = File::open(&archive_path) .map_err(|e| format!("Failed to open ZIP archive at {}: {}", archive_path, e))?; let mut archive = ZipArchive::new(archive_file) .map_err(|e| format!("Failed to read ZIP archive at {}: {}", archive_path, e))?; for i in 0..archive.len() { let mut file = archive .by_index(i) .map_err(|e| format!("Failed to read file {} in ZIP archive: {}", i, e))?; let outpath = Path::new(&target_dir).join(file.mangled_name()); if file.name().ends_with('/') { // Directory entry std::fs::create_dir_all(&outpath) .map_err(|e| format!("Failed to create directory {}: {}", outpath.display(), e))?; } else { // File entry if let Some(parent) = outpath.parent() { std::fs::create_dir_all(parent).map_err(|e| { format!( "Failed to create parent directory {}: {}", parent.display(), e ) })?; } let mut outfile = File::create(&outpath) .map_err(|e| format!("Failed to create file {}: {}", outpath.display(), e))?; std::io::copy(&mut file, &mut outfile) .map_err(|e| format!("Failed to write file {}: {}", outpath.display(), e))?; } } // Strip single top-level directory if archive created one let target_path = Path::new(&target_dir); if let Err(e) = strip_single_top_level_directory(target_path) { println!("Warning: Failed to strip top-level directory: {}", e); } Ok(format!( "Successfully extracted ZIP archive to {}", target_dir )) } /// Extract a TAR archive (uncompressed) #[tauri::command] fn extract_tar(archive_path: String, target_dir: String) -> Result { let archive_file = File::open(&archive_path) .map_err(|e| format!("Failed to open TAR archive at {}: {}", archive_path, e))?; let mut archive = TarArchive::new(archive_file); archive .unpack(&target_dir) .map_err(|e| format!("Failed to extract TAR archive to {}: {}", target_dir, e))?; // Strip single top-level directory if archive created one let target_path = Path::new(&target_dir); if let Err(e) = strip_single_top_level_directory(target_path) { println!("Warning: Failed to strip top-level directory: {}", e); } Ok(format!( "Successfully extracted TAR archive to {}", target_dir )) } /// Extract a TAR.GZ archive #[tauri::command] fn extract_tar_gz(archive_path: String, target_dir: String) -> Result { let archive_file = File::open(&archive_path) .map_err(|e| format!("Failed to open TAR.GZ archive at {}: {}", archive_path, e))?; let gz_decoder = GzDecoder::new(archive_file); let mut archive = TarArchive::new(gz_decoder); archive .unpack(&target_dir) .map_err(|e| format!("Failed to extract TAR.GZ archive to {}: {}", target_dir, e))?; // Strip single top-level directory if archive created one let target_path = Path::new(&target_dir); if let Err(e) = strip_single_top_level_directory(target_path) { println!("Warning: Failed to strip top-level directory: {}", e); } Ok(format!( "Successfully extracted TAR.GZ archive to {}", target_dir )) } /// Extract a TAR.XZ archive #[tauri::command] fn extract_tar_xz(archive_path: String, target_dir: String) -> Result { let archive_file = File::open(&archive_path) .map_err(|e| format!("Failed to open TAR.XZ archive at {}: {}", archive_path, e))?; let xz_decoder = XzDecoder::new(archive_file); let mut archive = TarArchive::new(xz_decoder); archive .unpack(&target_dir) .map_err(|e| format!("Failed to extract TAR.XZ archive to {}: {}", target_dir, e))?; // Strip single top-level directory if archive created one let target_path = Path::new(&target_dir); if let Err(e) = strip_single_top_level_directory(target_path) { println!("Warning: Failed to strip top-level directory: {}", e); } Ok(format!( "Successfully extracted TAR.XZ archive to {}", target_dir )) } /// Extract a TAR.BZ2 archive #[tauri::command] fn extract_tar_bz2(archive_path: String, target_dir: String) -> Result { let archive_file = File::open(&archive_path) .map_err(|e| format!("Failed to open TAR.BZ2 archive at {}: {}", archive_path, e))?; let bz_decoder = BzDecoder::new(archive_file); let mut archive = TarArchive::new(bz_decoder); archive .unpack(&target_dir) .map_err(|e| format!("Failed to extract TAR.BZ2 archive to {}: {}", target_dir, e))?; // Strip single top-level directory if archive created one let target_path = Path::new(&target_dir); if let Err(e) = strip_single_top_level_directory(target_path) { println!("Warning: Failed to strip top-level directory: {}", e); } Ok(format!( "Successfully extracted TAR.BZ2 archive to {}", target_dir )) } /// Detect archive type and extract it automatically #[tauri::command(async)] async fn extract_archive(archive_path: String, target_dir: String) -> Result { // Run extraction in a blocking task to avoid hanging the UI spawn_blocking(move || { let path = Path::new(&archive_path); // Get file extension(s) to determine archive type let extension = path .extension() .and_then(|ext| ext.to_str()) .unwrap_or("") .to_lowercase(); let filename = path .file_name() .and_then(|name| name.to_str()) .unwrap_or("") .to_lowercase(); // Check for multi-part extensions like .tar.gz if filename.ends_with(".tar.xz") || filename.ends_with(".txz") { extract_tar_xz(archive_path, target_dir) } else if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") { extract_tar_gz(archive_path, target_dir) } else if filename.ends_with(".tar.bz2") || filename.ends_with(".tbz2") || filename.ends_with(".tbz") { extract_tar_bz2(archive_path, target_dir) } else if extension == "tar" { extract_tar(archive_path, target_dir) } else if extension == "zip" { extract_zip(archive_path, target_dir) } else { // Try ZIP first (common), then TAR match extract_zip(archive_path.clone(), target_dir.clone()) { Ok(result) => Ok(result), Err(_) => { // Try as uncompressed TAR match extract_tar(archive_path, target_dir) { Ok(result) => Ok(result), Err(e) => Err(format!("Failed to extract archive. Supported formats: .zip, .tar, .tar.gz, .tar.xz, .tar.bz2. Error: {}", e)) } } } } }) .await .map_err(|e| format!("Blocking task failed: {}", e))? } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_upload::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_persisted_scope::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_window_state::Builder::new().build()) .plugin(tauri_plugin_opener::init()) .setup(|app| { #[cfg(desktop)] app.handle() .plugin(tauri_plugin_window_state::Builder::default().build())?; Ok(()) }) .invoke_handler(tauri::generate_handler![ extract_zip, extract_tar, extract_tar_gz, extract_tar_xz, extract_tar_bz2, extract_archive, launch_binary ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }