compression_codecs/xz2/
encoder.rs

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