gix_odb/store_impls/loose/
find.rsuse std::{cmp::Ordering, collections::HashSet, fs, io::Read, path::PathBuf};
use gix_features::zlib;
use crate::store_impls::loose::{hash_path, Store, HEADER_MAX_SIZE};
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error("decompression of loose object at '{path}' failed")]
DecompressFile {
source: zlib::inflate::Error,
path: PathBuf,
},
#[error("file at '{path}' showed invalid size of inflated data, expected {expected}, got {actual}")]
SizeMismatch { actual: u64, expected: u64, path: PathBuf },
#[error(transparent)]
Decode(#[from] gix_object::decode::LooseHeaderDecodeError),
#[error("Cannot store {size} in memory as it's not representable")]
OutOfMemory { size: u64 },
#[error("Could not {action} data at '{path}'")]
Io {
source: std::io::Error,
action: &'static str,
path: PathBuf,
},
}
impl Store {
const OPEN_ACTION: &'static str = "open";
pub fn contains(&self, id: &gix_hash::oid) -> bool {
debug_assert_eq!(self.object_hash, id.kind());
hash_path(id, self.path.clone()).is_file()
}
pub fn lookup_prefix(
&self,
prefix: gix_hash::Prefix,
mut candidates: Option<&mut HashSet<gix_hash::ObjectId>>,
) -> Result<Option<crate::store::prefix::lookup::Outcome>, crate::loose::iter::Error> {
let single_directory_iter = crate::loose::Iter {
inner: gix_features::fs::walkdir_new(
&self.path.join(prefix.as_oid().to_hex_with_len(2).to_string()),
gix_features::fs::walkdir::Parallelism::Serial,
false,
)
.min_depth(1)
.max_depth(1)
.follow_links(false)
.into_iter(),
hash_hex_len: prefix.as_oid().kind().len_in_hex(),
};
let mut candidate = None;
for oid in single_directory_iter {
let oid = match oid {
Ok(oid) => oid,
Err(err) => {
return match err.io_error() {
Some(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
None | Some(_) => Err(err),
}
}
};
if prefix.cmp_oid(&oid) == Ordering::Equal {
match &mut candidates {
Some(candidates) => {
candidates.insert(oid);
}
None => {
if candidate.is_some() {
return Ok(Some(Err(())));
}
candidate = Some(oid);
}
}
}
}
match &mut candidates {
Some(candidates) => match candidates.len() {
0 => Ok(None),
1 => Ok(candidates.iter().next().copied().map(Ok)),
_ => Ok(Some(Err(()))),
},
None => Ok(candidate.map(Ok)),
}
}
pub fn try_find<'a>(
&self,
id: &gix_hash::oid,
out: &'a mut Vec<u8>,
) -> Result<Option<gix_object::Data<'a>>, Error> {
debug_assert_eq!(self.object_hash, id.kind());
match self.find_inner(id, out) {
Ok(obj) => Ok(Some(obj)),
Err(err) => match err {
Error::Io {
source: err,
action,
path,
} => {
if action == Self::OPEN_ACTION && err.kind() == std::io::ErrorKind::NotFound {
Ok(None)
} else {
Err(Error::Io {
source: err,
action,
path,
})
}
}
err => Err(err),
},
}
}
pub fn try_header(&self, id: &gix_hash::oid) -> Result<Option<(u64, gix_object::Kind)>, Error> {
const BUF_SIZE: usize = 256;
let mut buf = [0_u8; BUF_SIZE];
let path = hash_path(id, self.path.clone());
let mut inflate = zlib::Inflate::default();
let mut istream = match fs::File::open(&path) {
Ok(f) => f,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => {
return Err(Error::Io {
source: err,
action: Self::OPEN_ACTION,
path,
})
}
};
let (compressed_buf, _) = buf.split_at_mut(BUF_SIZE - HEADER_MAX_SIZE);
let bytes_read = istream.read(compressed_buf).map_err(|e| Error::Io {
source: e,
action: "read",
path: path.to_owned(),
})?;
let (compressed_buf, header_buf) = buf.split_at_mut(bytes_read);
let (status, _consumed_in, consumed_out) =
inflate
.once(compressed_buf, header_buf)
.map_err(|e| Error::DecompressFile {
source: e,
path: path.to_owned(),
})?;
if status == zlib::Status::BufError {
return Err(Error::DecompressFile {
source: zlib::inflate::Error::Status(status),
path,
});
}
let (kind, size, _header_size) = gix_object::decode::loose_header(&header_buf[..consumed_out])?;
Ok(Some((size, kind)))
}
fn find_inner<'a>(&self, id: &gix_hash::oid, buf: &'a mut Vec<u8>) -> Result<gix_object::Data<'a>, Error> {
let path = hash_path(id, self.path.clone());
let mut inflate = zlib::Inflate::default();
let ((status, consumed_in, consumed_out), bytes_read) = {
let mut istream = fs::File::open(&path).map_err(|e| Error::Io {
source: e,
action: Self::OPEN_ACTION,
path: path.to_owned(),
})?;
buf.clear();
let bytes_read = istream.read_to_end(buf).map_err(|e| Error::Io {
source: e,
action: "read",
path: path.to_owned(),
})?;
buf.resize(bytes_read + HEADER_MAX_SIZE, 0);
let (input, output) = buf.split_at_mut(bytes_read);
(
inflate
.once(&input[..bytes_read], output)
.map_err(|e| Error::DecompressFile {
source: e,
path: path.to_owned(),
})?,
bytes_read,
)
};
if status == zlib::Status::BufError {
return Err(Error::DecompressFile {
source: zlib::inflate::Error::Status(status),
path,
});
}
let decompressed_start = bytes_read;
let (kind, size, header_size) =
gix_object::decode::loose_header(&buf[decompressed_start..decompressed_start + consumed_out])?;
if status == zlib::Status::StreamEnd {
let decompressed_body_bytes_sans_header =
decompressed_start + header_size..decompressed_start + consumed_out;
if consumed_out as u64 != size + header_size as u64 {
return Err(Error::SizeMismatch {
expected: size + header_size as u64,
actual: consumed_out as u64,
path,
});
}
buf.copy_within(decompressed_body_bytes_sans_header, 0);
} else {
let new_len = bytes_read as u64 + size + header_size as u64;
buf.resize(new_len.try_into().map_err(|_| Error::OutOfMemory { size: new_len })?, 0);
{
let (input, output) = buf.split_at_mut(bytes_read);
let num_decompressed_bytes = zlib::stream::inflate::read(
&mut &input[consumed_in..],
&mut inflate.state,
&mut output[consumed_out..],
)
.map_err(|e| Error::Io {
source: e,
action: "deflate",
path: path.to_owned(),
})?;
if num_decompressed_bytes as u64 + consumed_out as u64 != size + header_size as u64 {
return Err(Error::SizeMismatch {
expected: size + header_size as u64,
actual: num_decompressed_bytes as u64 + consumed_out as u64,
path,
});
}
};
buf.copy_within(decompressed_start + header_size.., 0);
}
buf.resize(
size.try_into()
.expect("BUG: here the size is already confirmed to fit into memory"),
0,
);
Ok(gix_object::Data { kind, data: buf })
}
}