From 5c2311a422580853951b8af76ca27a9345801c82 Mon Sep 17 00:00:00 2001 From: neoapps-dev Date: Wed, 16 Sep 2026 17:22:46 +0300 Subject: [PATCH] feat(workshop): backup files before extraction --- src-tauri/src/commands/workshop.rs | 143 +++++++++++++++++------------ 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/src-tauri/src/commands/workshop.rs b/src-tauri/src/commands/workshop.rs index f91d048..8bf5220 100644 --- a/src-tauri/src/commands/workshop.rs +++ b/src-tauri/src/commands/workshop.rs @@ -102,58 +102,21 @@ async fn download_and_assemble( Ok(combined) } -fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec, bool), String> { +fn list_archive(zip_tmp: &Path) -> Result, String> { let zip_str = zip_tmp.to_str().unwrap_or(""); if cfg!(target_os = "linux") { - let mut extract_ok = false; - let mut files: Vec = Vec::new(); if let Ok(out) = std::process::Command::new("bsdtar") .args(["-tf", zip_str]) .output() { if out.status.success() { let listing = String::from_utf8_lossy(&out.stdout); - files = listing.lines() - .map(|l| l.trim()) + return Ok(listing.lines() + .map(|l| l.trim().to_string()) .filter(|l| !l.is_empty() && !l.ends_with('/')) - .map(|l| dest_dir.join(l).to_string_lossy().to_string()) - .collect(); - let st = std::process::Command::new("bsdtar") - .args(["-xf", zip_str, "-C", dest_dir.to_str().unwrap()]) - .status() - .map_err(|e| e.to_string())?; - extract_ok = st.success(); + .collect()); } } - if !extract_ok { - let unzip_list = std::process::Command::new("unzip") - .args(["-l", zip_str]) - .output() - .map_err(|e| e.to_string())?; - if !unzip_list.status.success() { - return Err(format!("Failed to list contents of {}", zip_str)); - } - let listing = String::from_utf8_lossy(&unzip_list.stdout); - files = listing.lines() - .filter_map(|l| { - let mut parts = l.trim().split_whitespace(); - let size_str = parts.next()?; - size_str.parse::().ok()?; - parts.next()?; - parts.next()?; - Some(parts.collect::>().join(" ")) - }) - .filter(|l| !l.ends_with('/') && !l.contains('*')) - .map(|l| dest_dir.join(l).to_string_lossy().to_string()) - .collect(); - let st = std::process::Command::new("unzip") - .args(["-o", zip_str, "-d", dest_dir.to_str().unwrap()]) - .status() - .map_err(|e| e.to_string())?; - extract_ok = st.success(); - } - Ok((files, extract_ok)) - } else if cfg!(target_os = "android") { let unzip_list = std::process::Command::new("unzip") .args(["-l", zip_str]) .output() @@ -162,7 +125,7 @@ fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec, bool return Err(format!("Failed to list contents of {}", zip_str)); } let listing = String::from_utf8_lossy(&unzip_list.stdout); - let files: Vec = listing.lines() + Ok(listing.lines() .filter_map(|l| { let mut parts = l.trim().split_whitespace(); let size_str = parts.next()?; @@ -172,29 +135,76 @@ fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec, bool Some(parts.collect::>().join(" ")) }) .filter(|l| !l.ends_with('/') && !l.contains('*')) - .map(|l| dest_dir.join(l).to_string_lossy().to_string()) - .collect(); - let st = std::process::Command::new("unzip") - .args(["-o", zip_str, "-d", dest_dir.to_str().unwrap()]) - .status() - .map_err(|e| e.to_string())?; - Ok((files, st.success())) - } else { - let st = std::process::Command::new("tar") - .args(["-xf", zip_str, "-C", dest_dir.to_str().unwrap()]) + .collect()) + } else if cfg!(target_os = "android") { + let unzip_list = std::process::Command::new("unzip") + .args(["-l", zip_str]) .output() .map_err(|e| e.to_string())?; + if !unzip_list.status.success() { + return Err(format!("Failed to list contents of {}", zip_str)); + } + let listing = String::from_utf8_lossy(&unzip_list.stdout); + Ok(listing.lines() + .filter_map(|l| { + let mut parts = l.trim().split_whitespace(); + let size_str = parts.next()?; + size_str.parse::().ok()?; + parts.next()?; + parts.next()?; + Some(parts.collect::>().join(" ")) + }) + .filter(|l| !l.ends_with('/') && !l.contains('*')) + .collect()) + } else { let listing = std::process::Command::new("tar") .args(["-tf", zip_str]) .output() .map_err(|e| e.to_string())?; let listing_str = String::from_utf8_lossy(&listing.stdout); - let files: Vec = listing_str.lines() - .map(|l| l.trim()) + Ok(listing_str.lines() + .map(|l| l.trim().to_string()) .filter(|l| !l.is_empty() && !l.ends_with('/')) - .map(|l| dest_dir.join(l).to_string_lossy().to_string()) - .collect(); - Ok((files, st.status.success())) + .collect()) + } +} + +fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result { + let zip_str = zip_tmp.to_str().unwrap_or(""); + if cfg!(target_os = "linux") { + let mut extract_ok = false; + if let Ok(out) = std::process::Command::new("bsdtar") + .args(["-tf", zip_str]) + .output() + { + if out.status.success() { + let st = std::process::Command::new("bsdtar") + .args(["-xf", zip_str, "-C", dest_dir.to_str().unwrap()]) + .status() + .map_err(|e| e.to_string())?; + extract_ok = st.success(); + } + } + if !extract_ok { + let st = std::process::Command::new("unzip") + .args(["-o", zip_str, "-d", dest_dir.to_str().unwrap()]) + .status() + .map_err(|e| e.to_string())?; + extract_ok = st.success(); + } + Ok(extract_ok) + } else if cfg!(target_os = "android") { + let st = std::process::Command::new("unzip") + .args(["-o", zip_str, "-d", dest_dir.to_str().unwrap()]) + .status() + .map_err(|e| e.to_string())?; + Ok(st.success()) + } else { + let st = std::process::Command::new("tar") + .args(["-xf", zip_str, "-C", dest_dir.to_str().unwrap()]) + .status() + .map_err(|e| e.to_string())?; + Ok(st.success()) } } @@ -242,11 +252,24 @@ pub async fn workshop_install(app: AppHandle, request: WorkshopInstallRequest) - fs::create_dir_all(&dest_dir).map_err(|e| e.to_string())?; let archive_tmp = download_and_assemble(&app, &tmp_dir, &parts, &raw_base, &request.package_id, &mut done_parts, total_parts).await?; - let (extracted_files, extract_ok) = extract_archive(&archive_tmp, &dest_dir)?; + let files = list_archive(&archive_tmp)?; + for f in &files { + let full = dest_dir.join(f); + if full.is_file() { + let bak = format!("{}.bak", full.to_string_lossy()); + if !Path::new(&bak).exists() { + let _ = fs::rename(&full, &bak); + } + } + } + let extract_ok = extract_archive(&archive_tmp, &dest_dir)?; if !extract_ok { let _ = fs::remove_dir_all(&tmp_dir); return Err(format!("Extraction failed for {}", parts[0])); } + let extracted_files: Vec = files.iter() + .map(|f| dest_dir.join(f).to_string_lossy().to_string()) + .collect(); for f in &extracted_files { if !workshop_files.contains(f) { @@ -292,6 +315,10 @@ pub async fn workshop_uninstall(app: AppHandle, instance_id: String, package_id: if path.is_file() { let _ = fs::remove_file(&path); } + let bak = format!("{}.bak", path.to_string_lossy()); + if Path::new(&bak).is_file() { + let _ = fs::rename(&bak, &path); + } } }