use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::compress;
use crate::error::{Error, ObjectKindHint, Result};
use crate::hash::hash_object;
use crate::object::{ObjectType, parse_loose, serialize_loose};
use crate::oid::{HashAlgo, ObjectId};
use crate::vfs::Vfs;
use super::ObjectDatabase;
#[derive(Debug, Clone)]
pub struct LooseOdb<V: Vfs> {
vfs: V,
algo: HashAlgo,
max_object_size: usize,
}
impl<V: Vfs> LooseOdb<V> {
pub fn new(vfs: V, algo: HashAlgo) -> Self {
LooseOdb {
vfs,
algo,
max_object_size: 1 << 30,
}
}
pub fn with_max_object_size(mut self, max: usize) -> Self {
self.max_object_size = max;
self
}
pub fn vfs(&self) -> &V {
&self.vfs
}
fn object_path(&self, id: &ObjectId) -> String {
let hex = id.to_hex();
format!("{}/{}", &hex[..2], &hex[2..])
}
}
impl<V: Vfs> ObjectDatabase for LooseOdb<V> {
fn algo(&self) -> HashAlgo {
self.algo
}
fn contains(&self, id: &ObjectId) -> bool {
self.vfs.exists(&self.object_path(id))
}
fn read(&self, id: &ObjectId) -> Result<(ObjectType, Vec<u8>)> {
let path = self.object_path(id);
if !self.vfs.exists(&path) {
return Err(Error::NotFound(ObjectKindHint::Object(id.to_hex())));
}
let compressed = self.vfs.read(&path)?;
let raw = compress::inflate_capped(&compressed, self.max_object_size)?;
let (ty, payload) = parse_loose(&raw)?;
let computed = hash_object(self.algo, ty, payload);
if &computed != id {
return Err(Error::InvalidOid(format!(
"loose object {id} hashes to {computed}"
)));
}
Ok((ty, payload.to_vec()))
}
fn write(&self, ty: ObjectType, payload: &[u8]) -> Result<ObjectId> {
let id = hash_object(self.algo, ty, payload);
let path = self.object_path(&id);
if self.vfs.exists(&path) {
return Ok(id);
}
let raw = serialize_loose(ty, payload);
let compressed = compress::deflate(&raw)?;
self.vfs.write(&path, &compressed)?;
Ok(id)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::vfs::StdFs;
#[test]
fn write_read_roundtrip_on_disk() {
let dir = std::env::temp_dir().join(format!("puregit-odb-{}", core::line!()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let odb = LooseOdb::new(StdFs::new(&dir), HashAlgo::Sha1);
let id = odb.write(ObjectType::Blob, b"hello\n").unwrap();
assert_eq!(id.to_hex(), "ce013625030ba8dba906f756967f9e9ca394464a");
assert!(odb.contains(&id));
let (ty, payload) = odb.read(&id).unwrap();
assert_eq!(ty, ObjectType::Blob);
assert_eq!(payload, b"hello\n");
let _ = std::fs::remove_dir_all(&dir);
}
}