use std::path::Path;
use std::sync::{Mutex, MutexGuard, PoisonError};
use kernel::{AxError, RunId, Seq};
use crate::error::{StorageError, io_err};
use crate::jsonl::segment_names;
use crate::real_fs::RealFs;
use crate::vfs::Vfs;
use super::fold::{Folded, Located};
use super::reader::LineReader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refreshed {
Unchanged,
Appended { bytes_read: u64 },
Rebuilt,
}
pub struct LedgerIndex {
pub(crate) folded: Folded,
vfs: Mutex<Box<dyn Vfs>>,
}
impl LedgerIndex {
fn seam(&self) -> MutexGuard<'_, Box<dyn Vfs>> {
self.vfs.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn rebuild(dir: &Path) -> Result<LedgerIndex, StorageError> {
let vfs: Box<dyn Vfs> = Box::new(RealFs::new());
let folded = scan(vfs.as_ref(), dir)?;
Ok(LedgerIndex {
folded,
vfs: Mutex::new(vfs),
})
}
pub fn folding(
dir: &Path,
each: impl FnMut(&[u8]) -> Result<Option<Located>, AxError>,
) -> Result<LedgerIndex, AxError> {
let vfs: Box<dyn Vfs> = Box::new(RealFs::new());
let folded = walk(vfs.as_ref(), dir, each, StorageError::into_ax)?;
Ok(LedgerIndex {
folded,
vfs: Mutex::new(vfs),
})
}
#[must_use]
pub fn empty() -> LedgerIndex {
LedgerIndex {
folded: Folded::empty(),
vfs: Mutex::new(Box::new(RealFs::new())),
}
}
pub fn refresh(&mut self, dir: &Path) -> Result<Refreshed, StorageError> {
let plan = self.plan_refresh(dir)?;
match plan {
RefreshPlan::Rebuild => {
let folded = scan(self.seam().as_ref(), dir)?;
self.folded = folded;
Ok(Refreshed::Rebuilt)
}
RefreshPlan::Unchanged => Ok(Refreshed::Unchanged),
RefreshPlan::Appended { spans } => self.fold_spans(dir, spans),
}
}
fn plan_refresh(&self, dir: &Path) -> Result<RefreshPlan, StorageError> {
let vfs = self.seam();
let names = crate::jsonl::segment_names(vfs.as_ref(), dir)?;
let mut spans = Vec::new();
let mut held_here: usize = 0;
for name in &names {
let path = dir.join(name);
let size = vfs
.size(&path)
.map_err(crate::error::io_err("stat segment", &path))?;
let held = self.folded.scanned.get(name).copied();
if held.is_some() {
held_here = held_here.saturating_add(1);
}
match held {
Some(done) if done == size => continue,
Some(done) if done < size => spans.push(Span {
name: name.clone(),
from: done,
size,
}),
Some(_) => return Ok(RefreshPlan::Rebuild),
None => spans.push(Span {
name: name.clone(),
from: 0,
size,
}),
}
}
if held_here < self.folded.scanned.len() {
return Ok(RefreshPlan::Rebuild);
}
if spans.is_empty() {
return Ok(RefreshPlan::Unchanged);
}
Ok(RefreshPlan::Appended { spans })
}
fn fold_spans(&mut self, dir: &Path, spans: Vec<Span>) -> Result<Refreshed, StorageError> {
let mut bytes_read: u64 = 0;
for span in spans {
let path = dir.join(&span.name);
let tail = self
.seam()
.read_at(&path, span.from, span.size.saturating_sub(span.from))
.map_err(crate::error::io_err("read segment", &path))?;
bytes_read = bytes_read.saturating_add(u64::try_from(tail.len()).unwrap_or(u64::MAX));
let indexed = self.folded.fold_segment(&span.name, span.from, &tail);
self.folded
.scanned
.insert(span.name, span.from.saturating_add(indexed).min(span.size));
}
Ok(Refreshed::Appended { bytes_read })
}
pub fn reader(&self, dir: &Path) -> LineReader<'_> {
LineReader {
index: self,
dir: dir.to_path_buf(),
open: None,
}
}
pub fn run_seqs_before(&self, run: RunId, before: Option<Seq>) -> impl Iterator<Item = Seq> {
self.folded.run_seqs_before(run, before)
}
pub fn seqs(&self) -> impl DoubleEndedIterator<Item = Seq> + '_ {
self.folded.seqs()
}
pub fn seqs_from(&self, from: Seq) -> impl DoubleEndedIterator<Item = Seq> + '_ {
self.folded.seqs_from(from)
}
pub fn tail_seq(&self) -> Option<Seq> {
self.folded.tail_seq()
}
pub fn len(&self) -> usize {
self.folded.len()
}
pub fn is_empty(&self) -> bool {
self.folded.is_empty()
}
}
struct Span {
name: String,
from: u64,
size: u64,
}
enum RefreshPlan {
Unchanged,
Appended { spans: Vec<Span> },
Rebuild,
}
fn scan(vfs: &dyn Vfs, dir: &Path) -> Result<Folded, StorageError> {
walk(vfs, dir, |_| Ok(None), |failure| failure)
}
fn walk<E>(
vfs: &dyn Vfs,
dir: &Path,
mut each: impl FnMut(&[u8]) -> Result<Option<Located>, E>,
lift: impl Fn(StorageError) -> E,
) -> Result<Folded, E> {
let mut folded = Folded::empty();
for name in segment_names(vfs, dir).map_err(&lift)? {
let path = dir.join(&name);
let bytes = vfs
.read(&path)
.map_err(io_err("read segment", &path))
.map_err(&lift)?;
let mut offset: u64 = 0;
for line in bytes.split_inclusive(|b| *b == b'\n') {
let Some(body) = line.strip_suffix(b"\n") else {
break;
};
if !body.is_empty() {
match each(body)? {
Some(located) => folded.insert_located(&name, offset, located),
None => folded.insert_line(&name, offset, body),
}
}
let len = u64::try_from(line.len())
.map_err(std::io::Error::other)
.map_err(io_err("measure segment line", &path))
.map_err(&lift)?;
offset = offset.saturating_add(len);
}
folded.scanned.insert(name, offset);
}
Ok(folded)
}
impl serde::Serialize for LedgerIndex {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.folded.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for LedgerIndex {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Folded::deserialize(deserializer).map(|folded| LedgerIndex {
folded,
vfs: Mutex::new(Box::new(RealFs::new())),
})
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code"
)]
mod tests;