use std::fs::{self, File};
use std::io::{self, Write};
use std::path::Path;
use std::time::Instant;
use crate::archive::{open_archive, sanitize_relative_path};
use crate::codec::EncodingKind;
use crate::error::XzipError;
use crate::filter::PathFilter;
use crate::verbosity::Verbosity;
pub fn unpack_archive(
input_zip: &Path,
output_dir: &Path,
encoding: EncodingKind,
include: &[String],
exclude: &[String],
verbosity: Verbosity,
) -> Result<(), XzipError> {
let started = Instant::now();
let mut archive = open_archive(input_zip)?;
fs::create_dir_all(output_dir)?;
let filter = PathFilter::new(include, exclude)?;
let mut files_extracted = 0usize;
let mut dirs_created = 0usize;
for i in 0..archive.len() {
let mut entry = archive.by_index(i)?;
let raw_name = entry.name_raw();
let decoded_name = encoding.decode(raw_name)?;
if !filter.allows(&decoded_name) {
continue;
}
let safe_rel = sanitize_relative_path(&decoded_name)?;
let outpath = output_dir.join(&safe_rel);
if entry.is_dir() {
fs::create_dir_all(&outpath)?;
dirs_created += 1;
if verbosity.is_verbose() {
eprintln!("extracting: {decoded_name}");
}
continue;
}
if let Some(parent) = outpath.parent() {
fs::create_dir_all(parent)?;
}
if verbosity.is_verbose() {
if verbosity.is_extra() {
eprintln!(
"extracting: {decoded_name} ({} -> {} bytes)",
entry.size(),
entry.compressed_size()
);
} else {
eprintln!("extracting: {decoded_name}");
}
}
let mut outfile = File::create(&outpath)?;
io::copy(&mut entry, &mut outfile)?;
outfile.flush()?;
files_extracted += 1;
}
if verbosity.is_verbose() {
let elapsed = started.elapsed();
let file_label = if files_extracted == 1 {
"file"
} else {
"files"
};
eprintln!(
"{files_extracted} {file_label} extracted ({dirs_created} directories) in {:.2}s",
elapsed.as_secs_f64()
);
}
Ok(())
}