this repo has no description
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}