use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
pub type Decision = bool;
pub fn h1_ttl(journal_committed_at_unix: u64) -> Decision {
let ttl_secs: u64 = crate::constants::WAL_KEEP_SECS_DEFAULT;
if ttl_secs == 0 {
return false;
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let age = now.saturating_sub(journal_committed_at_unix);
age < ttl_secs
}
pub fn h2_lru_within_cap(workspace_committed_count: u64, age_rank: u64) -> Decision {
let max_count: u64 = crate::constants::WAL_MAX_COUNT_DEFAULT;
age_rank < max_count || workspace_committed_count <= max_count
}
pub fn h3_rate_limit() -> Decision {
static GLOBAL: RateWindow = RateWindow::new();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
GLOBAL.hit(now, crate::constants::WAL_RATE_LIMIT_DEFAULT)
}
#[derive(Debug)]
pub struct RateWindow {
start: AtomicU64,
count: AtomicU64,
}
impl RateWindow {
pub const fn new() -> Self {
Self {
start: AtomicU64::new(0),
count: AtomicU64::new(0),
}
}
pub fn hit(&self, now_secs: u64, max_per_window: u64) -> bool {
if max_per_window == 0 {
return false;
}
let start = self.start.load(Ordering::Relaxed);
if now_secs.saturating_sub(start) >= crate::constants::WAL_RATE_WINDOW_SECS {
match self
.start
.compare_exchange(start, now_secs, Ordering::Relaxed, Ordering::Relaxed)
{
Ok(_) => {
self.count.store(1, Ordering::Relaxed);
return false;
}
Err(_) => {
}
}
}
self.count.fetch_add(1, Ordering::Relaxed) + 1 > max_per_window
}
}
impl Default for RateWindow {
fn default() -> Self {
Self::new()
}
}
pub fn h4_sentinel(target: &Path) -> Decision {
let dir = target.parent().unwrap_or_else(|| Path::new("."));
dir.join(crate::constants::WAL_KEEP_SENTINEL).exists()
|| dir.join(crate::constants::WAL_LEGACY_SENTINEL).exists()
}
pub fn h5_archive(journal_committed_at_unix: u64) -> Decision {
let archive_days: u64 = crate::constants::WAL_ARCHIVE_DAYS_DEFAULT;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let age_days = now.saturating_sub(journal_committed_at_unix) / crate::constants::SECS_PER_DAY;
age_days >= archive_days
}
pub fn heuristics_should_preserve(
target: &Path,
journal_committed_at_unix: u64,
workspace_committed_count: u64,
age_rank: u64,
) -> bool {
h1_ttl(journal_committed_at_unix)
|| h2_lru_within_cap(workspace_committed_count, age_rank)
|| h4_sentinel(target)
|| h5_archive(journal_committed_at_unix)
}