packdir 0.1.0

Easily embed directories of binary file data
Documentation
use anyhow::{Result, bail};
use flate2::{Compression as GzipLevel, read::GzDecoder, write::GzEncoder};
use serde::{Deserialize, Serialize};
use std::{
    io::{Read, Write},
    path::Path,
};

use crate::pack;

pub const SIG: &[u8; 8] = b"PACK DIR";
pub const VERSION: u8 = 1;

/// Compression algorithm identifier
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "enc-dec", derive(bincode::Encode, bincode::Decode))]
pub enum Packer {
    #[default]
    Raw,
    Gzip(Gzip),
    Xz(Xz),
}

impl Packer {
    pub(crate) fn try_read_u8(data: &[u8], offset: &mut usize) -> Result<Self> {
        let packer = data[*offset].try_into()?;
        *offset += 1;
        Ok(packer)
    }
    pub fn pack_dir<P: AsRef<Path>>(&self, dir: P, pack_name: &str) -> Result<()> {
        pack(dir, pack_name, *self)
    }
    pub(crate) fn pack(&self, data: &[u8]) -> Result<Vec<u8>> {
        match self {
            Packer::Raw => Ok(data.to_vec()),
            Packer::Gzip(level) => {
                let mut encoder = GzEncoder::new(Vec::new(), level.into());
                encoder.write_all(data)?;
                Ok(encoder.finish()?)
            }
            Packer::Xz(level) => {
                use xz2::write::XzEncoder;
                let mut encoder = XzEncoder::new(Vec::new(), *level as u32);
                encoder.write_all(data)?;
                Ok(encoder.finish()?)
            }
        }
    }
    pub(crate) fn unpack(&self, data: &[u8]) -> Result<Vec<u8>> {
        match self {
            Packer::Raw => Ok(data.to_vec()),
            Packer::Gzip(_) => {
                let mut decoder = GzDecoder::new(data);
                let mut decompressed = Vec::new();
                decoder.read_to_end(&mut decompressed)?;
                Ok(decompressed)
            }
            Packer::Xz(_) => {
                use xz2::read::XzDecoder;
                let mut decoder = XzDecoder::new(data);
                let mut decompressed = Vec::new();
                decoder.read_to_end(&mut decompressed)?;
                Ok(decompressed)
            }
        }
    }
}
impl TryFrom<u8> for Packer {
    type Error = anyhow::Error;
    fn try_from(value: u8) -> Result<Self> {
        match value {
            0 => Ok(Packer::Raw),
            1 => Ok(Packer::Gzip(Gzip::default())),
            2 => Ok(Packer::Xz(Xz::default())),
            _ => bail!("Unsupported compression type: {}", value),
        }
    }
}

impl From<Packer> for u8 {
    fn from(val: Packer) -> Self {
        match val {
            Packer::Raw => 0,
            Packer::Gzip { .. } => 1,
            Packer::Xz { .. } => 2,
        }
    }
}

pub struct Raw;

impl From<Raw> for Packer {
    fn from(_: Raw) -> Self {
        Packer::Raw
    }
}

#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "enc-dec", derive(bincode::Encode, bincode::Decode))]
#[repr(u32)]
pub enum Xz {
    Level1 = 1,
    Level2 = 2,
    Level3 = 3,
    Level4 = 4,
    Level5 = 5,
    #[default]
    Level6 = 6,
    Level7 = 7,
    Level8 = 8,
    Level9 = 9,
}

impl Xz {
    pub fn best() -> Self {
        Self::Level9
    }
    pub fn fast() -> Self {
        Self::Level1
    }
    pub fn packer(&self) -> Packer {
        Packer::Xz(*self)
    }
    pub fn pack_dir<P: AsRef<Path>>(&self, dir: P, pack_name: &str) -> Result<()> {
        pack(dir, pack_name, self.packer())
    }
}

impl From<Xz> for Packer {
    fn from(xz: Xz) -> Self {
        Self::Xz(xz)
    }
}

#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "enc-dec", derive(bincode::Encode, bincode::Decode))]
#[repr(u32)]
pub enum Gzip {
    Level1 = 1,
    Level2 = 2,
    Level3 = 3,
    Level4 = 4,
    Level5 = 5,
    #[default]
    Level6 = 6,
    Level7 = 7,
    Level8 = 8,
    Level9 = 9,
}

impl Gzip {
    pub fn best() -> Self {
        Self::Level9
    }
    pub fn fast() -> Self {
        Self::Level1
    }
    pub fn packer(&self) -> Packer {
        Packer::Gzip(*self)
    }
    pub fn pack_dir<P: AsRef<Path>>(&self, dir: P, pack_name: &str) -> Result<()> {
        pack(dir, pack_name, self.packer())
    }
}

impl From<&Gzip> for GzipLevel {
    fn from(level: &Gzip) -> Self {
        GzipLevel::new(*level as u32)
    }
}

impl From<Gzip> for Packer {
    fn from(gzip: Gzip) -> Self {
        Self::Gzip(gzip)
    }
}