feat(workshop): backup files before extraction

This commit is contained in:
neoapps-dev
2026-09-16 17:22:46 +03:00
parent 6ae0e5c3d7
commit 5c2311a422
+85 -58
View File
@@ -102,58 +102,21 @@ async fn download_and_assemble(
Ok(combined) Ok(combined)
} }
fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec<String>, bool), String> { fn list_archive(zip_tmp: &Path) -> Result<Vec<String>, String> {
let zip_str = zip_tmp.to_str().unwrap_or(""); let zip_str = zip_tmp.to_str().unwrap_or("");
if cfg!(target_os = "linux") { if cfg!(target_os = "linux") {
let mut extract_ok = false;
let mut files: Vec<String> = Vec::new();
if let Ok(out) = std::process::Command::new("bsdtar") if let Ok(out) = std::process::Command::new("bsdtar")
.args(["-tf", zip_str]) .args(["-tf", zip_str])
.output() .output()
{ {
if out.status.success() { if out.status.success() {
let listing = String::from_utf8_lossy(&out.stdout); let listing = String::from_utf8_lossy(&out.stdout);
files = listing.lines() return Ok(listing.lines()
.map(|l| l.trim()) .map(|l| l.trim().to_string())
.filter(|l| !l.is_empty() && !l.ends_with('/')) .filter(|l| !l.is_empty() && !l.ends_with('/'))
.map(|l| dest_dir.join(l).to_string_lossy().to_string()) .collect());
.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();
} }
} }
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::<u64>().ok()?;
parts.next()?;
parts.next()?;
Some(parts.collect::<Vec<&str>>().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") let unzip_list = std::process::Command::new("unzip")
.args(["-l", zip_str]) .args(["-l", zip_str])
.output() .output()
@@ -162,7 +125,7 @@ fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec<String>, bool
return Err(format!("Failed to list contents of {}", zip_str)); return Err(format!("Failed to list contents of {}", zip_str));
} }
let listing = String::from_utf8_lossy(&unzip_list.stdout); let listing = String::from_utf8_lossy(&unzip_list.stdout);
let files: Vec<String> = listing.lines() Ok(listing.lines()
.filter_map(|l| { .filter_map(|l| {
let mut parts = l.trim().split_whitespace(); let mut parts = l.trim().split_whitespace();
let size_str = parts.next()?; let size_str = parts.next()?;
@@ -172,29 +135,76 @@ fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<(Vec<String>, bool
Some(parts.collect::<Vec<&str>>().join(" ")) Some(parts.collect::<Vec<&str>>().join(" "))
}) })
.filter(|l| !l.ends_with('/') && !l.contains('*')) .filter(|l| !l.ends_with('/') && !l.contains('*'))
.map(|l| dest_dir.join(l).to_string_lossy().to_string()) .collect())
.collect(); } else if cfg!(target_os = "android") {
let st = std::process::Command::new("unzip") let unzip_list = std::process::Command::new("unzip")
.args(["-o", zip_str, "-d", dest_dir.to_str().unwrap()]) .args(["-l", zip_str])
.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()])
.output() .output()
.map_err(|e| e.to_string())?; .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::<u64>().ok()?;
parts.next()?;
parts.next()?;
Some(parts.collect::<Vec<&str>>().join(" "))
})
.filter(|l| !l.ends_with('/') && !l.contains('*'))
.collect())
} else {
let listing = std::process::Command::new("tar") let listing = std::process::Command::new("tar")
.args(["-tf", zip_str]) .args(["-tf", zip_str])
.output() .output()
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let listing_str = String::from_utf8_lossy(&listing.stdout); let listing_str = String::from_utf8_lossy(&listing.stdout);
let files: Vec<String> = listing_str.lines() Ok(listing_str.lines()
.map(|l| l.trim()) .map(|l| l.trim().to_string())
.filter(|l| !l.is_empty() && !l.ends_with('/')) .filter(|l| !l.is_empty() && !l.ends_with('/'))
.map(|l| dest_dir.join(l).to_string_lossy().to_string()) .collect())
.collect(); }
Ok((files, st.status.success())) }
fn extract_archive(zip_tmp: &Path, dest_dir: &Path) -> Result<bool, String> {
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())?; 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 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 { if !extract_ok {
let _ = fs::remove_dir_all(&tmp_dir); let _ = fs::remove_dir_all(&tmp_dir);
return Err(format!("Extraction failed for {}", parts[0])); return Err(format!("Extraction failed for {}", parts[0]));
} }
let extracted_files: Vec<String> = files.iter()
.map(|f| dest_dir.join(f).to_string_lossy().to_string())
.collect();
for f in &extracted_files { for f in &extracted_files {
if !workshop_files.contains(f) { 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() { if path.is_file() {
let _ = fs::remove_file(&path); let _ = fs::remove_file(&path);
} }
let bak = format!("{}.bak", path.to_string_lossy());
if Path::new(&bak).is_file() {
let _ = fs::rename(&bak, &path);
}
} }
} }