use crate::archive::{ArchiveInfo, FileInfo};
use crate::crypto::DEFAULT_CHUNK_SIZE;
use crate::error::Result;
use crate::progress::ProgressCallback;
use std::path::PathBuf;
pub struct ArchiveExtractor {
archive_path: PathBuf,
password: Option<String>,
chunk_size: usize,
progress_callback: Option<ProgressCallback>,
output_dir: Option<PathBuf>,
}
impl ArchiveExtractor {
pub fn new<P: Into<PathBuf>>(archive_path: P) -> Self {
Self {
archive_path: archive_path.into(),
password: None,
chunk_size: DEFAULT_CHUNK_SIZE,
progress_callback: None,
output_dir: None,
}
}
pub fn with_password<S: Into<String>>(mut self, password: S) -> Self {
self.password = Some(password.into());
self
}
pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
self.chunk_size = chunk_size;
self
}
pub fn with_progress_callback<F>(mut self, callback: F) -> Self
where
F: FnMut(&crate::progress::ProgressInfo) + Send + Sync + 'static,
{
self.progress_callback = Some(Box::new(callback));
self
}
pub fn with_output_dir<P: Into<PathBuf>>(mut self, output_dir: P) -> Self {
self.output_dir = Some(output_dir.into());
self
}
pub fn extract_all(self) -> Result<ArchiveInfo> {
crate::compress::validate_chunk_size(self.chunk_size)?;
let output_dir = self
.output_dir
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
crate::archive::simple::extract_archive(
&self.archive_path,
&output_dir,
self.password.as_deref(),
)
}
pub fn extract_file<P: AsRef<std::path::Path>>(
self,
file_path: &str,
output_path: P,
) -> Result<()> {
crate::compress::validate_chunk_size(self.chunk_size)?;
crate::archive::simple::extract_file(
&self.archive_path,
file_path,
output_path.as_ref(),
self.password.as_deref(),
)
}
pub fn list_contents(self) -> Result<(ArchiveInfo, Vec<FileInfo>)> {
crate::archive::simple::list_archive_contents(&self.archive_path)
}
}
impl From<&str> for ArchiveExtractor {
fn from(path: &str) -> Self {
Self::new(path)
}
}
impl From<String> for ArchiveExtractor {
fn from(path: String) -> Self {
Self::new(path)
}
}
impl From<PathBuf> for ArchiveExtractor {
fn from(path: PathBuf) -> Self {
Self::new(path)
}
}
impl std::fmt::Debug for ArchiveExtractor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArchiveExtractor")
.field("archive_path", &self.archive_path)
.field("password", &self.password.is_some())
.field("chunk_size", &self.chunk_size)
.field("progress_callback", &self.progress_callback.is_some())
.field("output_dir", &self.output_dir)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extractor_creation() {
let extractor = ArchiveExtractor::new("test.small");
assert_eq!(extractor.archive_path, PathBuf::from("test.small"));
assert!(extractor.password.is_none());
assert_eq!(extractor.chunk_size, DEFAULT_CHUNK_SIZE);
assert!(extractor.progress_callback.is_none());
assert!(extractor.output_dir.is_none());
}
#[test]
fn test_extractor_from_conversions() {
let extractor1 = ArchiveExtractor::from("test.small");
assert_eq!(extractor1.archive_path, PathBuf::from("test.small"));
let extractor2 = ArchiveExtractor::from("test.small".to_string());
assert_eq!(extractor2.archive_path, PathBuf::from("test.small"));
let path = PathBuf::from("test.small");
let extractor3 = ArchiveExtractor::from(path.clone());
assert_eq!(extractor3.archive_path, path);
}
#[test]
fn test_extractor_chain() {
let extractor = ArchiveExtractor::new("test.small")
.with_password("secret")
.with_chunk_size(1024 * 1024)
.with_output_dir("extracted/");
assert_eq!(extractor.archive_path, PathBuf::from("test.small"));
assert_eq!(extractor.password, Some("secret".to_string()));
assert_eq!(extractor.chunk_size, 1024 * 1024);
assert_eq!(extractor.output_dir, Some(PathBuf::from("extracted/")));
}
#[test]
fn test_list_contents() {
let extractor = ArchiveExtractor::new("nonexistent.small");
let result = extractor.list_contents();
assert!(result.is_err());
}
#[test]
fn test_extract_all_nonexistent() {
let extractor = ArchiveExtractor::new("nonexistent.small");
let result = extractor.extract_all();
assert!(result.is_err());
}
#[test]
fn test_extract_file_nonexistent() {
let extractor = ArchiveExtractor::new("nonexistent.small");
let result = extractor.extract_file("test.txt", "output.txt");
assert!(result.is_err());
}
}