use crate::rewrite_fmt::emit;
use kevy_resp::Argv;
use kevy_store::{StreamData, StreamId};
use std::io::{self, Write};
pub fn write_stream_as_commands<W: Write>(
mut w: W,
key: &[u8],
s: &StreamData,
) -> io::Result<usize> {
stream_as_commands(&mut w, key, s, crate::AofFormat::V1, &mut Vec::new())
}
pub(crate) fn stream_as_commands<W: Write>(
w: &mut W,
key: &[u8],
s: &StreamData,
fmt: crate::AofFormat,
scratch: &mut Vec<u8>,
) -> io::Result<usize> {
let mut frames = 0usize;
for (id, fv) in s.entries() {
let mut argv: Vec<Vec<u8>> = Vec::with_capacity(3 + fv.len() * 2);
argv.push(b"XADD".to_vec());
argv.push(key.to_vec());
argv.push(id.encode());
for (f, v) in fv {
argv.push(f.to_vec());
argv.push(v.to_vec());
}
emit(w, &Argv::from(argv), fmt, scratch)?;
frames += 1;
}
frames += write_stream_id_fixup(w, key, s, fmt, scratch)?;
frames += write_stream_group_commands(w, key, s, fmt, scratch)?;
Ok(frames)
}
pub(crate) fn write_stream_id_fixup<W: Write>(
w: &mut W,
key: &[u8],
s: &StreamData,
fmt: crate::AofFormat,
scratch: &mut Vec<u8>,
) -> io::Result<usize> {
let mut frames = 0usize;
let (len, last, mxd, added) = (s.length(), s.last_id(), s.max_deleted_id(), s.entries_added());
if len == 0 && last != StreamId::MIN {
let argv = vec![
b"XADD".to_vec(),
key.to_vec(),
b"MAXLEN".to_vec(),
b"0".to_vec(),
last.encode(),
b"x".to_vec(),
b"x".to_vec(),
];
emit(w, &Argv::from(argv), fmt, scratch)?;
frames += 1;
}
let natural = if len > 0 {
(s.last_entry().map_or(StreamId::MIN, |(id, _)| id), len, StreamId::MIN)
} else {
(last, u64::from(last != StreamId::MIN), StreamId::MIN)
};
if natural != (last, added, mxd) {
let argv = vec![
b"XSETID".to_vec(),
key.to_vec(),
last.encode(),
b"ENTRIESADDED".to_vec(),
added.to_string().into_bytes(),
b"MAXDELETEDID".to_vec(),
mxd.encode(),
];
emit(w, &Argv::from(argv), fmt, scratch)?;
frames += 1;
}
Ok(frames)
}
fn xclaim_argv(
key: &[u8],
group: &[u8],
consumer: &[u8],
id: StreamId,
delivery_time_ms: impl core::fmt::Display,
delivery_count: impl core::fmt::Display,
) -> Vec<Vec<u8>> {
vec![
b"XCLAIM".to_vec(),
key.to_vec(),
group.to_vec(),
consumer.to_vec(),
b"0".to_vec(),
id.encode(),
b"TIME".to_vec(),
delivery_time_ms.to_string().into_bytes(),
b"RETRYCOUNT".to_vec(),
delivery_count.to_string().into_bytes(),
b"FORCE".to_vec(),
b"JUSTID".to_vec(),
]
}
pub(crate) fn write_stream_group_commands<W: Write>(
w: &mut W,
key: &[u8],
s: &StreamData,
fmt: crate::AofFormat,
scratch: &mut Vec<u8>,
) -> io::Result<usize> {
let mut frames = 0usize;
for g in s.export_groups() {
frames += write_group_head(w, key, &g, fmt, scratch)?;
for (ms, seq, consumer, delivery_time_ms, delivery_count) in &g.pel {
let id = StreamId::new(*ms, *seq);
let argv = if s.contains_entry(id) {
xclaim_argv(key, &g.name, consumer, id, *delivery_time_ms, *delivery_count)
} else {
pending_argv(key, &g.name, consumer, id, *delivery_time_ms, *delivery_count)
};
emit(w, &Argv::from(argv), fmt, scratch)?;
frames += 1;
}
frames += write_consumer_times(w, key, &g, fmt, scratch)?;
}
Ok(frames)
}
fn write_group_head<W: Write>(
w: &mut W,
key: &[u8],
g: &kevy_store::LoadedGroup,
fmt: crate::AofFormat,
scratch: &mut Vec<u8>,
) -> io::Result<usize> {
let last_delivered = StreamId::new(g.last_delivered.0, g.last_delivered.1).encode();
let named = |sub: &[u8], tail: &[&[u8]]| {
let mut argv = vec![b"XGROUP".to_vec(), sub.to_vec(), key.to_vec(), g.name.clone()];
argv.extend(tail.iter().map(|t| t.to_vec()));
Argv::from(argv)
};
emit(w, &named(b"CREATE", &[&last_delivered, b"MKSTREAM"]), fmt, scratch)?;
let mut frames = 1;
if let Some(n) = g.entries_read {
let n = n.to_string();
emit(w, &named(b"SETID", &[&last_delivered, b"ENTRIESREAD", n.as_bytes()]), fmt, scratch)?;
frames += 1;
}
for (consumer, _) in &g.consumers {
emit(w, &named(b"CREATECONSUMER", &[consumer]), fmt, scratch)?;
frames += 1;
}
Ok(frames)
}
fn pending_argv(
key: &[u8],
group: &[u8],
consumer: &[u8],
id: StreamId,
delivery_time_ms: u64,
delivery_count: u64,
) -> Vec<Vec<u8>> {
vec![
kevy_resp::ops_table::PENDING.as_bytes().to_vec(),
key.to_vec(),
group.to_vec(),
consumer.to_vec(),
delivery_time_ms.to_string().into_bytes(),
delivery_count.to_string().into_bytes(),
id.encode(),
]
}
fn write_consumer_times<W: Write>(
w: &mut W,
key: &[u8],
g: &kevy_store::LoadedGroup,
fmt: crate::AofFormat,
scratch: &mut Vec<u8>,
) -> io::Result<usize> {
let mut active: Vec<&(Vec<u8>, u64)> = g.active.iter().collect();
active.sort_unstable();
for (consumer, last_seen_ms) in &g.consumers {
let mut argv = vec![
kevy_resp::ops_table::CONSUMER_SEEN.as_bytes().to_vec(),
key.to_vec(),
g.name.clone(),
consumer.clone(),
last_seen_ms.to_string().into_bytes(),
];
if let Ok(i) = active.binary_search_by(|(name, _)| name.as_slice().cmp(consumer)) {
argv.push(active[i].1.to_string().into_bytes());
}
emit(w, &Argv::from(argv), fmt, scratch)?;
}
Ok(g.consumers.len())
}