use crate::compress::validate_chunk_size;
use crate::compress::CompressionResult;
use crate::crypto::DEFAULT_CHUNK_SIZE;
use crate::error::Result;
use crate::progress::ProgressCallback;
use std::path::PathBuf;
pub struct CompressionBuilder {
input_path: PathBuf,
password: Option<String>,
chunk_size: usize,
progress_callback: Option<ProgressCallback>,
output_path: Option<PathBuf>,
}
impl std::fmt::Debug for CompressionBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompressionBuilder")
.field("input_path", &self.input_path)
.field("password", &self.password.is_some())
.field("chunk_size", &self.chunk_size)
.field("progress_callback", &self.progress_callback.is_some())
.field("output_path", &self.output_path)
.finish()
}
}
impl Clone for CompressionBuilder {
fn clone(&self) -> Self {
Self {
input_path: self.input_path.clone(),
password: self.password.clone(),
chunk_size: self.chunk_size,
progress_callback: None, output_path: self.output_path.clone(),
}
}
}
impl CompressionBuilder {
pub fn new<P: Into<PathBuf>>(input_path: P) -> Self {
Self {
input_path: input_path.into(),
password: None,
chunk_size: DEFAULT_CHUNK_SIZE,
progress_callback: None,
output_path: 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_output_path<P: Into<PathBuf>>(mut self, output_path: P) -> Self {
self.output_path = Some(output_path.into());
self
}
pub fn with_progress_callback(mut self, callback: ProgressCallback) -> Self {
self.progress_callback = Some(callback);
self
}
pub fn compress(self) -> Result<CompressionResult> {
validate_chunk_size(self.chunk_size)?;
let _output_path = self.output_path.unwrap_or_else(|| {
let mut path = self.input_path.clone();
path.set_extension("small");
path
});
crate::compress::simple::compress_file_with_progress(
&self.input_path,
self.password.as_deref(),
self.chunk_size,
self.progress_callback,
)
}
}
impl From<&str> for CompressionBuilder {
fn from(path: &str) -> Self {
Self::new(path)
}
}
impl From<String> for CompressionBuilder {
fn from(path: String) -> Self {
Self::new(path)
}
}
impl From<PathBuf> for CompressionBuilder {
fn from(path: PathBuf) -> Self {
Self::new(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_builder_creation() {
let builder = CompressionBuilder::new("test.txt");
assert_eq!(builder.input_path, PathBuf::from("test.txt"));
assert!(builder.password.is_none());
assert_eq!(builder.chunk_size, DEFAULT_CHUNK_SIZE);
assert!(builder.progress_callback.is_none());
assert!(builder.output_path.is_none());
}
#[test]
fn test_builder_from_conversions() {
let builder1 = CompressionBuilder::from("test.txt");
assert_eq!(builder1.input_path, PathBuf::from("test.txt"));
let builder2 = CompressionBuilder::from("test.txt".to_string());
assert_eq!(builder2.input_path, PathBuf::from("test.txt"));
let path = PathBuf::from("test.txt");
let builder3 = CompressionBuilder::from(path.clone());
assert_eq!(builder3.input_path, path);
}
#[test]
fn test_builder_chain() {
let builder = CompressionBuilder::new("test.txt")
.with_password("secret")
.with_chunk_size(1024 * 1024)
.with_output_path("output.small");
assert_eq!(builder.input_path, PathBuf::from("test.txt"));
assert_eq!(builder.password, Some("secret".to_string()));
assert_eq!(builder.chunk_size, 1024 * 1024);
assert_eq!(builder.output_path, Some(PathBuf::from("output.small")));
}
#[test]
fn test_builder_progress_callback() {
use std::sync::atomic::{AtomicU32, Ordering};
static CALL_COUNT: AtomicU32 = AtomicU32::new(0);
let callback: crate::progress::ProgressCallback = Box::new(|progress| {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
assert!(progress.percentage >= 0.0);
});
let _builder = CompressionBuilder::new("test.txt").with_progress_callback(callback);
}
#[test]
fn test_output_path_default() {
let builder = CompressionBuilder::new("document.txt");
let expected = PathBuf::from("document.small");
let actual = builder.output_path.unwrap_or_else(|| {
let mut path = builder.input_path.clone();
path.set_extension("small");
path
});
assert_eq!(actual, expected);
}
#[test]
fn test_invalid_chunk_size() {
let builder = CompressionBuilder::new("test.txt").with_chunk_size(32 * 1024);
let result = builder.compress();
assert!(result.is_err());
match result.unwrap_err() {
crate::error::MismallError::Compression {
error: crate::error::CompressionError::InvalidChunkSize(size),
..
} => {
assert_eq!(size, 32 * 1024);
}
_ => panic!("Expected InvalidChunkSize error"),
}
}
#[test]
fn test_compression_with_real_file() {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"This is test data for compression builder testing. It should be compressible enough to show some savings.";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let result = CompressionBuilder::new(temp_file.path())
.with_password("test_password")
.compress();
assert!(result.is_ok());
let compression_result = result.unwrap();
assert_eq!(compression_result.original_size, test_data.len() as u64);
assert!(compression_result.encrypted);
assert!(compression_result.compressed_size > 0);
}
}