applesauce-core 0.3.1

A low level library interface for compressing and decompressing files using macos transparent compression
Documentation
use crate::decmpfs::{BlockInfo, Storage};
use crate::{compressor, decmpfs};
use std::io::{self, BufReader, Cursor, Read, Seek};

pub trait Open {
    type ResourceFork: Read + Seek;

    fn open_resource_fork(self) -> io::Result<Self::ResourceFork>;
}

impl<R: Read + Seek, F: FnOnce() -> R> Open for F {
    type ResourceFork = R;

    #[inline]
    fn open_resource_fork(self) -> io::Result<Self::ResourceFork> {
        Ok(self())
    }
}

#[derive(Debug)]
enum State<R> {
    Xattr(Cursor<Vec<u8>>),
    ResourceFork {
        // Stored in reverse order, so that we can pop() them off
        block_infos: Vec<BlockInfo>,
        last_offset: u32,
        reader: BufReader<R>,
    },
}

#[derive(Debug)]
pub struct Reader<R> {
    kind: compressor::Kind,
    state: State<R>,
}

impl<R: Read + Seek> Reader<R> {
    pub fn new<O>(decmpfs_data: &[u8], open: O) -> io::Result<Self>
    where
        O: Open<ResourceFork = R>,
    {
        let decmpfs_value = decmpfs::Value::from_data(decmpfs_data)?;
        let (kind, storage) = decmpfs_value
            .compression_type
            .compression_storage()
            .filter(|(kind, _)| kind.supported())
            .ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::Other,
                    "unsupported compression kind or storage",
                )
            })?;
        let state = match storage {
            Storage::Xattr => State::Xattr(Cursor::new(decmpfs_value.extra_data.to_vec())),
            Storage::ResourceFork => {
                let mut rfork = BufReader::new(open.open_resource_fork()?);
                let mut blocks_info =
                    kind.read_block_info(&mut rfork, decmpfs_value.uncompressed_size)?;

                // Seek back to the beginning of the resource fork
                rfork.rewind()?;
                // Reverse the block infos so that we can pop() them off
                blocks_info.reverse();

                State::ResourceFork {
                    block_infos: blocks_info,
                    last_offset: 0,
                    reader: rfork,
                }
            }
        };
        Ok(Self { kind, state })
    }

    pub fn read_block_into(&mut self, dst: &mut Vec<u8>) -> io::Result<bool> {
        match &mut self.state {
            State::Xattr(cursor) => cursor.read_to_end(dst).map(|bytes_read| bytes_read > 0),
            State::ResourceFork {
                block_infos,
                last_offset,
                reader,
            } => {
                let block = match block_infos.pop() {
                    Some(block) => block,
                    None => return Ok(false),
                };
                let diff = i64::from(block.offset) - i64::from(*last_offset);
                reader.seek_relative(diff)?;

                let bytes_read = reader
                    .by_ref()
                    .take(block.compressed_size.into())
                    .read_to_end(dst)?;
                if bytes_read < block.compressed_size as usize {
                    return Err(io::ErrorKind::UnexpectedEof.into());
                }
                *last_offset = block
                    .offset
                    .checked_add(block.compressed_size)
                    .ok_or(io::ErrorKind::InvalidData)?;
                Ok(true)
            }
        }
    }

    #[inline]
    pub fn compression_kind(&self) -> compressor::Kind {
        self.kind
    }

    #[inline]
    pub fn remaining_blocks(&self) -> usize {
        match &self.state {
            State::Xattr(cursor) => {
                let remaining = cursor.get_ref().len() as u64 - cursor.position();
                usize::from(remaining > 0)
            }
            State::ResourceFork { block_infos, .. } => block_infos.len(),
        }
    }
}