218 lines
6.3 KiB
Rust
218 lines
6.3 KiB
Rust
```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<Box<dyn Read>>` - 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<Box<dyn Read>> {
|
|
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<Box<dyn Write>>` - 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<Box<dyn Write>> {
|
|
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<W: Write> {
|
|
encoder: Option<GzEncoder<W>>,
|
|
}
|
|
|
|
impl<W: Write> AutoFinishGzEncoder<W> {
|
|
/// 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<W>) -> AutoFinishGzEncoder<W> {
|
|
AutoFinishGzEncoder {
|
|
encoder: Some(gz_encoder),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<W: Write> Drop for AutoFinishGzEncoder<W> {
|
|
/// 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<W: Write> Write for AutoFinishGzEncoder<W> {
|
|
/// Writes data to the underlying GZip encoder.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `buf` - The byte slice to write.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `io::Result<usize>` - 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<usize> {
|
|
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()
|
|
}
|
|
}
|