#![forbid(unsafe_code)]
mod cache;
mod engine;
mod index;
mod items;
mod record;
mod snapshot;
mod tasks;
#[cfg(test)]
mod tests;
pub mod warc;
use std::fmt;
use std::io;
use std::path::PathBuf;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub use cache::{Cache, CacheEntry, CacheLookup, CachePolicy};
pub use engine::Store;
pub use items::Items;
pub use snapshot::{ContentHash, SnapshotMeta, SnapshotPut, Snapshots};
pub use tasks::Tasks;
pub use warc::{
AssetSniffer, RecordType, SubresourceAsset, WarcDigests, WarcRecord, WarcWriter,
WebpageSnapshot,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncPolicy {
Always,
Interval(Duration),
Never,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Config {
pub dir: PathBuf,
pub sync: SyncPolicy,
pub max_segment_bytes: u64,
pub max_bytes: Option<u64>,
pub low_watermark: f64,
pub compact_ratio: f64,
pub compact_min_bytes: u64,
pub background: bool,
pub snapshot_max_bytes: usize,
pub snapshot_zstd_level: i32,
pub(crate) hash_mask: u64,
}
impl Config {
pub fn new(dir: impl Into<PathBuf>) -> Self {
Self {
dir: dir.into(),
sync: SyncPolicy::Interval(Duration::from_secs(1)),
max_segment_bytes: 64 << 20,
max_bytes: None,
low_watermark: 0.9,
compact_ratio: 0.5,
compact_min_bytes: 16 << 20,
background: true,
snapshot_max_bytes: 512 << 10,
snapshot_zstd_level: 3,
hash_mask: u64::MAX,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Io(io::Error),
Corrupt(String),
KeyTooLarge(usize),
ValueTooLarge {
size: usize,
limit: usize,
},
}
impl Error {
pub fn is_locked(&self) -> bool {
matches!(self, Error::Io(e) if e.kind() == io::ErrorKind::WouldBlock)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "store io error: {e}"),
Error::Corrupt(m) => write!(f, "store corrupt: {m}"),
Error::KeyTooLarge(n) => write!(f, "key too large: {n} bytes"),
Error::ValueTooLarge { size, limit } => {
write!(f, "value too large: {size} bytes (limit {limit})")
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct OpenReport {
pub segments: usize,
pub records: u64,
pub truncated_bytes: u64,
pub elapsed: Duration,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Stats {
pub entries: u64,
pub index_bytes: u64,
pub disk_bytes: u64,
pub live_bytes: u64,
pub segments: usize,
pub open: OpenReport,
pub last_error: Option<String>,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct GcReport {
pub evicted_entries: u64,
pub evicted_bytes: u64,
pub compacted_segments: usize,
pub disk_bytes_before: u64,
pub disk_bytes_after: u64,
}
pub(crate) fn to_ms(t: SystemTime) -> u64 {
t.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as u64)
}
pub(crate) fn from_ms(ms: u64) -> SystemTime {
UNIX_EPOCH + Duration::from_millis(ms)
}
pub(crate) fn now_secs() -> u32 {
(to_ms(SystemTime::now()) / 1000) as u32
}