1use crate::error::{IgtlError, Result};
32use flate2::read::{DeflateDecoder, GzDecoder};
33use flate2::write::{DeflateEncoder, GzEncoder};
34use flate2::Compression;
35use std::io::{Read, Write};
36use tracing::{debug, info, trace};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum CompressionType {
41 None,
43 Deflate,
45 Gzip,
47}
48
49impl CompressionType {
50 pub fn name(&self) -> &'static str {
52 match self {
53 Self::None => "none",
54 Self::Deflate => "deflate",
55 Self::Gzip => "gzip",
56 }
57 }
58
59 pub fn is_compressed(&self) -> bool {
61 !matches!(self, Self::None)
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CompressionLevel {
68 None,
70 Fast,
72 Default,
74 Best,
76 Custom(u32),
78}
79
80impl CompressionLevel {
81 fn to_flate2(self) -> Compression {
83 match self {
84 Self::None => Compression::none(),
85 Self::Fast => Compression::fast(),
86 Self::Default => Compression::default(),
87 Self::Best => Compression::best(),
88 Self::Custom(level) => Compression::new(level),
89 }
90 }
91
92 pub fn level(&self) -> u32 {
94 match self {
95 Self::None => 0,
96 Self::Fast => 1,
97 Self::Default => 6,
98 Self::Best => 9,
99 Self::Custom(level) => *level,
100 }
101 }
102}
103
104impl Default for CompressionLevel {
105 fn default() -> Self {
106 Self::Default
107 }
108}
109
110pub fn compress(
135 data: &[u8],
136 compression_type: CompressionType,
137 level: CompressionLevel,
138) -> Result<Vec<u8>> {
139 trace!(
140 compression_type = compression_type.name(),
141 level = level.level(),
142 input_size = data.len(),
143 "Starting compression"
144 );
145
146 let compressed = match compression_type {
147 CompressionType::None => {
148 debug!("No compression requested, returning original data");
149 data.to_vec()
150 }
151 CompressionType::Deflate => {
152 let mut encoder = DeflateEncoder::new(Vec::new(), level.to_flate2());
153 encoder.write_all(data).map_err(|e| {
154 IgtlError::Io(std::io::Error::new(
155 e.kind(),
156 format!("Deflate compression failed: {}", e),
157 ))
158 })?;
159 encoder.finish().map_err(|e| {
160 IgtlError::Io(std::io::Error::new(
161 e.kind(),
162 format!("Deflate compression finish failed: {}", e),
163 ))
164 })?
165 }
166 CompressionType::Gzip => {
167 let mut encoder = GzEncoder::new(Vec::new(), level.to_flate2());
168 encoder.write_all(data).map_err(|e| {
169 IgtlError::Io(std::io::Error::new(
170 e.kind(),
171 format!("Gzip compression failed: {}", e),
172 ))
173 })?;
174 encoder.finish().map_err(|e| {
175 IgtlError::Io(std::io::Error::new(
176 e.kind(),
177 format!("Gzip compression finish failed: {}", e),
178 ))
179 })?
180 }
181 };
182
183 let ratio = if !data.is_empty() {
184 (compressed.len() as f64 / data.len() as f64) * 100.0
185 } else {
186 0.0
187 };
188
189 info!(
190 compression_type = compression_type.name(),
191 level = level.level(),
192 original_size = data.len(),
193 compressed_size = compressed.len(),
194 ratio_pct = format!("{:.1}%", ratio),
195 "Compression completed"
196 );
197
198 Ok(compressed)
199}
200
201pub fn decompress(data: &[u8], compression_type: CompressionType) -> Result<Vec<u8>> {
226 trace!(
227 compression_type = compression_type.name(),
228 compressed_size = data.len(),
229 "Starting decompression"
230 );
231
232 let decompressed = match compression_type {
233 CompressionType::None => {
234 debug!("No decompression needed, returning original data");
235 data.to_vec()
236 }
237 CompressionType::Deflate => {
238 let mut decoder = DeflateDecoder::new(data);
239 let mut decompressed = Vec::new();
240 decoder.read_to_end(&mut decompressed).map_err(|e| {
241 IgtlError::Io(std::io::Error::new(
242 e.kind(),
243 format!("Deflate decompression failed: {}", e),
244 ))
245 })?;
246 decompressed
247 }
248 CompressionType::Gzip => {
249 let mut decoder = GzDecoder::new(data);
250 let mut decompressed = Vec::new();
251 decoder.read_to_end(&mut decompressed).map_err(|e| {
252 IgtlError::Io(std::io::Error::new(
253 e.kind(),
254 format!("Gzip decompression failed: {}", e),
255 ))
256 })?;
257 decompressed
258 }
259 };
260
261 info!(
262 compression_type = compression_type.name(),
263 compressed_size = data.len(),
264 decompressed_size = decompressed.len(),
265 "Decompression completed"
266 );
267
268 Ok(decompressed)
269}
270
271#[derive(Debug, Clone)]
273pub struct CompressionStats {
274 pub original_size: usize,
276 pub compressed_size: usize,
278 pub ratio: f64,
280 pub space_saved: usize,
282 pub compression_type: CompressionType,
284 pub level: CompressionLevel,
286}
287
288impl CompressionStats {
289 pub fn calculate(
291 original_size: usize,
292 compressed_size: usize,
293 compression_type: CompressionType,
294 level: CompressionLevel,
295 ) -> Self {
296 let ratio = if original_size > 0 {
297 compressed_size as f64 / original_size as f64
298 } else {
299 0.0
300 };
301
302 let space_saved = original_size.saturating_sub(compressed_size);
303
304 Self {
305 original_size,
306 compressed_size,
307 ratio,
308 space_saved,
309 compression_type,
310 level,
311 }
312 }
313
314 pub fn ratio_percent(&self) -> f64 {
316 self.ratio * 100.0
317 }
318
319 pub fn space_saved_percent(&self) -> f64 {
321 if self.original_size > 0 {
322 (self.space_saved as f64 / self.original_size as f64) * 100.0
323 } else {
324 0.0
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_no_compression() {
335 let data = vec![1, 2, 3, 4, 5];
336 let compressed = compress(&data, CompressionType::None, CompressionLevel::Default).unwrap();
337 assert_eq!(data, compressed);
338
339 let decompressed = decompress(&compressed, CompressionType::None).unwrap();
340 assert_eq!(data, decompressed);
341 }
342
343 #[test]
344 fn test_deflate_compression() {
345 let data = vec![0u8; 1000]; let compressed =
347 compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
348
349 assert!(compressed.len() < data.len());
351 assert!(compressed.len() < 100); let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
354 assert_eq!(data, decompressed);
355 }
356
357 #[test]
358 fn test_gzip_compression() {
359 let data = vec![1u8; 1000];
360 let compressed = compress(&data, CompressionType::Gzip, CompressionLevel::Default).unwrap();
361
362 assert!(compressed.len() < data.len());
363
364 let decompressed = decompress(&compressed, CompressionType::Gzip).unwrap();
365 assert_eq!(data, decompressed);
366 }
367
368 #[test]
369 fn test_compression_levels() {
370 let data = vec![0u8; 10000];
371
372 let fast = compress(&data, CompressionType::Deflate, CompressionLevel::Fast).unwrap();
373 let default = compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
374 let best = compress(&data, CompressionType::Deflate, CompressionLevel::Best).unwrap();
375
376 assert!(best.len() <= default.len());
378 assert!(default.len() <= fast.len() || default.len() < 100); assert_eq!(data, decompress(&fast, CompressionType::Deflate).unwrap());
382 assert_eq!(
383 data,
384 decompress(&default, CompressionType::Deflate).unwrap()
385 );
386 assert_eq!(data, decompress(&best, CompressionType::Deflate).unwrap());
387 }
388
389 #[test]
390 fn test_random_data_compression() {
391 let data: Vec<u8> = (0..1000).map(|i| (i * 37 % 256) as u8).collect();
393
394 let compressed =
395 compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
396
397 let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
399 assert_eq!(data, decompressed);
400 }
401
402 #[test]
403 fn test_empty_data() {
404 let data = vec![];
405 let compressed =
406 compress(&data, CompressionType::Deflate, CompressionLevel::Default).unwrap();
407 let decompressed = decompress(&compressed, CompressionType::Deflate).unwrap();
408 assert_eq!(data, decompressed);
409 }
410
411 #[test]
412 fn test_compression_stats() {
413 let stats = CompressionStats::calculate(
414 1000,
415 500,
416 CompressionType::Deflate,
417 CompressionLevel::Default,
418 );
419
420 assert_eq!(stats.original_size, 1000);
421 assert_eq!(stats.compressed_size, 500);
422 assert_eq!(stats.ratio, 0.5);
423 assert_eq!(stats.space_saved, 500);
424 assert_eq!(stats.ratio_percent(), 50.0);
425 assert_eq!(stats.space_saved_percent(), 50.0);
426 }
427
428 #[test]
429 fn test_compression_type_names() {
430 assert_eq!(CompressionType::None.name(), "none");
431 assert_eq!(CompressionType::Deflate.name(), "deflate");
432 assert_eq!(CompressionType::Gzip.name(), "gzip");
433 }
434
435 #[test]
436 fn test_compression_level_values() {
437 assert_eq!(CompressionLevel::None.level(), 0);
438 assert_eq!(CompressionLevel::Fast.level(), 1);
439 assert_eq!(CompressionLevel::Default.level(), 6);
440 assert_eq!(CompressionLevel::Best.level(), 9);
441 assert_eq!(CompressionLevel::Custom(5).level(), 5);
442 }
443}