use ninep::sync::server::{ClientId, ReadOutcome};
use std::{
collections::HashMap,
mem::swap,
sync::mpsc::{Receiver, Sender, channel},
thread::spawn,
};
use tracing::{debug, error};
pub(super) fn spawn_log_listener(
log_event_rx: Receiver<LogEvent>,
listener_tx: Sender<LogEvent>,
pending_rx: Receiver<Sender<Vec<u8>>>,
) {
spawn(move || {
for event in log_event_rx.iter() {
for tx in pending_rx.try_iter() {
_ = tx.send(event.as_log_line_bytes());
}
_ = listener_tx.send(event);
}
});
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum LogEvent {
Open(usize),
Close(usize),
Focus(usize),
Save(usize),
}
impl LogEvent {
pub(crate) fn as_log_line_bytes(self) -> Vec<u8> {
let s = match self {
LogEvent::Open(id) => format!("{id} open\n"),
LogEvent::Close(id) => format!("{id} close\n"),
LogEvent::Focus(id) => format!("{id} focus\n"),
LogEvent::Save(id) => format!("{id} save\n"),
};
s.into_bytes()
}
}
#[derive(Debug)]
pub(super) enum ClientLog {
Events(Vec<LogEvent>),
Pending,
}
impl ClientLog {
fn is_empty_events(&self) -> bool {
matches!(self, ClientLog::Events(v) if v.is_empty())
}
}
#[derive(Debug)]
pub(super) struct Log {
events: HashMap<ClientId, ClientLog>,
tx: Sender<Sender<Vec<u8>>>,
}
impl Log {
pub(super) fn new(tx: Sender<Sender<Vec<u8>>>) -> Self {
Self {
events: HashMap::default(),
tx,
}
}
#[inline]
pub(super) fn push(&mut self, evt: LogEvent) {
for cl in self.events.values_mut() {
match cl {
ClientLog::Events(v) => v.push(evt),
ClientLog::Pending => {
*cl = ClientLog::Events(Vec::new());
}
}
}
}
#[inline]
pub(super) fn add_client(&mut self, cid: ClientId) {
self.events.insert(cid, ClientLog::Events(Vec::new()));
}
#[inline]
pub(super) fn remove_client(&mut self, cid: ClientId) {
self.events.remove(&cid);
}
#[inline]
pub(super) fn events_since_last_read(&mut self, cid: ClientId) -> ReadOutcome {
match self.events.get_mut(&cid) {
Some(cl) if cl.is_empty_events() => {
let (tx, rx) = channel();
if self.tx.send(tx).is_err() {
error!("log listener died");
return ReadOutcome::Immediate(Vec::new());
}
*cl = ClientLog::Pending;
ReadOutcome::Blocked(rx)
}
Some(ClientLog::Events(events)) => {
let mut v = Vec::new();
swap(events, &mut v);
ReadOutcome::Immediate(v.into_iter().flat_map(|e| e.as_log_line_bytes()).collect())
}
Some(ClientLog::Pending) => {
debug!("got log read from {cid:?} while blocked read was outstanding");
ReadOutcome::Immediate(Vec::new())
}
None => {
error!("got log read from {cid:?} without initialising");
ReadOutcome::Immediate(Vec::new())
}
}
}
}