use crate::archive::ArchiveInfo;
use crate::crypto::DEFAULT_CHUNK_SIZE;
use crate::error::{ArchiveError, MismallError, Result};
use crate::progress::ProgressCallback;
pub struct ArchiveBuilder {
files: Vec<(String, Vec<u8>)>,
password: Option<String>,
chunk_size: usize,
progress_callback: Option<ProgressCallback>,
}
impl ArchiveBuilder {
fn archive_error(error: ArchiveError) -> MismallError {
MismallError::Archive {
error,
context: None,
suggestion: None,
}
}
pub fn new() -> Self {
Self {
files: Vec::new(),
password: None,
chunk_size: DEFAULT_CHUNK_SIZE,
progress_callback: None,
}
}
pub fn add_file<S: Into<String>, D: Into<Vec<u8>>>(mut self, path: S, data: D) -> Result<Self> {
let path = path.into();
let data = data.into();
if path.is_empty() {
return Err(Self::archive_error(ArchiveError::Creation(
"File path cannot be empty".to_string(),
)));
}
if path.len() > 255 {
return Err(Self::archive_error(ArchiveError::Creation(
"File path too long (max 255 characters)".to_string(),
)));
}
if self.files.len() >= 1000 {
return Err(Self::archive_error(ArchiveError::TooManyFiles(
self.files.len() + 1,
)));
}
let total_size: u64 = self.files.iter().map(|(_, data)| data.len() as u64).sum();
if total_size + data.len() as u64 > 1024 * 1024 * 1024 * 10 {
return Err(Self::archive_error(ArchiveError::TooLarge(
total_size + data.len() as u64,
)));
}
self.files.push((path, data));
Ok(self)
}
pub fn add_file_from_fs<P: AsRef<std::path::Path>, S: Into<String>>(
self,
fs_path: P,
archive_path: S,
) -> Result<Self> {
let fs_path = fs_path.as_ref();
let archive_path = archive_path.into();
let data = std::fs::read(fs_path).map_err(|e| {
Self::archive_error(ArchiveError::Creation(format!(
"Failed to read file {}: {}",
fs_path.display(),
e
)))
})?;
self.add_file(archive_path, data)
}
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 build<P: AsRef<std::path::Path>>(self, output_path: P) -> Result<ArchiveInfo> {
if self.files.is_empty() {
return Err(Self::archive_error(ArchiveError::Creation(
"Cannot create archive with no files".to_string(),
)));
}
crate::compress::validate_chunk_size(self.chunk_size)?;
let output_path = output_path.as_ref();
println!("Archive creation not yet fully implemented");
println!(
"Would create archive with {} files at '{}'",
self.files.len(),
output_path.display()
);
let total_original: u64 = self.files.iter().map(|(_, data)| data.len() as u64).sum();
let archive_info = ArchiveInfo::new(
self.files.len(),
total_original,
total_original, self.password.is_some(),
);
Ok(archive_info)
}
}
impl Default for ArchiveBuilder {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for ArchiveBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArchiveBuilder")
.field("file_count", &self.files.len())
.field("password", &self.password.is_some())
.field("chunk_size", &self.chunk_size)
.field("progress_callback", &self.progress_callback.is_some())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_creation() {
let builder = ArchiveBuilder::new();
assert_eq!(builder.files.len(), 0);
assert!(builder.password.is_none());
assert_eq!(builder.chunk_size, DEFAULT_CHUNK_SIZE);
assert!(builder.progress_callback.is_none());
}
#[test]
fn test_add_file() {
let builder = ArchiveBuilder::new()
.add_file("test.txt", b"Hello, world!")
.unwrap();
assert_eq!(builder.files.len(), 1);
assert_eq!(builder.files[0].0, "test.txt");
assert_eq!(builder.files[0].1, b"Hello, world!");
}
#[test]
fn test_add_file_validation() {
let result = ArchiveBuilder::new().add_file("", b"data");
assert!(result.is_err());
let long_path = "a".repeat(256);
let result = ArchiveBuilder::new().add_file(&long_path, b"data");
assert!(result.is_err());
}
#[test]
fn test_builder_chain() {
let builder = ArchiveBuilder::new()
.with_password("secret")
.with_chunk_size(1024 * 1024);
assert_eq!(builder.password, Some("secret".to_string()));
assert_eq!(builder.chunk_size, 1024 * 1024);
}
#[test]
fn test_build_empty_archive() {
let builder = ArchiveBuilder::new();
let result = builder.build("test.small");
assert!(result.is_err());
}
#[test]
fn test_build_archive() {
let _result = ArchiveBuilder::new()
.add_file("test.txt", b"Hello")
.unwrap()
.build("test.small");
println!("Note: Archive building is not fully implemented yet");
}
#[test]
fn test_file_limits() {
let mut builder = ArchiveBuilder::new();
for i in 0..1000 {
builder = builder.add_file(format!("file{}.txt", i), b"data").unwrap();
}
let result = builder.add_file("file1001.txt", b"data");
assert!(result.is_err());
}
}