use crate::snapshot_fmt::{
MAGIC, OP_EOF, OP_HASH, OP_HFTTL, OP_LIST, OP_SET, OP_STR, OP_STREAM, OP_ZSET,
VERSION, VERSION_ABSOLUTE_TTL, VERSION_FEED_CURSOR, VERSION_HASH_TTL, VERSION_RELATIVE_TTL,
read_bytes, read_ttl, read_u8, read_u32, read_u64,
};
use kevy_store::Store;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::Path;
pub fn read_snapshot_cursor(path: &Path) -> io::Result<Option<(u64, u64)>> {
let mut r = BufReader::new(File::open(path)?);
let mut magic = [0u8; 8];
r.read_exact(&mut magic)?;
if &magic != MAGIC {
return Err(io::Error::new(io::ErrorKind::InvalidData, "kevy snapshot: bad magic"));
}
let version = read_u8(&mut r)?;
if version < VERSION_FEED_CURSOR {
return Ok(None);
}
let mut gen_bytes = [0u8; 8];
let mut off_bytes = [0u8; 8];
r.read_exact(&mut gen_bytes)?;
r.read_exact(&mut off_bytes)?;
let generation = u64::from_le_bytes(gen_bytes);
let offset = u64::from_le_bytes(off_bytes);
Ok(Some((generation, offset)))
}
pub fn load_snapshot(store: &mut Store, path: &Path) -> io::Result<()> {
let r = BufReader::new(File::open(path)?);
load_snapshot_from(store, r)
}
pub fn load_snapshot_from<R: Read>(store: &mut Store, r: R) -> io::Result<()> {
load_snapshot_filtered(store, r, |_| true)
}
pub fn load_snapshot_filtered<R: Read>(
store: &mut Store,
mut r: R,
keep: impl Fn(&[u8]) -> bool,
) -> io::Result<()> {
let version = read_snapshot_header(&mut r)?;
let absolute_ttl = version >= VERSION_ABSOLUTE_TTL;
let now = kevy_store::now_unix_ms();
loop {
let op = read_u8(&mut r)?;
if op == OP_EOF {
return Ok(());
}
if op == OP_HFTTL {
load_hfttl_record(store, &mut r, &keep)?;
continue;
}
let raw_ttl = read_ttl(&mut r)?;
let ttl = if absolute_ttl {
raw_ttl.map(|deadline| deadline.saturating_sub(now))
} else {
raw_ttl
};
let key = read_bytes(&mut r)?;
load_record(store, &mut r, op, version, key, ttl, &keep)?;
}
}
fn read_snapshot_header<R: Read>(r: &mut R) -> io::Result<u8> {
let mut magic = [0u8; 8];
r.read_exact(&mut magic)?;
if &magic != MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"kevy snapshot: bad magic",
));
}
let version = read_u8(r)?;
if !(VERSION_RELATIVE_TTL..=VERSION_HASH_TTL).contains(&version) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"kevy snapshot: bad version",
));
}
if version >= VERSION_FEED_CURSOR {
let mut cur = [0u8; 16];
r.read_exact(&mut cur)?;
}
Ok(version)
}
fn load_hfttl_record<R: Read>(
store: &mut Store,
r: &mut R,
keep: &impl Fn(&[u8]) -> bool,
) -> io::Result<()> {
let key = read_bytes(r)?;
let field = read_bytes(r)?;
let mut d = [0u8; 8];
r.read_exact(&mut d)?;
if keep(&key) {
store.load_hash_field_ttl(&key, &field, u64::from_le_bytes(d));
}
Ok(())
}
fn load_record<R: Read>(
store: &mut Store,
r: &mut R,
op: u8,
version: u8,
key: Vec<u8>,
ttl: Option<u64>,
keep: &impl Fn(&[u8]) -> bool,
) -> io::Result<()> {
match op {
OP_STR => {
let val = read_bytes(r)?;
if keep(&key) {
store.load_str(key, val, ttl);
}
}
OP_HASH => {
let fields = read_pair_vec(r)?;
if keep(&key) {
store.load_hash(key, fields, ttl);
}
}
OP_LIST => {
let items = read_bulk_vec(r)?;
if keep(&key) {
store.load_list(key, items, ttl);
}
}
OP_SET => {
let members = read_bulk_vec(r)?;
if keep(&key) {
store.load_set(key, members, ttl);
}
}
OP_ZSET => {
let pairs = read_zset_pairs(r)?;
if keep(&key) {
store.load_zset(key, pairs, ttl);
}
}
OP_STREAM => load_stream_record(store, r, version, key, ttl, keep)?,
other => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("kevy snapshot: unknown opcode {other}"),
));
}
}
Ok(())
}
fn load_stream_record<R: Read>(
store: &mut Store,
r: &mut R,
version: u8,
key: Vec<u8>,
ttl: Option<u64>,
keep: &impl Fn(&[u8]) -> bool,
) -> io::Result<()> {
let last_ms = read_u64(r)?;
let last_seq = read_u64(r)?;
let mxd_ms = read_u64(r)?;
let mxd_seq = read_u64(r)?;
let entries_added = read_u64(r)?;
let n = read_u32(r)? as usize;
let mut entries = Vec::with_capacity(n);
for _ in 0..n {
let ms = read_u64(r)?;
let seq = read_u64(r)?;
let fv = read_pair_vec(r)?;
entries.push((ms, seq, fv));
}
let groups = if version >= VERSION {
read_stream_groups(r)?
} else {
Vec::new()
};
if keep(&key) {
store.load_stream(
key,
entries,
(last_ms, last_seq),
(mxd_ms, mxd_seq),
entries_added,
groups,
ttl,
);
}
Ok(())
}
fn read_bulk_vec<R: Read>(r: &mut R) -> io::Result<Vec<Vec<u8>>> {
let n = read_u32(r)? as usize;
let mut items = Vec::with_capacity(n);
for _ in 0..n {
items.push(read_bytes(r)?);
}
Ok(items)
}
fn read_pair_vec<R: Read>(r: &mut R) -> io::Result<Vec<(Vec<u8>, Vec<u8>)>> {
let n = read_u32(r)? as usize;
let mut pairs = Vec::with_capacity(n);
for _ in 0..n {
let f = read_bytes(r)?;
let v = read_bytes(r)?;
pairs.push((f, v));
}
Ok(pairs)
}
fn read_zset_pairs<R: Read>(r: &mut R) -> io::Result<Vec<(Vec<u8>, f64)>> {
let n = read_u32(r)? as usize;
let mut pairs = Vec::with_capacity(n);
for _ in 0..n {
let m = read_bytes(r)?;
let score = f64::from_bits(read_u64(r)?);
pairs.push((m, score));
}
Ok(pairs)
}
fn read_stream_groups<R: Read>(r: &mut R) -> io::Result<Vec<kevy_store::LoadedGroup>> {
let n = read_u32(r)? as usize;
let mut groups = Vec::with_capacity(n);
for _ in 0..n {
let name = read_bytes(r)?;
let last_delivered = (read_u64(r)?, read_u64(r)?);
let nc = read_u32(r)? as usize;
let mut consumers = Vec::with_capacity(nc);
for _ in 0..nc {
let cname = read_bytes(r)?;
consumers.push((cname, read_u64(r)?));
}
let np = read_u32(r)? as usize;
let mut pel = Vec::with_capacity(np);
for _ in 0..np {
let ms = read_u64(r)?;
let seq = read_u64(r)?;
let consumer = read_bytes(r)?;
let delivery_time_ms = read_u64(r)?;
let delivery_count = read_u32(r)?;
pel.push((ms, seq, consumer, delivery_time_ms, delivery_count));
}
groups.push(kevy_store::LoadedGroup { name, last_delivered, consumers, pel });
}
Ok(groups)
}