use crate::Error;
use kio::time::{Deadline, Instant};
use moq_net::{broadcast, track};
use std::{
collections::VecDeque,
task::{Poll, ready},
time::Duration,
};
pub const TRACK: &str = "chat";
pub const PRIORITY: u8 = 10;
pub const HISTORY: Duration = Duration::from_secs(10);
pub type Event = moq_json::window::Event<String>;
pub fn info() -> track::Info {
track::Info::default().with_priority(PRIORITY).with_ordered(false)
}
pub struct Publisher {
producer: moq_json::window::Producer<String>,
expires: VecDeque<Instant>,
deadline: Deadline,
}
impl Publisher {
pub fn create(broadcast: &mut broadcast::Producer) -> Result<Self, Error> {
Ok(Self::new(broadcast.create_track(TRACK, info())?))
}
pub fn new(track: track::Producer) -> Self {
Self {
producer: moq_json::window::Producer::new(
track,
moq_json::window::ProducerConfig::default().with_op_ratio(0),
),
expires: VecDeque::new(),
deadline: Deadline::new(),
}
}
pub fn send(&mut self, text: &str) -> Result<(), Error> {
if text.is_empty() {
return Ok(());
}
self.prune()?;
self.producer.push(&text.to_owned())?;
self.expires.push_back(Instant::now() + HISTORY);
Ok(())
}
fn prune(&mut self) -> Result<(), Error> {
let now = Instant::now();
let count = self.expires.iter().take_while(|expires| **expires <= now).count();
if count == 0 {
return Ok(());
}
self.producer.pop(count as u64)?;
self.expires.drain(..count);
Ok(())
}
pub fn poll_expire(&mut self, waiter: &kio::Waiter) -> Poll<Result<(), Error>> {
self.deadline.set(self.expires.front().copied());
ready!(self.deadline.poll(waiter));
Poll::Ready(self.prune())
}
pub async fn expire(&mut self) -> Result<(), Error> {
kio::wait(|waiter| self.poll_expire(waiter)).await
}
pub fn finish(self) -> Result<(), Error> {
Ok(self.producer.finish()?)
}
}
pub struct Subscriber {
consumer: moq_json::window::Consumer<String>,
}
impl Subscriber {
pub async fn subscribe(broadcast: &broadcast::Consumer) -> Result<Self, Error> {
Ok(Self::new(broadcast.track(TRACK)?.subscribe(None).await?))
}
pub fn new(mut track: track::Subscriber) -> Self {
if let Some(latest) = track.latest() {
track.start_at(latest);
}
Self {
consumer: moq_json::window::Consumer::new(track, Default::default()),
}
}
pub fn poll_recv(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Event>, Error>> {
Poll::Ready(Ok(ready!(self.consumer.poll_next(waiter))?))
}
pub async fn recv(&mut self) -> Result<Option<Event>, Error> {
kio::wait(|waiter| self.poll_recv(waiter)).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn expires_idle_history_and_late_readers_only_see_retained_messages() {
tokio::time::pause();
let mut broadcast = broadcast::Info::new().produce();
let mut publisher = Publisher::create(&mut broadcast).unwrap();
let mut subscriber = Subscriber::subscribe(&broadcast.consume()).await.unwrap();
publisher.send("first").unwrap();
assert_eq!(
subscriber.recv().await.unwrap(),
Some(Event::Push {
index: 0,
value: "first".into()
})
);
publisher.expire().await.unwrap();
assert_eq!(subscriber.recv().await.unwrap(), Some(Event::Pop(0..1)));
publisher.send("second").unwrap();
let mut late = Subscriber::subscribe(&broadcast.consume()).await.unwrap();
assert_eq!(
late.recv().await.unwrap(),
Some(Event::Push {
index: 1,
value: "second".into()
})
);
publisher.finish().unwrap();
assert!(late.recv().await.unwrap().is_none());
}
#[tokio::test]
async fn rejects_malformed_records() {
let mut broadcast = broadcast::Info::new().produce();
let mut track = broadcast.create_track(TRACK, info()).unwrap();
let mut subscriber = Subscriber::subscribe(&broadcast.consume()).await.unwrap();
track
.write_frame(moq_net::Timestamp::now(), r#"{"offset":0,"records":[42]}"#)
.unwrap();
assert!(matches!(subscriber.recv().await, Err(Error::Json(_))));
}
#[tokio::test]
async fn propagates_track_failure() {
let mut broadcast = broadcast::Info::new().produce();
let publisher = Publisher::create(&mut broadcast).unwrap();
let mut subscriber = Subscriber::subscribe(&broadcast.consume()).await.unwrap();
drop(publisher);
assert!(subscriber.recv().await.is_err());
}
}