assetpack-core 0.3.0

Content-addressed asset packing, chunking, recipe, and SQLite storage primitives.
Documentation
use std::{
  fs,
  io::{self, Cursor, Read, Seek, SeekFrom, Write},
  path::Path,
};

use crate::error::{Error, Result};

#[derive(Debug)]
pub enum StoredStreamSpool {
  Memory { buffer: Vec<u8>, max_bytes: u64 },
  File { file: tempfile::NamedTempFile, len: u64 },
}

impl StoredStreamSpool {
  pub fn from_bytes(buffer: Vec<u8>) -> Self {
    let max_bytes = buffer.len().max(1) as u64;
    Self::Memory { buffer, max_bytes }
  }

  pub fn new_memory(max_bytes: u64) -> Self {
    Self::Memory {
      buffer: Vec::new(),
      max_bytes: max_bytes.max(1),
    }
  }

  pub fn new_file(temp_dir: Option<&Path>) -> Result<Self> {
    let file = if let Some(dir) = temp_dir {
      fs::create_dir_all(dir)?;
      tempfile::Builder::new()
        .prefix("assetpack-transform-precomp2-")
        .tempfile_in(dir)
        .map_err(Error::Io)?
    } else {
      tempfile::Builder::new()
        .prefix("assetpack-transform-precomp2-")
        .tempfile()
        .map_err(Error::Io)?
    };
    Ok(Self::File { file, len: 0 })
  }

  pub fn len(&self) -> u64 {
    match self {
      StoredStreamSpool::Memory { buffer, .. } => buffer.len() as u64,
      StoredStreamSpool::File { len, .. } => *len,
    }
  }

  pub fn is_empty(&self) -> bool {
    self.len() == 0
  }

  pub fn as_slice(&self) -> Option<&[u8]> {
    match self {
      StoredStreamSpool::Memory { buffer, .. } => Some(buffer.as_slice()),
      StoredStreamSpool::File { .. } => None,
    }
  }

  pub fn reader(&self) -> Result<StoredStreamReader<'_>> {
    match self {
      StoredStreamSpool::Memory { buffer, .. } => Ok(StoredStreamReader::Memory(Cursor::new(buffer.as_slice()))),
      StoredStreamSpool::File { file, .. } => Ok(StoredStreamReader::File(file.reopen()?)),
    }
  }

  pub fn into_bytes(self) -> Result<Vec<u8>> {
    match self {
      StoredStreamSpool::Memory { buffer, .. } => Ok(buffer),
      StoredStreamSpool::File { mut file, .. } => {
        let mut buf = Vec::new();
        file.rewind()?;
        file.read_to_end(&mut buf)?;
        Ok(buf)
      }
    }
  }
}

impl Write for StoredStreamSpool {
  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
    match self {
      StoredStreamSpool::Memory { buffer, max_bytes } => {
        let next_len = buffer.len().saturating_add(buf.len());
        if next_len as u64 > *max_bytes {
          return Err(io::Error::new(io::ErrorKind::OutOfMemory, "stored stream exceeded memory limit"));
        }
        buffer.extend_from_slice(buf);
        Ok(buf.len())
      }
      StoredStreamSpool::File { file, len } => {
        file.as_file_mut().write_all(buf)?;
        *len += buf.len() as u64;
        Ok(buf.len())
      }
    }
  }

  fn flush(&mut self) -> io::Result<()> {
    match self {
      StoredStreamSpool::Memory { .. } => Ok(()),
      StoredStreamSpool::File { file, .. } => file.as_file_mut().flush(),
    }
  }
}

pub enum StoredStreamReader<'a> {
  Memory(Cursor<&'a [u8]>),
  File(std::fs::File),
}

impl Read for StoredStreamReader<'_> {
  fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
    match self {
      StoredStreamReader::Memory(cursor) => cursor.read(buf),
      StoredStreamReader::File(file) => file.read(buf),
    }
  }
}

impl Seek for StoredStreamReader<'_> {
  fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
    match self {
      StoredStreamReader::Memory(cursor) => cursor.seek(pos),
      StoredStreamReader::File(file) => file.seek(pos),
    }
  }
}