compression_codecs/xz2/
encoder.rs1use 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
16pub 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(¶ms)?;
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 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 #[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}