#![allow(clippy::needless_pass_by_value, clippy::collapsible_if)]
use rayon::prelude::*;
use std::path::{Path, PathBuf};
use super::lspk::{PakPhase, PakProgress};
use super::pak_tools::{PakOperations, ProgressCallback};
use crate::error::Result;
use crate::gr2_extraction::{Gr2ExtractionOptions, Gr2ExtractionResult, process_extracted_gr2};
#[derive(Debug, Clone)]
pub struct SmartExtractionResult {
pub files_extracted: usize,
pub gr2s_processed: usize,
pub glb_files_created: usize,
pub textures_extracted: usize,
pub gr2_folders: Vec<PathBuf>,
pub warnings: Vec<String>,
}
impl SmartExtractionResult {
fn new() -> Self {
Self {
files_extracted: 0,
gr2s_processed: 0,
glb_files_created: 0,
textures_extracted: 0,
gr2_folders: Vec::new(),
warnings: Vec::new(),
}
}
}
pub fn extract_files_smart<P: AsRef<Path>, S: AsRef<str>>(
pak_path: P,
output_dir: P,
file_paths: &[S],
options: Gr2ExtractionOptions,
progress: ProgressCallback,
) -> Result<SmartExtractionResult> {
let pak_path = pak_path.as_ref();
let output_dir = output_dir.as_ref();
let mut result = SmartExtractionResult::new();
if file_paths.is_empty() {
return Ok(result);
}
progress(&PakProgress {
phase: PakPhase::DecompressingFiles,
current: 0,
total: file_paths.len(),
current_file: None,
});
PakOperations::extract_files_with_progress(pak_path, output_dir, file_paths, progress)?;
result.files_extracted = file_paths.len();
if !options.has_gr2_processing() {
return Ok(result);
}
let gr2_paths: Vec<PathBuf> = file_paths
.iter()
.filter(|p| {
let path = p.as_ref().to_lowercase();
path.ends_with(".gr2")
})
.map(|p| output_dir.join(p.as_ref()))
.filter(|p| p.exists())
.collect();
if gr2_paths.is_empty() {
return Ok(result);
}
progress(&PakProgress {
phase: PakPhase::WritingFiles,
current: 0,
total: gr2_paths.len(),
current_file: Some("Processing GR2 files...".to_string()),
});
let processing_results: Vec<(PathBuf, std::result::Result<Gr2ExtractionResult, String>)> =
gr2_paths
.par_iter()
.map(|gr2_path| {
let folder_result = process_single_gr2(gr2_path, output_dir, &options);
(gr2_path.clone(), folder_result)
})
.collect();
for (gr2_path, process_result) in processing_results {
match process_result {
Ok(proc_result) => {
result.gr2s_processed += 1;
if proc_result.glb_path.is_some() {
result.glb_files_created += 1;
}
result.textures_extracted += proc_result.texture_paths.len();
if let Some(folder) = gr2_path.parent() {
if !result.gr2_folders.contains(&folder.to_path_buf()) {
result.gr2_folders.push(folder.to_path_buf());
}
}
result.warnings.extend(proc_result.warnings);
}
Err(e) => {
let path_display = gr2_path.display();
result
.warnings
.push(format!("Failed to process {path_display}: {e}"));
}
}
}
Ok(result)
}
fn process_single_gr2(
gr2_path: &Path,
output_base: &Path,
options: &Gr2ExtractionOptions,
) -> std::result::Result<Gr2ExtractionResult, String> {
let gr2_filename = gr2_path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| "Invalid GR2 filename".to_string())?;
let folder_name = gr2_filename
.trim_end_matches(".GR2")
.trim_end_matches(".gr2");
let gr2_folder = output_base.join(folder_name);
std::fs::create_dir_all(&gr2_folder).map_err(|e| format!("Failed to create folder: {e}"))?;
let new_gr2_path = gr2_folder.join(gr2_filename);
if gr2_path != new_gr2_path {
std::fs::rename(gr2_path, &new_gr2_path).map_err(|e| format!("Failed to move GR2: {e}"))?;
cleanup_empty_parent_dirs(gr2_path, output_base);
}
let result = process_extracted_gr2(&new_gr2_path, options).map_err(|e| e.to_string())?;
if !options.keep_original_gr2 && result.glb_path.is_some() {
let _ = std::fs::remove_file(&new_gr2_path);
}
Ok(result)
}
fn cleanup_empty_parent_dirs(file_path: &Path, base_dir: &Path) {
let mut current = file_path.parent();
while let Some(dir) = current {
if dir == base_dir || dir.as_os_str().is_empty() {
break;
}
if std::fs::remove_dir(dir).is_err() {
break; }
current = dir.parent();
}
}
pub fn extract_pak_smart<P: AsRef<Path>>(
pak_path: P,
output_dir: P,
options: Gr2ExtractionOptions,
progress: ProgressCallback,
) -> Result<SmartExtractionResult> {
let pak_path = pak_path.as_ref();
let output_dir = output_dir.as_ref();
let all_files = PakOperations::list(pak_path)?;
extract_files_smart(pak_path, output_dir, &all_files, options, progress)
}