use super::stream::{copy_stream, guard_file_output, open_input, prepare_output};
use crate::{
progress::ProgressArgs,
utils::{
CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor,
DefaultCompressionValidator, LevelArgs, Result,
},
};
use clap::Args;
use std::io::{self, Write};
use xz2::read::XzDecoder;
use xz2::stream::{LzmaOptions, Stream};
use xz2::write::XzEncoder;
const LZMA_DECODER_MEMLIMIT: u64 = u64::MAX;
struct NoFlush<W>(W);
impl<W: Write> Write for NoFlush<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(Args, Debug)]
pub struct LzmaArgs {
#[clap(flatten)]
pub common_args: CommonArgs,
#[clap(flatten)]
progress_args: ProgressArgs,
#[clap(flatten)]
pub level_args: LevelArgs,
}
#[derive(Clone)]
pub struct Lzma {
pub level: i32,
pub progress_args: ProgressArgs,
}
impl Default for Lzma {
fn default() -> Self {
let validator = DefaultCompressionValidator;
Lzma {
level: validator.default_level(),
progress_args: ProgressArgs::default(),
}
}
}
impl Lzma {
pub fn new(args: &LzmaArgs) -> Lzma {
Lzma {
level: args.level_args.resolve(&DefaultCompressionValidator),
progress_args: args.progress_args,
}
}
fn encoder_stream(&self) -> Result<Stream> {
let options = LzmaOptions::new_preset(self.level as u32)?;
Ok(Stream::new_lzma_encoder(&options)?)
}
fn decoder_stream() -> Result<Stream> {
Ok(Stream::new_lzma_decoder(LZMA_DECODER_MEMLIMIT)?)
}
}
impl Compressor for Lzma {
fn extension(&self) -> &str {
"lzma"
}
fn name(&self) -> &str {
"lzma"
}
fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result {
guard_file_output(&output, "LZMA")?;
let (input_stream, file_size, pipeline_inner) = open_input(input, "LZMA")?;
let (writer, target) = prepare_output(output)?;
let mut encoder = XzEncoder::new_stream(writer, self.encoder_stream()?);
copy_stream(
input_stream,
NoFlush(&mut encoder),
file_size,
pipeline_inner,
&self.progress_args,
target,
)?;
encoder.try_finish()?;
Ok(())
}
fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result {
guard_file_output(&output, "LZMA")?;
let (input_stream, file_size, pipeline_inner) = open_input(input, "LZMA")?;
let decoder = XzDecoder::new_stream(input_stream, Self::decoder_stream()?);
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_lzma_interface() {
let compressor = Lzma::default();
test_compressor_interface(&compressor, "lzma", Some("lzma"));
}
#[test]
fn test_lzma_default_compression() -> Result {
let compressor = Lzma::default();
test_compression(&compressor)
}
#[test]
fn test_lzma_fast_compression() -> Result {
let fast_compressor = Lzma {
level: 1,
progress_args: ProgressArgs::default(),
};
test_compression(&fast_compressor)
}
#[test]
fn test_lzma_best_compression() -> Result {
let best_compressor = Lzma {
level: 9,
progress_args: ProgressArgs::default(),
};
test_compression(&best_compressor)
}
}