pub mod merge;
pub mod meta;
pub mod multi_writer;
pub mod reader;
pub mod scanner;
pub mod writer;
use crate::{
Checksum, GlobalTableId, TreeId, blob_tree::FragmentationMap, file_accessor::FileAccessor,
vlog::BlobFileId,
};
pub use meta::Metadata;
use std::{
path::{Path, PathBuf},
sync::{Arc, atomic::AtomicBool},
};
#[derive(Debug)]
pub struct Inner {
pub id: BlobFileId,
pub tree_id: TreeId,
pub path: PathBuf,
pub meta: Metadata,
pub is_deleted: AtomicBool,
pub checksum: Checksum,
pub(crate) file_accessor: FileAccessor,
}
impl Inner {
fn global_id(&self) -> GlobalTableId {
GlobalTableId::from((self.tree_id, self.id))
}
}
impl Drop for Inner {
fn drop(&mut self) {
if self.is_deleted.load(std::sync::atomic::Ordering::Acquire) {
log::trace!(
"Cleanup deleted blob file {:?} at {}",
self.id,
self.path.display(),
);
if let Err(e) = std::fs::remove_file(&*self.path) {
log::warn!(
"Failed to cleanup deleted blob file {:?} at {}: {e:?}",
self.id,
self.path.display(),
);
}
self.file_accessor
.as_descriptor_table()
.inspect(|d| d.remove_for_blob_file(&self.global_id()));
}
}
}
#[derive(Clone)]
pub struct BlobFile(pub(crate) Arc<Inner>);
impl Eq for BlobFile {}
impl PartialEq for BlobFile {
fn eq(&self, other: &Self) -> bool {
self.id().eq(&other.id())
}
}
impl std::hash::Hash for BlobFile {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl BlobFile {
pub(crate) fn mark_as_deleted(&self) {
self.0
.is_deleted
.store(true, std::sync::atomic::Ordering::Release);
}
#[must_use]
pub fn id(&self) -> BlobFileId {
self.0.id
}
#[must_use]
pub fn checksum(&self) -> Checksum {
self.0.checksum
}
#[must_use]
pub fn path(&self) -> &Path {
&self.0.path
}
#[must_use]
pub(crate) fn file_accessor(&self) -> &FileAccessor {
&self.0.file_accessor
}
#[must_use]
#[expect(clippy::len_without_is_empty)]
pub fn len(&self) -> u64 {
self.0.meta.item_count
}
pub(crate) fn is_stale(&self, frag_map: &FragmentationMap, threshold: f32) -> bool {
frag_map.get(&self.id()).is_some_and(|x| {
#[expect(
clippy::cast_precision_loss,
reason = "ok to lose precision as this is an approximate calculation"
)]
let stale_bytes = x.bytes as f32;
#[expect(
clippy::cast_precision_loss,
reason = "ok to lose precision as this is an approximate calculation"
)]
let all_bytes = self.0.meta.total_uncompressed_bytes as f32;
let ratio = stale_bytes / all_bytes;
ratio >= threshold
})
}
pub(crate) fn is_dead(&self, frag_map: &FragmentationMap) -> bool {
frag_map.get(&self.id()).is_some_and(|x| {
let stale_bytes = x.bytes;
let all_bytes = self.0.meta.total_uncompressed_bytes;
stale_bytes == all_bytes
})
}
}