compression_codecs/xz2/
encoder.rs

1use compression_core::{
2    util::{PartialBuffer, WriteBuffer},
3    Level,
4};
5use liblzma::stream::{Action, Check, Status, Stream};
6use std::{
7    convert::{TryFrom, TryInto},
8    fmt, io,
9};
10
11use crate::{
12    lzma::params::{LzmaEncoderParams, LzmaOptions},
13    EncodeV2, Xz2FileFormat,
14};
15
16/// Xz2 encoding stream
17pub struct Xz2Encoder {
18    stream: Stream,
19    params: LzmaEncoderParams,
20}
21
22impl fmt::Debug for Xz2Encoder {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        f.debug_struct("Xz2Encoder").finish_non_exhaustive()
25    }
26}
27
28impl TryFrom<LzmaEncoderParams> for Xz2Encoder {
29    type Error = liblzma::stream::Error;
30
31    fn try_from(params: LzmaEncoderParams) -> Result<Self, Self::Error> {
32        let stream = Stream::try_from(&params)?;
33        Ok(Self {
34            stream,
35            params: params.clone(),
36        })
37    }
38}
39
40fn xz2_level(level: Level) -> u32 {
41    match level {
42        Level::Fastest => 0,
43        Level::Best => 9,
44        Level::Precise(quality) => quality.try_into().unwrap_or(0).clamp(0, 9),
45        _ => 5,
46    }
47}
48
49impl Xz2Encoder {
50    pub fn new(format: Xz2FileFormat, level: Level) -> Self {
51        let preset = xz2_level(level);
52        let params = match format {
53            Xz2FileFormat::Xz => LzmaEncoderParams::Easy {
54                preset,
55                check: Check::Crc64,
56            },
57            Xz2FileFormat::Lzma => {
58                let options = LzmaOptions::default().preset(preset);
59                LzmaEncoderParams::Lzma { options }
60            }
61        };
62
63        Self::try_from(params).unwrap()
64    }
65
66    #[cfg(feature = "xz-parallel")]
67    pub fn xz_parallel(level: Level, threads: std::num::NonZeroU32) -> Self {
68        use crate::lzma::params::MtStreamBuilder;
69
70        let preset = xz2_level(level);
71        let mut builder = MtStreamBuilder::default();
72        builder
73            .threads(threads)
74            .timeout_ms(300)
75            .preset(preset)
76            .check(Check::Crc64);
77        let params = LzmaEncoderParams::MultiThread { builder };
78        Self::try_from(params).unwrap()
79    }
80
81    /// Return `Ok(true)` if stream ends.
82    fn process(
83        &mut self,
84        input: &mut PartialBuffer<&[u8]>,
85        output: &mut WriteBuffer<'_>,
86        action: Action,
87    ) -> io::Result<bool> {
88        let previous_in = self.stream.total_in() as usize;
89        let previous_out = self.stream.total_out() as usize;
90
91        let res = self
92            .stream
93            .process(input.unwritten(), output.initialize_unwritten(), action);
94
95        input.advance(self.stream.total_in() as usize - previous_in);
96        output.advance(self.stream.total_out() as usize - previous_out);
97
98        match res? {
99            Status::Ok => Ok(false),
100            Status::StreamEnd => Ok(true),
101            Status::GetCheck => Err(io::Error::other("Unexpected lzma integrity check")),
102            Status::MemNeeded => Err(io::ErrorKind::OutOfMemory.into()),
103        }
104    }
105}
106
107impl EncodeV2 for Xz2Encoder {
108    fn encode(
109        &mut self,
110        input: &mut PartialBuffer<&[u8]>,
111        output: &mut WriteBuffer<'_>,
112    ) -> io::Result<()> {
113        self.process(input, output, Action::Run).map(|_| ())
114    }
115
116    fn flush(&mut self, output: &mut WriteBuffer<'_>) -> io::Result<bool> {
117        let action = match &self.params {
118            // Multi-threaded streams don't support SyncFlush, use FullFlush instead
119            #[cfg(feature = "xz-parallel")]
120            LzmaEncoderParams::MultiThread { builder: _ } => Action::FullFlush,
121            _ => Action::SyncFlush,
122        };
123
124        self.process(&mut PartialBuffer::new(&[]), output, action)
125    }
126
127    fn finish(&mut self, output: &mut WriteBuffer<'_>) -> io::Result<bool> {
128        self.process(&mut PartialBuffer::new(&[]), output, Action::Finish)
129    }
130}