use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use kevy_resp::Argv;
pub fn replay_aof<F: FnMut(Argv)>(path: &Path, mut apply: F) -> io::Result<()> {
let mut data = Vec::new();
match File::open(path) {
Ok(mut f) => {
f.read_to_end(&mut data)?;
}
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(e) => return Err(e),
}
let total = data.len();
if total == 0 {
return Ok(());
}
let mut pos = if data.len() >= crate::aof::AOF_MAGIC.len()
&& &data[..crate::aof::AOF_MAGIC.len()] == crate::aof::AOF_MAGIC
{
crate::aof::AOF_MAGIC.len()
} else {
0
};
let mut replayed: u64 = 0;
let stop = loop {
if pos >= total {
break ReplayStop::Clean;
}
match kevy_resp::parse_command(&data[pos..]) {
Ok(Some((args, consumed))) => {
apply(args);
pos += consumed;
replayed += 1;
}
Ok(None) => break ReplayStop::TruncatedTail,
Err(e) => break ReplayStop::CorruptFrame(format!("{e:?}")),
}
};
log_replay_summary(path, total, pos, replayed, &data[pos.min(total)..], stop);
Ok(())
}
enum ReplayStop {
Clean,
TruncatedTail,
CorruptFrame(String),
}
fn log_replay_summary(
path: &Path,
total: usize,
pos: usize,
replayed: u64,
remainder: &[u8],
stop: ReplayStop,
) {
let display = path.display();
let dropped = total - pos;
match stop {
ReplayStop::Clean => {
eprintln!(
"kevy: AOF {display} replayed {replayed} commands from {total} bytes (clean)"
);
}
ReplayStop::TruncatedTail => {
eprintln!(
"kevy: AOF {display} replayed {replayed} commands; trailing {dropped} bytes \
were a partial frame (crash mid-append, recoverable)"
);
}
ReplayStop::CorruptFrame(err) => {
let preview = preview_bytes(remainder);
eprintln!(
"kevy WARN: AOF {display} replayed {replayed} commands then hit a corrupt \
frame at byte {pos}; dropping the trailing {dropped} bytes. \
Preview: {preview}. Parser error: {err}. \
Common cause: non-kevy bytes got written into this file path \
(e.g. deploy pipeline redirecting stderr to the AOF)."
);
}
}
}
fn preview_bytes(b: &[u8]) -> String {
let n = b.len().min(16);
let mut hex = String::with_capacity(n * 3);
let mut ascii = String::with_capacity(n);
for &x in &b[..n] {
if !hex.is_empty() {
hex.push(' ');
}
use std::fmt::Write;
let _ = write!(hex, "{:02x}", x);
ascii.push(if (0x20..0x7f).contains(&x) { x as char } else { '.' });
}
format!("hex=[{hex}] ascii=[{ascii}]")
}