this repo has no description
0
fork

Configure Feed

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

at f7bbb7638e8ed85d5c92d23cfdcf38ed665b3b21 208 lines 6.4 kB view raw
1use super::stream::{guard_file_output, open_input, open_output}; 2use crate::progress::{ProgressArgs, copy_with_progress}; 3use crate::utils::*; 4use clap::Args; 5use flate2::write::GzEncoder; 6use flate2::{Compression, read::GzDecoder}; 7use std::io; 8 9#[derive(Args, Debug)] 10pub struct GzipArgs { 11 #[clap(flatten)] 12 pub common_args: CommonArgs, 13 14 #[clap(flatten)] 15 pub level_args: LevelArgs, 16 17 #[clap(flatten)] 18 pub progress_args: ProgressArgs, 19} 20 21pub struct Gzip { 22 pub compression_level: i32, 23 pub progress_args: ProgressArgs, 24} 25 26impl Default for Gzip { 27 fn default() -> Self { 28 let validator = DefaultCompressionValidator; 29 Gzip { 30 compression_level: validator.default_level(), 31 progress_args: ProgressArgs::default(), 32 } 33 } 34} 35 36impl Gzip { 37 pub fn new(args: &GzipArgs) -> Gzip { 38 Gzip { 39 compression_level: args.level_args.resolve(&DefaultCompressionValidator), 40 progress_args: args.progress_args, 41 } 42 } 43} 44 45impl Compressor for Gzip { 46 /// The standard extension for the gzip format. 47 fn extension(&self) -> &str { 48 "gz" 49 } 50 51 /// Full name for gzip. 52 fn name(&self) -> &str { 53 "gzip" 54 } 55 56 /// Gzip extracts to a file by default 57 fn default_extracted_target(&self) -> ExtractedTarget { 58 ExtractedTarget::File 59 } 60 61 /// Compress an input file or pipe to a gzip archive 62 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 63 guard_file_output(&output, "Gzip")?; 64 let (mut input_stream, file_size) = open_input(input, "Gzip")?; 65 let level = Compression::new(self.compression_level as u32); 66 67 if let CmprssOutput::Writer(writer) = output { 68 let mut encoder = GzEncoder::new(writer, level); 69 io::copy(&mut input_stream, &mut encoder)?; 70 encoder.finish()?; 71 } else { 72 let output_stream = open_output(&output)?; 73 let mut encoder = GzEncoder::new(output_stream, level); 74 copy_with_progress( 75 &mut input_stream, 76 &mut encoder, 77 self.progress_args.chunk_size.size_in_bytes, 78 file_size, 79 self.progress_args.progress, 80 &output, 81 )?; 82 encoder.finish()?; 83 } 84 Ok(()) 85 } 86 87 /// Extract a gzip archive 88 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 89 guard_file_output(&output, "Gzip")?; 90 let (input_stream, file_size) = open_input(input, "Gzip")?; 91 let mut decoder = GzDecoder::new(input_stream); 92 93 if let CmprssOutput::Writer(mut writer) = output { 94 io::copy(&mut decoder, &mut writer)?; 95 } else { 96 let mut output_stream = open_output(&output)?; 97 copy_with_progress( 98 &mut decoder, 99 &mut output_stream, 100 self.progress_args.chunk_size.size_in_bytes, 101 file_size, 102 self.progress_args.progress, 103 &output, 104 )?; 105 } 106 Ok(()) 107 } 108} 109 110#[cfg(test)] 111mod tests { 112 use super::*; 113 use crate::test_utils::*; 114 use std::fs; 115 use std::io::{Read, Write}; 116 use tempfile::tempdir; 117 118 /// Test the basic interface of the Gzip compressor 119 #[test] 120 fn test_gzip_interface() { 121 let compressor = Gzip::default(); 122 test_compressor_interface(&compressor, "gzip", Some("gz")); 123 } 124 125 /// Test the default compression level 126 #[test] 127 fn test_gzip_default_compression() -> Result { 128 let compressor = Gzip::default(); 129 test_compression(&compressor) 130 } 131 132 /// Test fast compression level 133 #[test] 134 fn test_gzip_fast_compression() -> Result { 135 let fast_compressor = Gzip { 136 compression_level: 1, 137 progress_args: ProgressArgs::default(), 138 }; 139 test_compression(&fast_compressor) 140 } 141 142 /// Test best compression level 143 #[test] 144 fn test_gzip_best_compression() -> Result { 145 let best_compressor = Gzip { 146 compression_level: 9, 147 progress_args: ProgressArgs::default(), 148 }; 149 test_compression(&best_compressor) 150 } 151 152 /// Test for gzip-specific behavior: handling of concatenated gzip archives 153 #[test] 154 fn test_concatenated_gzip() -> Result { 155 let compressor = Gzip::default(); 156 let temp_dir = tempdir().expect("Failed to create temp dir"); 157 158 // Create two test files 159 let input_path1 = temp_dir.path().join("input1.txt"); 160 let input_path2 = temp_dir.path().join("input2.txt"); 161 let test_data1 = "This is the first file"; 162 let test_data2 = "This is the second file"; 163 fs::write(&input_path1, test_data1)?; 164 fs::write(&input_path2, test_data2)?; 165 166 // Compress each file separately 167 let archive_path1 = temp_dir.path().join("archive1.gz"); 168 let archive_path2 = temp_dir.path().join("archive2.gz"); 169 170 compressor.compress( 171 CmprssInput::Path(vec![input_path1.clone()]), 172 CmprssOutput::Path(archive_path1.clone()), 173 )?; 174 175 compressor.compress( 176 CmprssInput::Path(vec![input_path2.clone()]), 177 CmprssOutput::Path(archive_path2.clone()), 178 )?; 179 180 // Create a concatenated archive 181 let concat_archive = temp_dir.path().join("concat.gz"); 182 let mut concat_file = fs::File::create(&concat_archive)?; 183 184 // Concat the two gzip files 185 let mut archive1_data = Vec::new(); 186 let mut archive2_data = Vec::new(); 187 fs::File::open(&archive_path1)?.read_to_end(&mut archive1_data)?; 188 fs::File::open(&archive_path2)?.read_to_end(&mut archive2_data)?; 189 190 concat_file.write_all(&archive1_data)?; 191 concat_file.write_all(&archive2_data)?; 192 concat_file.flush()?; 193 194 // Extract the concatenated archive - this should yield the first file's contents 195 let output_path = temp_dir.path().join("output.txt"); 196 197 compressor.extract( 198 CmprssInput::Path(vec![concat_archive]), 199 CmprssOutput::Path(output_path.clone()), 200 )?; 201 202 // Verify the result is the first file's content 203 let output_data = fs::read_to_string(output_path)?; 204 assert_eq!(output_data, test_data1); 205 206 Ok(()) 207 } 208}