this repo has no description
1use crate::{
2 progress::{ProgressArgs, copy_with_progress},
3 utils::{
4 CmprssInput, CmprssOutput, CommonArgs, CompressionLevelValidator, Compressor,
5 ExtractedTarget, LevelArgs, Result,
6 },
7};
8use anyhow::bail;
9use bzip2::Compression;
10use bzip2::write::{BzDecoder, BzEncoder};
11use clap::Args;
12use std::{
13 fs::File,
14 io::{self, BufReader, BufWriter, Read, Write},
15};
16
17/// BZip2-specific compression validator (1-9 range)
18#[derive(Debug, Clone, Copy)]
19pub struct Bzip2CompressionValidator;
20
21impl CompressionLevelValidator for Bzip2CompressionValidator {
22 fn min_level(&self) -> i32 {
23 1
24 }
25 fn max_level(&self) -> i32 {
26 9
27 }
28 fn default_level(&self) -> i32 {
29 9
30 }
31
32 fn name_to_level(&self, name: &str) -> Option<i32> {
33 match name.to_lowercase().as_str() {
34 "fast" => Some(1),
35 "best" => Some(9),
36 _ => None,
37 }
38 }
39}
40
41#[derive(Args, Debug)]
42pub struct Bzip2Args {
43 #[clap(flatten)]
44 pub common_args: CommonArgs,
45
46 #[clap(flatten)]
47 pub progress_args: ProgressArgs,
48
49 #[clap(flatten)]
50 pub level_args: LevelArgs,
51}
52
53pub struct Bzip2 {
54 pub level: i32, // 1-9
55 pub progress_args: ProgressArgs,
56}
57
58impl Default for Bzip2 {
59 fn default() -> Self {
60 let validator = Bzip2CompressionValidator;
61 Bzip2 {
62 level: validator.default_level(),
63 progress_args: ProgressArgs::default(),
64 }
65 }
66}
67
68impl Bzip2 {
69 pub fn new(args: &Bzip2Args) -> Self {
70 let validator = Bzip2CompressionValidator;
71 let level = validator.validate_and_clamp_level(args.level_args.level.level);
72
73 Bzip2 {
74 level,
75 progress_args: args.progress_args,
76 }
77 }
78}
79
80impl Compressor for Bzip2 {
81 /// Default extension for bzip2 files
82 fn extension(&self) -> &str {
83 "bz2"
84 }
85
86 /// Name of this compressor
87 fn name(&self) -> &str {
88 "bzip2"
89 }
90
91 /// Bzip2 extracts to a file by default
92 fn default_extracted_target(&self) -> ExtractedTarget {
93 ExtractedTarget::FILE
94 }
95
96 /// Compress an input file or pipe to a bz2 archive
97 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result {
98 let mut file_size = None;
99 let mut input_stream = match input {
100 CmprssInput::Path(paths) => {
101 if paths.len() > 1 {
102 bail!("Multiple input files not supported for bzip2");
103 }
104 let path = &paths[0];
105 file_size = Some(std::fs::metadata(path)?.len());
106 Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send>
107 }
108 CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
109 CmprssInput::Reader(reader) => reader.0,
110 };
111 if let CmprssOutput::Writer(writer) = output {
112 let mut encoder = BzEncoder::new(writer, Compression::new(self.level as u32));
113 io::copy(&mut input_stream, &mut encoder)?;
114 } else {
115 let output_stream: Box<dyn Write + Send> = match &output {
116 CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)),
117 CmprssOutput::Pipe(pipe) => Box::new(pipe),
118 CmprssOutput::Writer(_) => unreachable!(),
119 };
120 let mut encoder = BzEncoder::new(output_stream, Compression::new(self.level as u32));
121 copy_with_progress(
122 &mut input_stream,
123 &mut encoder,
124 self.progress_args.chunk_size.size_in_bytes,
125 file_size,
126 self.progress_args.progress,
127 &output,
128 )?;
129 }
130
131 Ok(())
132 }
133
134 /// Extract a bz2 archive to a file or pipe
135 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result {
136 let mut file_size = None;
137 let mut input_stream = match input {
138 CmprssInput::Path(paths) => {
139 if paths.len() > 1 {
140 bail!("Multiple input files not supported for bzip2 extraction");
141 }
142 let path = &paths[0];
143 file_size = Some(std::fs::metadata(path)?.len());
144 Box::new(BufReader::new(File::open(path)?)) as Box<dyn Read + Send>
145 }
146 CmprssInput::Pipe(pipe) => Box::new(pipe) as Box<dyn Read + Send>,
147 CmprssInput::Reader(reader) => reader.0,
148 };
149 if let CmprssOutput::Writer(writer) = output {
150 let mut decoder = BzDecoder::new(writer);
151 io::copy(&mut input_stream, &mut decoder)?;
152 } else {
153 let output_stream: Box<dyn Write + Send> = match &output {
154 CmprssOutput::Path(path) => Box::new(BufWriter::new(File::create(path)?)),
155 CmprssOutput::Pipe(pipe) => Box::new(pipe),
156 CmprssOutput::Writer(_) => unreachable!(),
157 };
158 let mut decoder = BzDecoder::new(output_stream);
159 copy_with_progress(
160 &mut input_stream,
161 &mut decoder,
162 self.progress_args.chunk_size.size_in_bytes,
163 file_size,
164 self.progress_args.progress,
165 &output,
166 )?;
167 }
168
169 Ok(())
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::test_utils::*;
177
178 /// Test the basic interface of the Bzip2 compressor
179 #[test]
180 fn test_bzip2_interface() {
181 let compressor = Bzip2::default();
182 test_compressor_interface(&compressor, "bzip2", Some("bz2"));
183 }
184
185 #[test]
186 fn test_bzip2_compression_validator() {
187 let validator = Bzip2CompressionValidator;
188 test_compression_validator_helper(
189 &validator,
190 1, // min_level
191 9, // max_level
192 9, // default_level
193 Some(1), // fast_name_level
194 Some(9), // best_name_level
195 None, // none_name_level
196 );
197 }
198
199 /// Test the default compression level
200 #[test]
201 fn test_bzip2_default_compression() -> Result {
202 let compressor = Bzip2::default();
203 test_compression(&compressor)
204 }
205
206 /// Test fast compression level
207 #[test]
208 fn test_bzip2_fast_compression() -> Result {
209 let fast_compressor = Bzip2 {
210 level: 1,
211 progress_args: ProgressArgs::default(),
212 };
213 test_compression(&fast_compressor)
214 }
215
216 /// Test best compression level
217 #[test]
218 fn test_bzip2_best_compression() -> Result {
219 let best_compressor = Bzip2 {
220 level: 9,
221 progress_args: ProgressArgs::default(),
222 };
223 test_compression(&best_compressor)
224 }
225}