use super::stream::{copy_stream, guard_file_output, open_input, prepare_output};
use crate::progress::ProgressArgs;
use crate::utils::{
CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor, LevelArgs, Result,
};
use brotli::{CompressorWriter, Decompressor};
use clap::Args;
use std::io::Write;
const BROTLI_BUFFER_SIZE: usize = 4096;
const BROTLI_LGWIN: u32 = 22;
#[derive(Debug, Clone, Copy)]
pub struct BrotliCompressionValidator;
impl CompressionLevelValidator for BrotliCompressionValidator {
fn min_level(&self) -> i32 {
0
}
fn max_level(&self) -> i32 {
11
}
fn default_level(&self) -> i32 {
6
}
fn name_to_level(&self, name: &str) -> Option<i32> {
match name.to_lowercase().as_str() {
"none" => Some(0),
"fast" => Some(1),
"best" => Some(11),
_ => None,
}
}
}
#[derive(Args, Debug)]
pub struct BrotliArgs {
#[clap(flatten)]
pub common_args: CommonArgs,
#[clap(flatten)]
pub level_args: LevelArgs,
#[clap(flatten)]
pub progress_args: ProgressArgs,
}
#[derive(Clone)]
pub struct Brotli {
pub compression_level: i32,
pub progress_args: ProgressArgs,
}
impl Default for Brotli {
fn default() -> Self {
let validator = BrotliCompressionValidator;
Brotli {
compression_level: validator.default_level(),
progress_args: ProgressArgs::default(),
}
}
}
impl Brotli {
pub fn new(args: &BrotliArgs) -> Brotli {
Brotli {
compression_level: args.level_args.resolve(&BrotliCompressionValidator),
progress_args: args.progress_args,
}
}
}
impl Compressor for Brotli {
fn extension(&self) -> &str {
"br"
}
fn name(&self) -> &str {
"brotli"
}
fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result {
guard_file_output(&output, "Brotli")?;
let (input_stream, file_size, pipeline_inner) = open_input(input, "Brotli")?;
let (writer, target) = prepare_output(output)?;
let mut encoder = CompressorWriter::new(
writer,
BROTLI_BUFFER_SIZE,
self.compression_level as u32,
BROTLI_LGWIN,
);
copy_stream(
input_stream,
&mut encoder,
file_size,
pipeline_inner,
&self.progress_args,
target,
)?;
encoder.flush()?;
Ok(())
}
fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result {
guard_file_output(&output, "Brotli")?;
let (input_stream, file_size, pipeline_inner) = open_input(input, "Brotli")?;
let decoder = Decompressor::new(input_stream, BROTLI_BUFFER_SIZE);
let (writer, target) = prepare_output(output)?;
copy_stream(
decoder,
writer,
file_size,
pipeline_inner,
&self.progress_args,
target,
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn test_brotli_interface() {
let compressor = Brotli::default();
test_compressor_interface(&compressor, "brotli", Some("br"));
}
#[test]
fn test_brotli_default_compression() -> Result {
let compressor = Brotli::default();
test_compression(&compressor)
}
#[test]
fn test_brotli_fast_compression() -> Result {
let fast_compressor = Brotli {
compression_level: 1,
progress_args: ProgressArgs::default(),
};
test_compression(&fast_compressor)
}
#[test]
fn test_brotli_best_compression() -> Result {
let best_compressor = Brotli {
compression_level: 11,
progress_args: ProgressArgs::default(),
};
test_compression(&best_compressor)
}
#[test]
fn test_brotli_compression_validator() {
let validator = BrotliCompressionValidator;
test_compression_validator_helper(
&validator,
0, 11, 6, Some(1), Some(11), Some(0), );
}
}