use crate::error::{Error, Result};
use crate::streaming::EntrySource;
use std::borrow::Cow;
use std::io::{Read, Write};
const WALK_CHUNK: usize = 256 * 1024;
pub(crate) enum MemberBytes<'a> {
Borrowed(&'a [u8]),
Source(&'a EntrySource),
}
impl<'a> MemberBytes<'a> {
pub(crate) fn len(&self) -> Result<u64> {
match self {
Self::Borrowed(data) => Ok(data.len() as u64),
Self::Source(source) => source.len(),
}
}
pub(crate) fn load(&self) -> Result<Cow<'_, [u8]>> {
match self {
Self::Borrowed(data) => Ok(Cow::Borrowed(data)),
Self::Source(source) => {
let expected = source.len()?;
let capacity = usize::try_from(expected).map_err(|_| {
Error::InvalidHeader("member is larger than this host can hold")
})?;
let mut data = Vec::with_capacity(capacity);
source.open()?.read_to_end(&mut data)?;
if data.len() as u64 != expected {
return Err(Error::InvalidHeader(
"entry source size changed while compressing",
));
}
Ok(Cow::Owned(data))
}
}
}
pub(crate) fn walk(&self, mut visit: impl FnMut(&[u8])) -> Result<()> {
match self {
Self::Borrowed(data) => visit(data),
Self::Source(source) => {
let mut reader = source.open()?;
let mut buffer = vec![0u8; WALK_CHUNK];
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
visit(&buffer[..read]);
}
}
}
Ok(())
}
pub(crate) fn source(&self) -> Option<&'a EntrySource> {
match self {
Self::Borrowed(_) => None,
Self::Source(source) => Some(source),
}
}
}
pub(crate) enum MemberPayload<'a> {
Packed(Vec<u8>),
Copied(&'a EntrySource),
}
impl MemberPayload<'_> {
pub(crate) fn size(&self, unpacked_size: u64) -> u64 {
match self {
Self::Packed(packed) => packed.len() as u64,
Self::Copied(_) => unpacked_size,
}
}
pub(crate) fn write_to(&self, output: &mut dyn Write, expected: u64) -> Result<()> {
match self {
Self::Packed(packed) => output.write_all(packed)?,
Self::Copied(source) => {
let copied = std::io::copy(&mut source.open()?, output)?;
if copied != expected {
return Err(Error::InvalidHeader(
"entry source size changed while writing",
));
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_source_walks_in_chunks_and_loads_whole() {
let data: Vec<u8> = (0..WALK_CHUNK * 2 + 7).map(|index| index as u8).collect();
let source = EntrySource::from_bytes(data.clone());
let bytes = MemberBytes::Source(&source);
assert_eq!(bytes.len().unwrap(), data.len() as u64);
assert_eq!(bytes.load().unwrap().as_ref(), data.as_slice());
let mut chunks = Vec::new();
let mut seen = Vec::new();
bytes
.walk(|chunk| {
chunks.push(chunk.len());
seen.extend_from_slice(chunk);
})
.unwrap();
assert_eq!(seen, data);
assert!(chunks.len() > 1, "a large source should arrive in pieces");
}
#[test]
fn borrowed_bytes_are_never_copied() {
let data = b"already in memory".to_vec();
let bytes = MemberBytes::Borrowed(&data);
assert!(matches!(bytes.load().unwrap(), Cow::Borrowed(_)));
assert!(bytes.source().is_none());
}
}