use crate::compression_engine::{get_compression_engine, CompressionType}; use crate::services::error::CoreError; use std::io::Read; use std::path::PathBuf; use std::str::FromStr; use anyhow::anyhow; pub struct CompressionService; /// Service for handling compression and decompression of item content. /// /// Provides methods to read compressed item files either fully into memory /// or as streaming readers. Supports various compression types via engines. /// /// # Examples /// /// ``` /// let service = CompressionService::new(); /// let content = service.get_item_content(path, "gzip")?; /// ``` impl CompressionService { /// Creates a new CompressionService instance. /// /// # Returns /// /// A new `CompressionService`. /// /// # Examples /// /// ``` /// let service = CompressionService::new(); /// ``` pub fn new() -> Self { Self } /// Reads and decompresses the full content of an item file into memory. /// /// # Arguments /// /// * `item_path` - Path to the compressed item file. /// * `compression` - Compression type string (e.g., "gzip"). /// /// # Returns /// /// * `Result, CoreError>` - Decompressed content bytes. /// /// # Errors /// /// * `CoreError::Compression(...)` - If compression type invalid. /// * `CoreError::Other(...)` - If file open or read fails. /// /// # Examples /// /// ``` /// let content = service.get_item_content(item_path, "lz4")?; /// assert_eq!(content.len(), expected_size); /// ``` pub fn get_item_content(&self, item_path: PathBuf, compression: &str) -> Result, CoreError> { let compression_type = CompressionType::from_str(compression) .map_err(|e| CoreError::Compression(e.to_string()))?; let engine = get_compression_engine(compression_type) .map_err(|e| CoreError::Other(anyhow!(e.to_string())))?; let mut reader = engine.open(item_path.clone()) .map_err(|e| CoreError::Other(anyhow!("Failed to open item file {:?}: {}", item_path, e)))?; let mut content = Vec::new(); reader.read_to_end(&mut content)?; Ok(content) } /// Opens a streaming reader for decompressing item content. /// /// For Send compatibility, reads full content into memory and returns a Cursor. /// Note: Not suitable for very large files due to memory usage. /// /// # Arguments /// /// * `item_path` - Path to the compressed item file. /// * `compression` - Compression type string. /// /// # Returns /// /// * `Result, CoreError>` - Boxed streaming reader. /// /// # Errors /// /// * `CoreError::Compression(...)` - If compression type invalid. /// * `CoreError::Other(...)` - If file open or read fails. /// /// # Examples /// /// ``` /// let mut reader = service.stream_item_content(item_path, "gzip")?; /// let mut buf = [0; 1024]; /// let n = reader.read(&mut buf)?; /// ``` pub fn stream_item_content( &self, item_path: PathBuf, compression: &str, ) -> Result, CoreError> { let compression_type = CompressionType::from_str(compression) .map_err(|e| CoreError::Compression(e.to_string()))?; let engine = get_compression_engine(compression_type) .map_err(|e| CoreError::Other(anyhow!(e.to_string())))?; let reader = engine.open(item_path.clone()) .map_err(|e| CoreError::Other(anyhow!("Failed to open item file {:?}: {}", item_path, e)))?; // Since we can't guarantee the reader implements Send, we need to wrap it // We'll read the content into a buffer and return a Cursor which is Send // This is not ideal for large files, but it ensures Send is implemented let mut content = Vec::new(); let mut temp_reader = reader; temp_reader.read_to_end(&mut content)?; Ok(Box::new(std::io::Cursor::new(content))) } } impl Default for CompressionService { fn default() -> Self { Self::new() } }