use std::collections::HashSet;
use std::io::Cursor;
use std::ops::ControlFlow;
use std::sync::{Arc, RwLock};
use crate::diagnostics::NullDiagnostics;
use crate::error::{Error, Result};
use crate::midx::{prepared_midx_chain_strict, PreparedMidxChain};
use crate::objects::{HashAlgo, Object, ObjectId, ObjectInfo};
use crate::pack::clear_pack_cache;
use crate::pack_store::PackStore;
use super::{normalize_oid_prefix, ObjectStore, ObjectStream, PackedObjects};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MidxObjectsStatus {
Disabled,
Active,
Unusable,
}
pub struct MidxObjects {
packs: PackedObjects,
multi_pack_index: bool,
status: RwLock<MidxObjectsStatus>,
}
impl std::fmt::Debug for MidxObjects {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MidxObjects")
.field("objects_dir", &self.packs.pack_store().objects_dir())
.field("multi_pack_index", &self.multi_pack_index)
.field("status", &self.status())
.finish()
}
}
impl MidxObjects {
pub fn new(store: Arc<PackStore>, multi_pack_index: bool) -> Result<Self> {
let packs = PackedObjects::new(Arc::clone(&store));
let status = if multi_pack_index {
classify_midx_status(store.objects_dir())?
} else {
MidxObjectsStatus::Disabled
};
Ok(Self {
packs,
multi_pack_index,
status: RwLock::new(status),
})
}
#[must_use]
pub fn status(&self) -> MidxObjectsStatus {
self.status
.read()
.map(|g| *g)
.unwrap_or(MidxObjectsStatus::Unusable)
}
#[must_use]
pub fn multi_pack_index_enabled(&self) -> bool {
self.multi_pack_index
}
pub fn covered_pack_basenames(&self) -> Result<HashSet<String>> {
let Some(chain) = self.active_chain()? else {
return Ok(HashSet::new());
};
Ok(chain.covered_pack_basenames())
}
#[must_use]
pub fn packed_objects(&self) -> &PackedObjects {
&self.packs
}
fn active_chain(&self) -> Result<Option<Arc<PreparedMidxChain>>> {
if self.status() != MidxObjectsStatus::Active {
return Ok(None);
}
match prepared_midx_chain_strict(self.packs.pack_store().objects_dir()) {
Ok(chain) => Ok(chain),
Err(Error::Midx(_)) => Ok(None),
Err(err) => Err(err),
}
}
fn read_via_chain(&self, oid: &ObjectId) -> Result<Option<Object>> {
let Some(chain) = self.active_chain()? else {
return Ok(None);
};
chain.try_read_object(oid, &NullDiagnostics)
}
fn read_info_via_chain(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
let Some(chain) = self.active_chain()? else {
return Ok(None);
};
chain.try_read_info(oid, &NullDiagnostics)
}
}
impl ObjectStore for MidxObjects {
fn hash_algo(&self) -> HashAlgo {
self.packs.hash_algo()
}
fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
self.read_via_chain(oid)
}
fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
self.read_info_via_chain(oid)
}
fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
let Some(info) = self.read_info(oid)? else {
return Ok(None);
};
let Some(obj) = self.read(oid)? else {
return Ok(None);
};
let size = u64::try_from(obj.data.len()).map_err(|_| {
Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
})?;
Ok(Some(ObjectStream {
kind: info.kind,
size,
reader: Box::new(Cursor::new(obj.data)),
}))
}
fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
let Some(chain) = self.active_chain()? else {
return Ok(());
};
chain.for_each_listed_object(f)
}
fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
if prefix.len() > self.hash_algo().hex_len() {
return Ok(());
}
let Some(chain) = self.active_chain()? else {
return Ok(());
};
let prefix = normalize_oid_prefix(prefix, self.hash_algo())?;
chain.lookup_prefix(prefix.as_str(), limit, out)
}
fn refresh(&self) -> Result<bool> {
if !self.multi_pack_index {
return Ok(false);
}
let objects_dir = self.packs.pack_store().objects_dir();
clear_pack_cache();
crate::midx::evict_midx_read_cache_for_pack_dir(&objects_dir.join("pack"));
let new_status = classify_midx_status(objects_dir)?;
let mut guard = self
.status
.write()
.map_err(|e| Error::Io(std::io::Error::other(e.to_string())))?;
let changed = *guard != new_status;
*guard = new_status;
Ok(changed)
}
}
fn classify_midx_status(objects_dir: &std::path::Path) -> Result<MidxObjectsStatus> {
match prepared_midx_chain_strict(objects_dir) {
Ok(Some(_)) => Ok(MidxObjectsStatus::Active),
Ok(None) | Err(Error::Midx(_)) | Err(Error::CorruptObject(_)) => {
Ok(MidxObjectsStatus::Unusable)
}
Err(err) => Err(err),
}
}