use std::path::{Path, PathBuf};
use kernel::{B3Hash, EventRecord, Seq};
use crate::error::{StorageError, io_err};
use crate::vfs::Vfs;
pub(crate) const SEGMENT_ROLL_BYTES: u64 = 64 * 1024 * 1024;
pub struct OpenReport {
pub recovered: Option<TailTruncation>,
pub counted: crate::ProofCount,
}
pub struct TailTruncation {
pub dropped_bytes: u64,
}
pub struct JsonlLedger {
pub(crate) vfs: Box<dyn Vfs>,
pub(crate) dir: PathBuf,
pub(crate) seg_path: PathBuf,
pub(crate) seg_len: u64,
pub(crate) next_seq: Seq,
pub(crate) prev: B3Hash,
pub(crate) roll_bytes: u64,
pub(crate) barrier: super::barrier::Barrier,
pub(crate) sessions: Option<crate::sessions::Sessions>,
pub(crate) observer: Option<WriteObserver>,
pub(crate) lock: Option<WriterLock>,
pub(crate) halt: crate::chain_audit::ChainHalt,
pub(crate) pending_unwind: Option<Vec<PathBuf>>,
}
pub(crate) struct WriterLock {
_held: std::fs::File,
}
impl WriterLock {
pub(crate) fn take(dir: &Path) -> Result<WriterLock, StorageError> {
let name = dir.file_name().ok_or_else(|| {
io_err("name the ledger lock", dir)(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"a ledger directory needs a name for its lock to sit beside it",
))
})?;
let mut lock_name = name.to_os_string();
lock_name.push(".lock");
let path = dir.with_file_name(lock_name);
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
std::fs::create_dir_all(parent)
.map_err(io_err("create the ledger's parent directory", parent))?;
}
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(&path)
.map_err(io_err("open the ledger lock", &path))?;
match file.try_lock() {
Ok(()) => Ok(WriterLock { _held: file }),
Err(std::fs::TryLockError::WouldBlock) => Err(StorageError::LedgerHeld {
dir: dir.to_path_buf(),
}),
Err(std::fs::TryLockError::Error(source)) => {
Err(io_err("lock the ledger", &path)(source))
}
}
}
}
pub type WriteObserver = Box<dyn FnMut(&EventRecord) + Send>;
pub(crate) struct TailBoundary {
pub(crate) prev: B3Hash,
pub(crate) next_seq: Seq,
pub(crate) prior: Option<PriorSegment>,
}
pub(crate) struct PriorSegment {
pub(crate) path: PathBuf,
pub(crate) len: u64,
}
const SEGMENT_PREFIX: &str = "ledger-";
const SEGMENT_SUFFIX: &str = ".jsonl";
pub(crate) fn segment_file_name(first_seq: Seq) -> String {
format!("{SEGMENT_PREFIX}{:020}{SEGMENT_SUFFIX}", first_seq.value())
}
pub(crate) fn is_segment(path: &Path) -> bool {
match path.file_name().and_then(|n| n.to_str()) {
Some(name) => name.starts_with(SEGMENT_PREFIX) && name.ends_with(SEGMENT_SUFFIX),
None => false,
}
}
pub(crate) fn segment_first_seq(name: &str) -> Option<Seq> {
let digits = name
.strip_prefix(SEGMENT_PREFIX)?
.strip_suffix(SEGMENT_SUFFIX)?;
digits.parse::<u64>().ok().map(Seq::new)
}
pub(crate) fn segment_names(vfs: &dyn Vfs, dir: &Path) -> Result<Vec<String>, StorageError> {
let mut names = Vec::new();
for path in vfs.list(dir).map_err(io_err("list ledger dir", dir))? {
if !is_segment(&path) {
continue;
}
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
names.push(name.to_owned());
}
}
Ok(names)
}
pub(crate) fn complete_lines(bytes: &[u8]) -> (Vec<&[u8]>, usize) {
let mut lines = Vec::new();
let mut consumed = 0usize;
let mut start = 0usize;
for (index, byte) in bytes.iter().enumerate() {
if *byte == b'\n' {
if let Some(line) = bytes.get(start..index) {
lines.push(line);
}
start = index.saturating_add(1);
consumed = start;
}
}
(lines, consumed)
}