use crate::{
progress::{ProgressArgs, copy_with_progress},
utils::{
CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor,
ExtractedTarget, LevelArgs, Result,
},
};
use anyhow::bail;
use bzip2::Compression;
use bzip2::write::{BzDecoder, BzEncoder};
use clap::Args;
use std::{
fs::File,
io::{self, BufReader, BufWriter, Read, Write},
};
#[derive(Debug, Clone, Copy)]
pub struct Bzip2CompressionValidator;
impl CompressionLevelValidator for Bzip2CompressionValidator {
fn min_level(&self) -> i32 {
1
}
fn max_level(&self) -> i32 {
9
}
fn default_level(&self) -> i32 {
9
}
fn name_to_level(&self, name: &str) -> Option<i32> {
match name.to_lowercase().as_str() {
"fast" => Some(1),
"best" => Some(9),
_ => None,
}
}
}
#[derive(Args, Debug)]
pub struct Bzip2Args {
#[clap(flatten)]
pub common_args: CommonArgs,
#[clap(flatten)]
pub progress_args: ProgressArgs,
#[clap(flatten)]
pub level_args: LevelArgs,
}
pub struct Bzip2 {
pub level: i32, pub progress_args: ProgressArgs,
}
impl Default for Bzip2 {
fn default() -> Self {
let validator = Bzip2CompressionValidator;
Bzip2 {
level: validator.default_level(),
progress_args: ProgressArgs::default(),
}
}
}
impl Bzip2 {
pub fn new(args: &Bzip2Args) -> Self {
let validator = Bzip2CompressionValidator;
let level = validator.validate_and_clamp_level(args.level_args.level.level);
Bzip2 {
level,
progress_args: args.progress_args,
}
}
}
impl Compressor for Bzip2 {
fn extension(&self) -> &str {
"bz2"
}
fn name(&self) -> &str {
"bzip2"
}
fn default_extracted_target(&self) -> ExtractedTarget {
ExtractedTarget::FILE
}
fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result {
let mut file_size = None;
let mut input_stream = match input {
CmprssInput::Path(paths) => {
if paths.len() > 1 {
bail!("Multiple input files not supported for bzip2");
}
let path = &paths[0];
file_size = Some(std::fs::metadata(path)?.len());
Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send>
}
CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
CmprssInput::Reader(reader) => reader.0,
};
if let CmprssOutput::Writer(writer) = output {
let mut encoder = BzEncoder::new(writer, Compression::new(self.level as u32));
io::copy(&mut input_stream, &mut encoder)?;
} else {
let output_stream: Box<dyn Write + Send> = match &output {
CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)),
CmprssOutput::Pipe(pipe) => Box::new(pipe),
CmprssOutput::Writer(_) => unreachable!(),
};
let mut encoder = BzEncoder::new(output_stream, Compression::new(self.level as u32));
copy_with_progress(
&mut input_stream,
&mut encoder,
self.progress_args.chunk_size.size_in_bytes,
file_size,
self.progress_args.progress,
&output,
)?;
}
Ok(())
}
fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result {
let mut file_size = None;
let mut input_stream = match input {
CmprssInput::Path(paths) => {
if paths.len() > 1 {
bail!("Multiple input files not supported for bzip2 extraction");
}
let path = &paths[0];
file_size = Some(std::fs::metadata(path)?.len());
Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send>
}
CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
CmprssInput::Reader(reader) => reader.0,
};
if let CmprssOutput::Writer(writer) = output {
let mut decoder = BzDecoder::new(writer);
io::copy(&mut input_stream, &mut decoder)?;
} else {
let output_stream: Box<dyn Write + Send> = match &output {
CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)),
CmprssOutput::Pipe(pipe) => Box::new(pipe),
CmprssOutput::Writer(_) => unreachable!(),
};
let mut decoder = BzDecoder::new(output_stream);
copy_with_progress(
&mut input_stream,
&mut decoder,
self.progress_args.chunk_size.size_in_bytes,
file_size,
self.progress_args.progress,
&output,
)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_bzip2_interface() {
let compressor = Bzip2::default();
test_compressor_interface(&compressor, "bzip2", Some("bz2"));
}
#[test]
fn test_bzip2_compression_validator() {
let validator = Bzip2CompressionValidator;
test_compression_validator_helper(
&validator,
1, 9, 9, Some(1), Some(9), None, );
}
#[test]
fn test_bzip2_default_compression() -> Result {
let compressor = Bzip2::default();
test_compression(&compressor)
}
#[test]
fn test_bzip2_fast_compression() -> Result {
let fast_compressor = Bzip2 {
level: 1,
progress_args: ProgressArgs::default(),
};
test_compression(&fast_compressor)
}
#[test]
fn test_bzip2_best_compression() -> Result {
let best_compressor = Bzip2 {
level: 9,
progress_args: ProgressArgs::default(),
};
test_compression(&best_compressor)
}
}