use crate::decompress::DecompressionResult;
use crate::error::{DecompressionError, Result};
use crate::headers::Headers;
use crate::huffman::decoder::{decode, DecodeInfo};
use crate::progress::{ProgressCallback, ProgressTracker};
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::path::Path;
pub const DECOMPRESS_CHUNK_SIZE: usize = 16 * 1024 * 1024;
pub fn decompress_file<P: AsRef<Path>>(
input_path: P,
password: Option<&str>,
) -> Result<DecompressionResult> {
let input_path = input_path.as_ref();
let mut input_file =
File::open(input_path).map_err(|e| crate::error::MismallError::Decompression {
error: DecompressionError::InputRead(format!("Failed to open input file: {}", e)),
context: None,
suggestion: None,
})?;
let mut output_buffer = Cursor::new(Vec::new());
let decode_info = decompress_stream(
&mut input_file,
password,
&mut output_buffer,
DECOMPRESS_CHUNK_SIZE,
)?;
Ok(DecompressionResult::new(
decode_info.original_file_name,
decode_info.original_size,
decode_info.checksum,
password.is_some(),
))
}
pub fn decompress_file_with_progress<P: AsRef<Path>>(
input_path: P,
password: Option<&str>,
chunk_size: usize,
progress_callback: Option<ProgressCallback>,
) -> Result<DecompressionResult> {
let input_path = input_path.as_ref();
let file_size = std::fs::metadata(input_path)
.map_err(|e| crate::error::MismallError::Decompression {
error: DecompressionError::InputRead(format!("Failed to read file metadata: {}", e)),
context: None,
suggestion: None,
})?
.len();
let mut progress_tracker = ProgressTracker::new(
file_size,
crate::progress::ProcessingStage::Reading,
progress_callback,
);
let mut input_file =
File::open(input_path).map_err(|e| crate::error::MismallError::Decompression {
error: DecompressionError::InputRead(format!("Failed to open input file: {}", e)),
context: None,
suggestion: None,
})?;
let mut output_buffer = Cursor::new(Vec::new());
progress_tracker.update(file_size / 3);
progress_tracker.set_stage(crate::progress::ProcessingStage::Decoding);
let decode_info = decompress_stream(&mut input_file, password, &mut output_buffer, chunk_size)?;
progress_tracker.update(2 * file_size / 3);
progress_tracker.set_stage(crate::progress::ProcessingStage::Writing);
progress_tracker.update(file_size);
Ok(DecompressionResult::new(
decode_info.original_file_name,
decode_info.original_size,
decode_info.checksum,
password.is_some(),
))
}
pub fn decompress_stream<R: Read + std::io::Seek, W: Write>(
reader: &mut R,
password: Option<&str>,
writer: &mut W,
chunk_size: usize,
) -> Result<DecodeInfo> {
crate::compress::validate_chunk_size(chunk_size)?;
let header =
Headers::from_reader(reader).map_err(|e| crate::error::MismallError::Decompression {
error: DecompressionError::InvalidFormat(format!("Failed to read headers: {}", e)),
context: None,
suggestion: None,
})?;
decode(header, reader, password, writer, chunk_size).map_err(|e| {
crate::error::MismallError::Decompression {
error: DecompressionError::CorruptedData(format!("Failed to decode: {}", e)),
context: None,
suggestion: None,
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_decompress_file_basic() {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"Hello, world! This is test data.";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let compressed_result = crate::compress_file(temp_file.path(), None).unwrap();
assert!(compressed_result.original_size > 0);
}
#[test]
fn test_decompress_stream() {
let mut reader = Cursor::new(b"dummy_compressed_data");
let mut writer = Cursor::new(Vec::new());
let result = decompress_stream(
&mut reader,
None,
&mut writer,
1024 * 1024, );
assert!(result.is_err());
}
}