use core::future::Future;
use tokio::sync::broadcast::{self, error::RecvError};
use super::{AuthUpdate, Connection};
use crate::core::{AuthEvent, SessionGuard};
pub struct Updates {
conn: Connection,
guard: SessionGuard,
events: broadcast::Receiver<AuthEvent>,
pending: Option<Pending>,
ended: bool,
}
enum Pending {
Event(AuthEvent),
Lagged,
}
impl Updates {
pub(super) fn new(conn: Connection) -> Self {
let upgrade = conn.upgrade();
let guard = conn.auth().guard(&upgrade.user, &upgrade.session);
let events = conn.auth().events();
Self {
conn,
guard,
events,
pending: None,
ended: false,
}
}
pub async fn next(&mut self) -> Option<AuthUpdate> {
loop {
if self.ended {
return None;
}
if self.pending.is_none() {
tokio::select! {
biased;
_ = self.guard.ended() => {
self.ended = true;
return None;
}
event = self.events.recv() => {
self.pending = Some(match event {
Ok(event) => Pending::Event(event),
Err(RecvError::Lagged(..)) => Pending::Lagged,
Err(RecvError::Closed) => {
self.ended = true;
return None;
}
});
}
}
}
let result = match &self.pending {
Some(Pending::Event(event)) => self.conn.update(event).await,
Some(Pending::Lagged) => self.conn.lagged().await,
None => continue,
};
let pending = self.pending.take();
match result {
Ok(Some(update)) => return Some(update),
Ok(None) => {}
Err(error) => {
let what = match pending {
Some(Pending::Lagged) => "a lag",
_ => "an event",
};
tracing::error!(error = ?error, "auth: filtering {what} for a socket failed: {error}");
}
}
}
}
pub async fn unless_ended<F>(&self, future: F) -> Option<F::Output>
where
F: Future,
{
if self.ended {
return None;
}
tokio::select! {
biased;
_ = self.guard.ended() => None,
output = future => Some(output),
}
}
pub fn connection(&self) -> &Connection {
&self.conn
}
pub fn guard(&self) -> &SessionGuard {
&self.guard
}
}