use rayon::prelude::*;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering};
use walkdir::WalkDir;
use super::PakOperations;
use super::lspk::{PakPhase, PakProgress};
#[derive(Debug, Clone)]
pub struct BatchPakResult {
pub success_count: usize,
pub fail_count: usize,
pub results: Vec<String>,
}
pub fn find_pak_files<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
let mut pak_files: Vec<_> = WalkDir::new(dir)
.follow_links(true)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| {
e.path().is_file()
&& e.path()
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("pak"))
})
.map(|e| e.path().to_path_buf())
.collect();
pak_files.sort();
pak_files
}
pub fn find_packable_folders<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
let mut folders: Vec<_> = WalkDir::new(dir)
.follow_links(true)
.min_depth(1) .max_depth(1) .into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| {
let path = e.path();
if !path.is_dir() {
return false;
}
contains_files_recursive(path)
})
.map(|e| e.path().to_path_buf())
.collect();
folders.sort();
folders
}
fn contains_files_recursive(dir: &Path) -> bool {
WalkDir::new(dir)
.follow_links(true)
.into_iter()
.filter_map(std::result::Result::ok)
.any(|e| e.path().is_file())
}
pub fn batch_extract<F>(
pak_files: &[PathBuf],
source_base: &Path,
dest_base: &Path,
progress: F,
) -> BatchPakResult
where
F: Fn(&PakProgress) + Send + Sync,
{
let success_counter = AtomicUsize::new(0);
let fail_counter = AtomicUsize::new(0);
let processed = AtomicUsize::new(0);
let total = pak_files.len();
let results: Vec<String> = pak_files
.par_iter()
.map(|pak_path| {
let relative_path = pak_path
.strip_prefix(source_base)
.unwrap_or(pak_path.as_path());
let display_path = relative_path.to_string_lossy();
let current = processed.fetch_add(1, Ordering::SeqCst) + 1;
progress(&PakProgress::with_file(
PakPhase::DecompressingFiles,
current,
total,
display_path.to_string(),
));
let relative_parent = relative_path.parent().unwrap_or(Path::new(""));
let pak_stem = pak_path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let pak_dest = dest_base.join(relative_parent).join(&pak_stem);
if let Err(e) = std::fs::create_dir_all(&pak_dest) {
fail_counter.fetch_add(1, Ordering::SeqCst);
return format!("Failed to create folder for {display_path}: {e}");
}
let pak_str = pak_path.to_string_lossy().to_string();
let dest_str = pak_dest.to_string_lossy().to_string();
match PakOperations::extract(&pak_str, &dest_str) {
Ok(()) => {
success_counter.fetch_add(1, Ordering::SeqCst);
format!("Extracted: {display_path}")
}
Err(e) => {
fail_counter.fetch_add(1, Ordering::SeqCst);
format!("Failed {display_path}: {e}")
}
}
})
.collect();
BatchPakResult {
success_count: success_counter.load(Ordering::SeqCst),
fail_count: fail_counter.load(Ordering::SeqCst),
results,
}
}
pub fn batch_create<F>(
folders: &[PathBuf],
source_base: &Path,
dest_base: &Path,
progress: F,
) -> BatchPakResult
where
F: Fn(&PakProgress) + Send + Sync,
{
let success_counter = AtomicUsize::new(0);
let fail_counter = AtomicUsize::new(0);
let processed = AtomicUsize::new(0);
let total = folders.len();
let results: Vec<String> = folders
.par_iter()
.map(|folder_path| {
let folder_name = folder_path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
let relative_path = folder_path
.strip_prefix(source_base)
.unwrap_or(folder_path.as_path());
let display_path = relative_path.to_string_lossy();
let current = processed.fetch_add(1, Ordering::SeqCst) + 1;
progress(&PakProgress::with_file(
PakPhase::CompressingFiles,
current,
total,
display_path.to_string(),
));
let relative_parent = relative_path.parent().unwrap_or(Path::new(""));
let pak_dest_dir = dest_base.join(relative_parent);
if let Err(e) = std::fs::create_dir_all(&pak_dest_dir) {
fail_counter.fetch_add(1, Ordering::SeqCst);
return format!("Failed to create dir for {display_path}: {e}");
}
let pak_path = pak_dest_dir.join(format!("{folder_name}.pak"));
let folder_str = folder_path.to_string_lossy().to_string();
let pak_str = pak_path.to_string_lossy().to_string();
match PakOperations::create(&folder_str, &pak_str) {
Ok(()) => {
success_counter.fetch_add(1, Ordering::SeqCst);
format!("Created: {display_path}.pak")
}
Err(e) => {
fail_counter.fetch_add(1, Ordering::SeqCst);
format!("Failed {display_path}: {e}")
}
}
})
.collect();
BatchPakResult {
success_count: success_counter.load(Ordering::SeqCst),
fail_count: fail_counter.load(Ordering::SeqCst),
results,
}
}