use crate::audit_log::AuditEntry;
use std::fs;
use std::io::{self, Read, Write};
use std::path::PathBuf;
#[derive(Debug, Default)]
pub struct ExportFilter {
pub key: Option<String>,
pub since_ms: Option<u64>,
pub event_prefix: Option<String>,
pub namespace: Option<String>,
}
pub fn export_chains(data_dir: &str, filter: &ExportFilter) -> io::Result<(u64, usize)> {
let dir = PathBuf::from(data_dir).join("_audit");
let mut written = 0u64;
let mut chains = 0usize;
let stdout = io::stdout();
let mut out = stdout.lock();
let entries = match fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => return Ok((0, 0)),
};
let mut paths: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().map(|x| x == "log").unwrap_or(false))
.collect();
paths.sort();
for path in paths {
let stem = match path.file_stem().and_then(|s| s.to_str()) {
Some(s) => s,
None => continue,
};
if let Some(ref k) = filter.key {
if stem != k {
continue;
}
}
chains += 1;
let mut file = fs::File::open(&path)?;
loop {
let mut len_buf = [0u8; 4];
match file.read_exact(&mut len_buf) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e),
}
let len = u32::from_le_bytes(len_buf) as usize;
let mut payload = vec![0u8; len];
if file.read_exact(&mut payload).is_err() {
break;
}
let entry: AuditEntry = match serde_json::from_slice(&payload) {
Ok(e) => e,
Err(_) => continue,
};
if let Some(min_ts) = filter.since_ms {
if entry.ts_ms < min_ts {
continue;
}
}
if let Some(ref p) = filter.event_prefix {
if !entry.event_type.starts_with(p) {
continue;
}
}
if let Some(ref ns) = filter.namespace {
if &entry.ns != ns {
continue;
}
}
if let Ok(line) = serde_json::to_string(&entry) {
writeln!(out, "{}", line)?;
written += 1;
}
}
}
Ok((written, chains))
}
pub fn parse_duration_ms(s: &str) -> Result<u64, String> {
let s = s.trim();
if s.is_empty() {
return Err("empty duration".into());
}
let (num_str, suffix) = s.split_at(s.len() - 1);
let multiplier = match suffix {
"s" => 1_000u64,
"m" => 60 * 1_000,
"h" => 60 * 60 * 1_000,
"d" => 24 * 60 * 60 * 1_000,
_ => {
return Err(format!(
"unknown duration suffix '{}' — use s | m | h | d",
suffix
))
}
};
let num: u64 = num_str
.parse()
.map_err(|_| format!("invalid duration number '{}'", num_str))?;
Ok(num.saturating_mul(multiplier))
}