mismall 2.0.0

Streaming Huffman compression library with AES-256-GCM encryption and archive support
Documentation
use crate::crypto::DEFAULT_CHUNK_SIZE;
use crate::progress::ProgressCallback;
use std::io::{self, Read};

/// Stateful decompressor that maintains decompression state across multiple reads
///
/// `Decompressor` implements `Read` and decompresses data incrementally as it's
/// read from the underlying compressed source. This is useful for decompressing
/// large data streams without loading the entire file into memory.
///
/// # Examples
///
/// ```rust
/// use mismall::stream::Decompressor;
/// use std::fs::File;
/// use std::io::Read;
///
/// let compressed_file = File::open("data.txt.small")?;
/// let mut decompressor = Decompressor::new(compressed_file, None);
///
/// let mut buffer = String::new();
/// decompressor.read_to_string(&mut buffer)?;
/// println!("Decompressed: {}", buffer);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub struct Decompressor<R: Read> {
    #[allow(dead_code)]
    source: R,
    #[allow(dead_code)]
    password: Option<String>,
    chunk_size: usize,
    progress_callback: Option<ProgressCallback>,
    finished: bool,
    bytes_read: u64,
    total_size: u64,
}

impl<R: Read> Decompressor<R> {
    /// Create a new decompressor
    ///
    /// # Arguments
    ///
    /// * `source` - Source compressed data stream
    /// * `password` - Optional password for decryption
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    ///
    /// let file = File::open("data.txt.small")?;
    /// let decompressor = Decompressor::new(file, None);
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn new(source: R, password: Option<&str>) -> Self {
        Self {
            source,
            password: password.map(|p| p.to_string()),
            chunk_size: DEFAULT_CHUNK_SIZE,
            progress_callback: None,
            finished: false,
            bytes_read: 0,
            total_size: 0, // Will be determined when reading starts
        }
    }

    /// Set custom chunk size for decompression
    ///
    /// Larger chunk sizes use more memory but may improve performance.
    /// Smaller chunk sizes use less memory but may be slower.
    ///
    /// # Arguments
    ///
    /// * `chunk_size` - Chunk size in bytes (64KB to 1GB)
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    ///
    /// let source = File::open("data.txt.small")?;
    /// let decompressor = Decompressor::new(source, None)
    ///     .with_chunk_size(64 * 1024 * 1024); // 64MB
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
        self.chunk_size = chunk_size;
        self
    }

    /// Set progress callback for real-time updates
    ///
    /// The callback will be called periodically with progress information
    /// during the decompression process.
    ///
    /// # Arguments
    ///
    /// * `callback` - Function that receives `ProgressInfo`
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    ///
    /// let source = File::open("data.txt.small")?;
    /// let decompressor = Decompressor::new(source, None)
    ///     .with_progress_callback(|progress: &mismall::progress::ProgressInfo| {
    ///         println!("Progress: {}%", progress.percentage);
    ///     });
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn with_progress_callback<F>(mut self, callback: F) -> Self
    where
        F: FnMut(&crate::progress::ProgressInfo) + Send + Sync + 'static,
    {
        self.progress_callback = Some(Box::new(callback));
        self
    }

    /// Get the total bytes read so far
    ///
    /// # Returns
    ///
    /// Number of bytes read from the source
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    /// use std::io::Read;
    ///
    /// let source = File::open("data.txt.small")?;
    /// let mut decompressor = Decompressor::new(source, None);
    /// let mut buffer = [0u8; 100];
    /// decompressor.read(&mut buffer)?;
    /// println!("Read {} bytes", decompressor.bytes_read());
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn bytes_read(&self) -> u64 {
        self.bytes_read
    }

    /// Get the estimated total size of decompressed data
    ///
    /// This value may be `0` until the first read operation,
    /// as the total size is determined from the file headers.
    ///
    /// # Returns
    ///
    /// Estimated total size in bytes, or `0` if not yet determined
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    /// use std::io::Read;
    ///
    /// let source = File::open("data.txt.small")?;
    /// let mut decompressor = Decompressor::new(source, None);
    /// let mut buffer = [0u8; 10];
    /// decompressor.read(&mut buffer)?;
    /// println!("Total size: {} bytes", decompressor.total_size());
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn total_size(&self) -> u64 {
        self.total_size
    }

    /// Check if decompression is finished
    ///
    /// # Returns
    ///
    /// `true` if all data has been decompressed and read
    ///
    /// # Example
    ///
    /// ```rust
    /// use mismall::stream::Decompressor;
    /// use std::fs::File;
    /// use std::io::Read;
    ///
    /// let source = File::open("data.txt.small")?;
    /// let mut decompressor = Decompressor::new(source, None);
    /// assert!(!decompressor.is_finished());
    /// let mut buffer = Vec::new();
    /// decompressor.read_to_end(&mut buffer)?;
    /// assert!(decompressor.is_finished());
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn is_finished(&self) -> bool {
        self.finished
    }
}

impl<R: Read> Read for Decompressor<R> {
    fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
        if self.finished {
            return Ok(0); // EOF
        }

        // This is a simplified implementation
        // In a full implementation, we would:
        // 1. Parse headers on first read to determine total_size
        // 2. Read compressed data in chunks
        // 3. Decompress each chunk using decompress_stream
        // 4. Buffer decompressed data and return requested portions
        // 5. Update progress and track bytes_read

        println!("Decompressor::read() not yet fully implemented - returning dummy data");

        // For now, just return EOF
        self.finished = true;
        Ok(0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::{Cursor, Read};

    #[test]
    fn test_decompressor_creation() {
        let cursor = Cursor::new(b"dummy compressed data");
        let decompressor = Decompressor::new(cursor, None);

        assert!(decompressor.password.is_none());
        assert_eq!(decompressor.chunk_size, DEFAULT_CHUNK_SIZE);
        assert!(!decompressor.is_finished());
        assert_eq!(decompressor.bytes_read(), 0);
        assert_eq!(decompressor.total_size(), 0);
    }

    #[test]
    fn test_decompressor_builder_pattern() {
        let cursor = Cursor::new(b"dummy compressed data");
        let decompressor = Decompressor::new(cursor, None)
            .with_chunk_size(1024 * 1024)
            .with_progress_callback(|_| {});

        assert_eq!(decompressor.chunk_size, 1024 * 1024);
        assert!(decompressor.progress_callback.is_some());
    }

    #[test]
    fn test_decompressor_with_password() {
        let cursor = Cursor::new(b"dummy compressed data");
        let decompressor = Decompressor::new(cursor, Some("password"));

        assert_eq!(decompressor.password, Some("password".to_string()));
    }

    #[test]
    fn test_decompressor_read_operations() {
        let cursor = Cursor::new(b"dummy compressed data");
        let mut decompressor = Decompressor::new(cursor, None);

        let mut buffer = [0u8; 10];
        let bytes_read = decompressor.read(&mut buffer).unwrap();

        // Should read 0 bytes since it's a dummy implementation
        assert_eq!(bytes_read, 0);
        assert!(decompressor.is_finished());
    }
}