```rust /// GZip compression engine module. /// /// This module provides the implementation for GZip compression and decompression /// using the `flate2` crate. It includes the main `CompressionEngineGZip` struct /// and supporting types for handling GZip streams. /// /// # Usage /// /// ```rust /// use keep::compression_engine::get_compression_engine; /// let engine = get_compression_engine(keep::compression_engine::CompressionType::GZip) /// .expect("GZip engine creation failed"); /// let reader = engine.open("/path/to/file.gz".into()).expect("Open failed"); /// ``` use anyhow::Result; use log::*; use std::fs::File; use std::io; use std::io::{Read, Write}; use std::path::PathBuf; use flate2::Compression; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use crate::compression_engine::CompressionEngine; #[derive(Debug, Eq, PartialEq, Clone, Default)] /// GZip compression engine implementation. /// /// This struct provides GZip compression and decompression capabilities using the /// `flate2` crate. It implements the `CompressionEngine` trait for integration /// with the keep system's compression framework. pub struct CompressionEngineGZip {} impl CompressionEngineGZip { /// Creates a new instance of `CompressionEngineGZip`. /// /// # Returns /// /// A new `CompressionEngineGZip` instance. /// /// # Examples /// /// ``` /// let engine = CompressionEngineGZip::new(); /// ``` pub fn new() -> CompressionEngineGZip { CompressionEngineGZip {} } } impl CompressionEngine for CompressionEngineGZip { /// Checks if GZip compression is supported. /// /// GZip is a built-in compression method using the `flate2` crate, so it is /// always supported. /// /// # Returns /// /// Always returns `true` since GZip is built-in. /// /// # Examples /// /// ``` /// let engine = CompressionEngineGZip::new(); /// assert!(engine.is_supported()); /// ``` fn is_supported(&self) -> bool { true } /// Opens a GZip compressed file for reading. /// /// This method creates a `GzDecoder` wrapped around the file, allowing the /// file to be read as if it were uncompressed. /// /// # Arguments /// /// * `file_path` - Path to the GZip compressed file. /// /// # Returns /// /// * `Result>` - A boxed reader that decompresses the GZip file on read. /// /// # Errors /// /// * `anyhow::Error` - If the file cannot be opened or decoded. /// /// # Examples /// /// ``` /// let reader = engine.open("/path/to/file.gz".into()).expect("Open failed"); /// ``` fn open(&self, file_path: PathBuf) -> Result> { debug!("COMPRESSION: Opening {:?} using {:?}", file_path, *self); let file = File::open(file_path)?; Ok(Box::new(GzDecoder::new(file))) } /// Creates a new GZip compressed file for writing. /// /// This method creates a file and wraps it in a `GzEncoder` with default /// compression settings. It uses `AutoFinishGzEncoder` to ensure the GZip /// stream is properly closed on drop. /// /// # Arguments /// /// * `file_path` - Path where the GZip compressed file will be created. /// /// # Returns /// /// * `Result>` - A boxed writer that compresses data using GZip on write. /// /// # Errors /// /// * `anyhow::Error` - If the file cannot be created or encoder fails. /// /// # Examples /// /// ``` /// let writer = engine.create("/path/to/file.gz".into()).expect("Create failed"); /// ``` fn create(&self, file_path: PathBuf) -> Result> { debug!("COMPRESSION: Writing to {:?} using {:?}", file_path, *self); let file = File::create(file_path)?; let gzip_write = GzEncoder::new(file, Compression::default()); Ok(Box::new(AutoFinishGzEncoder::new(gzip_write))) } } #[derive(Debug)] /// Wrapper around `GzEncoder` that automatically finishes the compression stream on drop. /// /// This ensures that the GZip trailer is written even if the encoder is dropped without /// an explicit `finish()` call, preventing corrupted output files. pub struct AutoFinishGzEncoder { encoder: Option>, } impl AutoFinishGzEncoder { /// Creates a new `AutoFinishGzEncoder` wrapping the given GZip encoder. /// /// # Arguments /// /// * `gz_encoder` - The GZip encoder to wrap. /// /// # Returns /// /// A new `AutoFinishGzEncoder` instance. /// /// # Examples /// /// ``` /// let file = File::create("test.gz").unwrap(); /// let encoder = GzEncoder::new(file, Compression::default()); /// let auto_encoder = AutoFinishGzEncoder::new(encoder); /// ``` fn new(gz_encoder: GzEncoder) -> AutoFinishGzEncoder { AutoFinishGzEncoder { encoder: Some(gz_encoder), } } } impl Drop for AutoFinishGzEncoder { /// Automatically finishes the GZip encoding when the writer is dropped. /// /// This method ensures the GZip stream is properly closed by calling `finish()` /// on the underlying encoder. /// /// # Errors /// /// Errors during finish are logged but ignored. fn drop(&mut self) { if let Some(encoder) = self.encoder.take() { debug!("COMPRESSION: Finishing"); let _ = encoder.finish(); } } } impl Write for AutoFinishGzEncoder { /// Writes data to the underlying GZip encoder. /// /// # Arguments /// /// * `buf` - The byte slice to write. /// /// # Returns /// /// * `io::Result` - The number of bytes written or an I/O error. /// /// # Errors /// /// Propagates errors from the underlying encoder. fn write(&mut self, buf: &[u8]) -> io::Result { self.encoder.as_mut().unwrap().write(buf) } /// Flushes the underlying GZip encoder. /// /// # Returns /// /// * `io::Result<()>` - Success or an I/O error. /// /// # Errors /// /// Propagates errors from the underlying encoder. fn flush(&mut self) -> io::Result<()> { self.encoder.as_mut().unwrap().flush() } }