this repo has no description
1use super::stream::{copy_stream, guard_file_output, open_input, prepare_output};
2use crate::{
3 progress::ProgressArgs,
4 utils::{
5 CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor,
6 DefaultCompressionValidator, LevelArgs, Result,
7 },
8};
9use clap::Args;
10use std::io::{self, Write};
11use xz2::read::XzDecoder;
12use xz2::stream::{LzmaOptions, Stream};
13use xz2::write::XzEncoder;
14
15/// Memory limit passed to the LZMA decoder. `u64::MAX` disables the limit,
16/// which matches the behavior of `xz --lzma1 -d` / `unlzma`.
17const LZMA_DECODER_MEMLIMIT: u64 = u64::MAX;
18
19/// Swallows `flush()` calls on the wrapped writer. The legacy LZMA1
20/// (`lzma_alone`) encoder in liblzma rejects `LZMA_FULL_FLUSH`, which is what
21/// the inner `XzEncoder::flush` issues, so progress/copy helpers that call
22/// `flush` mid-stream must see a no-op flush and let `try_finish` (via Drop
23/// or `finish()`) finalize the stream instead.
24struct NoFlush<W>(W);
25
26impl<W: Write> Write for NoFlush<W> {
27 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
28 self.0.write(buf)
29 }
30
31 fn flush(&mut self) -> io::Result<()> {
32 Ok(())
33 }
34}
35
36#[derive(Args, Debug)]
37pub struct LzmaArgs {
38 #[clap(flatten)]
39 pub common_args: CommonArgs,
40
41 #[clap(flatten)]
42 progress_args: ProgressArgs,
43
44 #[clap(flatten)]
45 pub level_args: LevelArgs,
46}
47
48#[derive(Clone)]
49pub struct Lzma {
50 pub level: i32,
51 pub progress_args: ProgressArgs,
52}
53
54impl Default for Lzma {
55 fn default() -> Self {
56 let validator = DefaultCompressionValidator;
57 Lzma {
58 level: validator.default_level(),
59 progress_args: ProgressArgs::default(),
60 }
61 }
62}
63
64impl Lzma {
65 pub fn new(args: &LzmaArgs) -> Lzma {
66 Lzma {
67 level: args.level_args.resolve(&DefaultCompressionValidator),
68 progress_args: args.progress_args,
69 }
70 }
71
72 /// Build a fresh LZMA1 (`lzma_alone`) encoder stream at the configured level.
73 fn encoder_stream(&self) -> Result<Stream> {
74 let options = LzmaOptions::new_preset(self.level as u32)?;
75 Ok(Stream::new_lzma_encoder(&options)?)
76 }
77
78 /// Build a fresh LZMA1 (`lzma_alone`) decoder stream.
79 fn decoder_stream() -> Result<Stream> {
80 Ok(Stream::new_lzma_decoder(LZMA_DECODER_MEMLIMIT)?)
81 }
82}
83
84impl Compressor for Lzma {
85 /// The standard extension for legacy LZMA (`.lzma`) files.
86 fn extension(&self) -> &str {
87 "lzma"
88 }
89
90 /// Full name for lzma.
91 fn name(&self) -> &str {
92 "lzma"
93 }
94
95 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result {
96 guard_file_output(&output, "LZMA")?;
97 let (input_stream, file_size, pipeline_inner) = open_input(input, "LZMA")?;
98 let (writer, target) = prepare_output(output)?;
99 let mut encoder = XzEncoder::new_stream(writer, self.encoder_stream()?);
100 // `copy_stream` flushes the final writer on the path/pipe branch via
101 // `copy_with_progress`; LZMA1 (`lzma_alone`) rejects mid-stream flush,
102 // so wrap the encoder to swallow those calls and let `try_finish`
103 // finalize the stream.
104 copy_stream(
105 input_stream,
106 NoFlush(&mut encoder),
107 file_size,
108 pipeline_inner,
109 &self.progress_args,
110 target,
111 )?;
112 encoder.try_finish()?;
113 Ok(())
114 }
115
116 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result {
117 guard_file_output(&output, "LZMA")?;
118 let (input_stream, file_size, pipeline_inner) = open_input(input, "LZMA")?;
119 let decoder = XzDecoder::new_stream(input_stream, Self::decoder_stream()?);
120 let (writer, target) = prepare_output(output)?;
121 copy_stream(
122 decoder,
123 writer,
124 file_size,
125 pipeline_inner,
126 &self.progress_args,
127 target,
128 )?;
129 Ok(())
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::test_utils::*;
137
138 /// Test the basic interface of the Lzma compressor
139 #[test]
140 fn test_lzma_interface() {
141 let compressor = Lzma::default();
142 test_compressor_interface(&compressor, "lzma", Some("lzma"));
143 }
144
145 /// Test the default compression level
146 #[test]
147 fn test_lzma_default_compression() -> Result {
148 let compressor = Lzma::default();
149 test_compression(&compressor)
150 }
151
152 /// Test fast compression level
153 #[test]
154 fn test_lzma_fast_compression() -> Result {
155 let fast_compressor = Lzma {
156 level: 1,
157 progress_args: ProgressArgs::default(),
158 };
159 test_compression(&fast_compressor)
160 }
161
162 /// Test best compression level
163 #[test]
164 fn test_lzma_best_compression() -> Result {
165 let best_compressor = Lzma {
166 level: 9,
167 progress_args: ProgressArgs::default(),
168 };
169 test_compression(&best_compressor)
170 }
171}