this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at d7eac9681af468ef2dd62f00e8a09e36cf744585 225 lines 6.8 kB view raw
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}