bitbottle 0.10.0

a modern archive file format
Documentation
use cfg_if::cfg_if;
use num_enum::TryFromPrimitive;
use snap::read::FrameDecoder;
use snap::write::FrameEncoder;
use std::fmt;
use std::io::{Read, Write};
use crate::{BottleError, BottleResult};


#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum CompressionAlgorithm {
    SNAPPY = 0,
    LZMA2 = 1,
}

cfg_if! {
    if #[cfg(feature = "lzma2")] {
        type LzmaLibraryWriter<W> = lzma::LzmaWriter<W>;
        type LzmaLibraryReader<R> = lzma::LzmaReader<R>;
    } else {
        type LzmaLibraryWriter<W> = W;
        type LzmaLibraryReader<R> = R;
    }
}

/// Abstract compressor that can use a runtime-selected algorithm to compress
/// wrap a `Write`.
pub enum Compressor<W: Write> {
    Snappy(Box<FrameEncoder<W>>),
    Lzma2(LzmaLibraryWriter<W>),
}

impl<W: Write> Compressor<W> {
    pub fn new(write: W, algorithm: CompressionAlgorithm) -> BottleResult<Compressor<W>> {
        match algorithm {
            CompressionAlgorithm::SNAPPY => Ok(Compressor::Snappy(Box::new(FrameEncoder::new(write)))),
            CompressionAlgorithm::LZMA2 => {
                cfg_if! {
                    if #[cfg(feature = "lzma2")] {
                        let encoder = lzma::LzmaWriter::new_compressor(write, 9).map_err(BottleError::Lzma2Error)?;
                        Ok(Compressor::Lzma2(encoder))
                    } else {
                        Compressor::Lzma2(write);
                        Err(BottleError::LzmaFeatureMissing)
                    }
                }
            },
        }
    }

    pub fn close(self) -> BottleResult<W> {
        match self {
            Compressor::Snappy(encoder) => encoder.into_inner().map_err(|_| BottleError::CompressionError),
            Compressor::Lzma2(encoder) => {
                cfg_if! {
                    if #[cfg(feature = "lzma2")] {
                        encoder.finish().map_err(BottleError::Lzma2Error)
                    } else {
                        let _ = encoder;
                        Err(BottleError::LzmaFeatureMissing)
                    }
                }
            }
        }
    }
}

impl<W: Write> Write for Compressor<W> {
    fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
        match self {
            Compressor::Snappy(encoder) => encoder.write(data),
            Compressor::Lzma2(encoder) => encoder.write(data),
        }
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}


/// Abstract decompressor that can use a runtime-selected algorithm to wrap
/// a `Read`.
pub enum Decompressor<R: Read> {
    Snappy(FrameDecoder<R>),
    Lzma2(LzmaLibraryReader<R>),
}

impl<R: Read> fmt::Debug for Decompressor<R> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Decompressor({})", match self {
            Decompressor::Snappy(_) => "snappy",
            Decompressor::Lzma2(_) => "lzma2",
        })
    }
}

impl<R: Read> Decompressor<R> {
    pub fn new(read: R, algorithm: CompressionAlgorithm) -> BottleResult<Decompressor<R>> {
        match algorithm {
            CompressionAlgorithm::SNAPPY => Ok(Decompressor::Snappy(FrameDecoder::new(read))),
            CompressionAlgorithm::LZMA2 => {
                cfg_if! {
                    if #[cfg(feature = "lzma2")] {
                        let decoder = lzma::LzmaReader::new_decompressor(read).map_err(BottleError::Lzma2Error)?;
                        Ok(Decompressor::Lzma2(decoder))
                    } else {
                        Decompressor::Lzma2(read);
                        Err(BottleError::LzmaFeatureMissing)
                    }
                }
            },
        }
    }
}

impl<R: Read> Read for Decompressor<R> {
    fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
        match self {
            Decompressor::Snappy(decoder) => decoder.read(buffer),
            Decompressor::Lzma2(decoder) => decoder.read(buffer),
        }
    }
}