use std::borrow::Cow;
use std::io;
use crate::hash::Hash;
use crate::object::{ChunkedBlob, Object};
use super::{WorktreeError, WorktreeResult};
pub fn read_blob<S: crate::store::ObjectSource + ?Sized>(
store: &S,
hash: &Hash,
) -> WorktreeResult<Vec<u8>> {
LoadedBlob::load(store, hash)?.into_content(store)
}
#[derive(Debug)]
pub enum LoadedBlob {
Inline(Vec<u8>),
Chunked(ChunkedBlob),
}
impl LoadedBlob {
pub fn load<S: crate::store::ObjectSource + ?Sized>(
store: &S,
hash: &Hash,
) -> WorktreeResult<Self> {
match store.read_object(hash)? {
Object::Blob(b) => Ok(Self::Inline(b.data)),
Object::ChunkedBlob(manifest) => Ok(Self::Chunked(manifest)),
other => Err(not_a_blob("object", hash, &other)),
}
}
#[must_use]
pub fn len(&self) -> u64 {
match self {
Self::Inline(data) => data.len() as u64,
Self::Chunked(manifest) => manifest.total_size,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn prefix<S: crate::store::ObjectSource + ?Sized>(
&self,
store: &S,
max_len: usize,
) -> WorktreeResult<Cow<'_, [u8]>> {
match self {
Self::Inline(data) => Ok(Cow::Borrowed(&data[..data.len().min(max_len)])),
Self::Chunked(manifest) => {
let cap = usize::try_from(manifest.total_size)
.unwrap_or(max_len)
.min(max_len);
let mut data = Vec::with_capacity(cap);
for chunk in &manifest.chunks {
if data.len() >= max_len {
break;
}
data.extend_from_slice(&read_chunk(store, chunk)?);
}
data.truncate(max_len);
Ok(Cow::Owned(data))
}
}
}
pub fn into_content<S: crate::store::ObjectSource + ?Sized>(
self,
store: &S,
) -> WorktreeResult<Vec<u8>> {
match self {
Self::Inline(data) => Ok(data),
Self::Chunked(manifest) => {
let mut data =
Vec::with_capacity(usize::try_from(manifest.total_size).unwrap_or(0));
for chunk in &manifest.chunks {
data.extend_from_slice(&read_chunk(store, chunk)?);
}
manifest.check_reassembled_size(data.len())?;
Ok(data)
}
}
}
#[must_use]
pub fn empty() -> Self {
Self::Inline(Vec::new())
}
}
fn read_chunk<S: crate::store::ObjectSource + ?Sized>(
store: &S,
chunk: &Hash,
) -> WorktreeResult<Vec<u8>> {
match store.read_object(chunk)? {
Object::Blob(b) => Ok(b.data),
other => Err(not_a_blob("chunk", chunk, &other)),
}
}
fn not_a_blob(what: &str, hash: &Hash, got: &Object) -> WorktreeError {
WorktreeError::Io(io::Error::other(format!(
"{what} {} is not a blob (got {})",
crate::hash::to_hex(hash),
got.object_type().name()
)))
}