use std::{
collections::BTreeSet,
sync::{Arc, Mutex},
};
use tracing::warn;
use super::options::PathOptions;
use crate::Hash;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum BaoFilePart {
Outboard,
Data,
Sizes,
Bitfield,
}
pub(super) fn pair(options: Arc<PathOptions>) -> (ProtectHandle, DeleteHandle) {
let ds = Arc::new(Mutex::new(DeleteSet::default()));
(ProtectHandle(ds.clone()), DeleteHandle::new(ds, options))
}
#[derive(Debug, Default)]
struct DeleteSet(BTreeSet<(Hash, BaoFilePart)>);
impl DeleteSet {
fn delete(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
for part in parts {
self.0.insert((hash, part));
}
}
fn protect(&mut self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
for part in parts {
self.0.remove(&(hash, part));
}
}
fn commit(&mut self, options: &PathOptions) {
for (hash, to_delete) in &self.0 {
tracing::debug!("deleting {:?} for {hash}", to_delete);
let path = match to_delete {
BaoFilePart::Data => options.data_path(hash),
BaoFilePart::Outboard => options.outboard_path(hash),
BaoFilePart::Sizes => options.sizes_path(hash),
BaoFilePart::Bitfield => options.bitfield_path(hash),
};
if let Err(cause) = std::fs::remove_file(&path) {
if cause.kind() != std::io::ErrorKind::NotFound {
warn!(
"failed to delete {:?} {}: {}",
to_delete,
path.display(),
cause
);
}
}
}
self.0.clear();
}
fn clear(&mut self) {
self.0.clear();
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Debug, Clone)]
pub(super) struct ProtectHandle(Arc<Mutex<DeleteSet>>);
impl ProtectHandle {
pub(super) fn protect(&self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
let mut guard = self.0.lock().unwrap();
guard.protect(hash, parts);
}
}
#[derive(Debug)]
pub(super) struct DeleteHandle {
ds: Arc<Mutex<DeleteSet>>,
options: Arc<PathOptions>,
}
impl DeleteHandle {
fn new(ds: Arc<Mutex<DeleteSet>>, options: Arc<PathOptions>) -> Self {
Self { ds, options }
}
pub(super) fn begin_write(&mut self) -> FileTransaction<'_> {
FileTransaction::new(self)
}
}
#[derive(Debug)]
pub(super) struct FileTransaction<'a>(&'a DeleteHandle);
impl<'a> FileTransaction<'a> {
fn new(inner: &'a DeleteHandle) -> Self {
let guard = inner.ds.lock().unwrap();
debug_assert!(guard.is_empty());
drop(guard);
Self(inner)
}
pub fn delete(&self, hash: Hash, parts: impl IntoIterator<Item = BaoFilePart>) {
let mut guard = self.0.ds.lock().unwrap();
guard.delete(hash, parts);
}
pub fn commit(self) {
let mut guard = self.0.ds.lock().unwrap();
guard.commit(&self.0.options);
}
}
impl Drop for FileTransaction<'_> {
fn drop(&mut self) {
self.0.ds.lock().unwrap().clear();
}
}