pklib 0.2.0

Pure Rust implementation of PKWare Data Compression Library (DCL) with full PKLib compatibility
Documentation
//! Async compression module
//!
//! This module provides async streaming compression capabilities with
//! pipelined operations for improved performance.

#[cfg(feature = "async")]
/// Async streaming compression with pipelined operations
pub mod writer {
    use crate::implode::ImplodeState;
    use crate::{CompressionMode, DictionarySize, PkLibError, Result};
    use pin_project::pin_project;
    use std::pin::Pin;
    use std::task::{Context, Poll};
    use tokio::io::{AsyncWrite, AsyncWriteExt};

    /// Async streaming compressor with pipeline processing
    #[pin_project]
    #[derive(Debug)]
    pub struct AsyncImplodeWriter<W: AsyncWrite + Unpin> {
        #[pin]
        writer: W,
        state: ImplodeState,
        mode: CompressionMode,
        dict_size: DictionarySize,
        initialized: bool,
        finished: bool,
        // Pipeline buffers for overlapped operations
        input_buffers: [Vec<u8>; 3],
        compress_buffers: [Vec<u8>; 3],
        output_buffers: [Vec<u8>; 3],
        current_buffer: usize,
        buffer_size: usize,
        pending_input: Vec<u8>,
    }

    impl<W: AsyncWrite + Unpin> AsyncImplodeWriter<W> {
        /// Create a new AsyncImplodeWriter
        pub fn new(writer: W, mode: CompressionMode, dict_size: DictionarySize) -> Result<Self> {
            Self::with_buffer_size(writer, mode, dict_size, 64 * 1024)
        }

        /// Create a new AsyncImplodeWriter with custom buffer size
        pub fn with_buffer_size(
            writer: W,
            mode: CompressionMode,
            dict_size: DictionarySize,
            buffer_size: usize,
        ) -> Result<Self> {
            let state = ImplodeState::new(mode, dict_size)?;

            Ok(Self {
                writer,
                state,
                mode,
                dict_size,
                initialized: false,
                finished: false,
                input_buffers: [
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                ],
                compress_buffers: [
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                ],
                output_buffers: [
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                    Vec::with_capacity(buffer_size),
                ],
                current_buffer: 0,
                buffer_size,
                pending_input: Vec::new(),
            })
        }

        /// Initialize the writer by setting up compression state
        async fn initialize(&mut self) -> Result<()> {
            if self.initialized {
                return Ok(());
            }

            // Write header
            let header = [self.mode as u8, self.dict_size.bits()];
            self.writer.write_all(&header).await?;

            self.initialized = true;
            Ok(())
        }

        /// Write a chunk of data asynchronously
        pub async fn write_chunk(&mut self, data: &[u8]) -> Result<()> {
            if !self.initialized {
                self.initialize().await?;
            }

            if self.finished {
                return Err(PkLibError::InvalidData(
                    "Writer already finished".to_string(),
                ));
            }

            // Add data to pending input
            self.pending_input.extend_from_slice(data);

            // Process full buffers
            while self.pending_input.len() >= self.buffer_size {
                let mut chunk = self.pending_input.split_off(self.buffer_size);
                std::mem::swap(&mut chunk, &mut self.pending_input);

                self.process_buffer(chunk).await?;
            }

            Ok(())
        }

        /// Process a buffer through the compression pipeline
        async fn process_buffer(&mut self, data: Vec<u8>) -> Result<()> {
            // Simulate compression work
            let compressed = self.compress_data(&data)?;

            // Write compressed data
            if !compressed.is_empty() {
                self.writer.write_all(&compressed).await?;
            }

            Ok(())
        }

        /// Compress data using PKLib algorithm
        fn compress_data(&mut self, data: &[u8]) -> Result<Vec<u8>> {
            // Copy data to work buffer
            if data.len() > self.state.work_buff.len() {
                return Err(PkLibError::InvalidData(
                    "Data too large for buffer".to_string(),
                ));
            }

            self.state.work_buff[..data.len()].copy_from_slice(data);
            self.state.work_bytes = data.len();

            // Build hash table and compress
            if self.state.work_bytes > 1 {
                self.state.sort_buffer(0, self.state.work_bytes);

                // Simplified compression logic
                let mut output = Vec::new();
                let mut pos = 0;

                while pos < self.state.work_bytes {
                    let match_result = self.state.find_repetition(pos);

                    if match_result.is_match() {
                        // Encode match (simplified)
                        let length_code = (match_result.length + 0xFE).min(255);
                        output.push(length_code as u8);

                        // Encode distance (simplified)
                        let distance = match_result.distance.min(255);
                        output.push(distance as u8);

                        pos += match_result.length;
                    } else {
                        // Encode literal
                        output.push(self.state.work_buff[pos]);
                        pos += 1;
                    }
                }

                return Ok(output);
            }

            Ok(Vec::new())
        }

        /// Finish compression and flush remaining data
        pub async fn finish(mut self) -> Result<W> {
            if self.finished {
                return Ok(self.writer);
            }

            if !self.initialized {
                self.initialize().await?;
            }

            // Process any remaining input
            if !self.pending_input.is_empty() {
                let remaining = std::mem::take(&mut self.pending_input);
                self.process_buffer(remaining).await?;
            }

            // Write end marker
            let end_marker = [0x05, 0x03]; // Simplified end marker
            self.writer.write_all(&end_marker).await?;

            // Flush writer
            self.writer.flush().await?;

            self.finished = true;
            Ok(self.writer)
        }

        /// Flush any pending data
        pub async fn flush(&mut self) -> Result<()> {
            if !self.initialized {
                self.initialize().await?;
            }

            // Process pending input if we have enough for a partial buffer
            if self.pending_input.len() > self.buffer_size / 2 {
                let to_process = std::mem::take(&mut self.pending_input);
                self.process_buffer(to_process).await?;
            }

            self.writer.flush().await?;
            Ok(())
        }
    }

    impl<W: AsyncWrite + Unpin> tokio::io::AsyncWrite for AsyncImplodeWriter<W> {
        fn poll_write(
            self: Pin<&mut Self>,
            _cx: &mut Context<'_>,
            buf: &[u8],
        ) -> Poll<std::io::Result<usize>> {
            let this = self.project();

            // Add data to pending input
            this.pending_input.extend_from_slice(buf);

            // For async write trait, we just accept all data
            Poll::Ready(Ok(buf.len()))
        }

        fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
            let this = self.project();
            this.writer.poll_flush(cx)
        }

        fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
            let this = self.project();
            this.writer.poll_shutdown(cx)
        }
    }
}

#[cfg(feature = "async")]
pub use writer::AsyncImplodeWriter;