use std::sync::Arc;
use anyhow::Result;
use super::auth::Viewer;
use super::changes::{Notifier, Receiver, Revision};
use super::watching::Following;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum Readiness {
Wait,
Page,
Drain,
}
pub(super) struct Subscription {
changes: Receiver,
check: Check,
authorized_accounts: Option<u64>,
granted: Option<Revision>,
lease: Option<Arc<Following>>,
}
impl Subscription {
pub fn new(changes: Receiver, viewer: Viewer, notifier: &Notifier) -> Self {
let lease = notifier.follow(changes.clone());
Self::with(changes, Check::Viewer(viewer), lease)
}
fn with(changes: Receiver, check: Check, lease: Arc<Following>) -> Self {
Self {
changes,
check,
authorized_accounts: None,
granted: None,
lease: Some(lease),
}
}
pub async fn ready(&mut self, mode: Readiness) -> Result<bool> {
if self.lease.is_none() {
return Ok(false);
}
let result = self.next(mode).await;
if !matches!(result, Ok(true)) {
self.lease = None;
}
result
}
pub fn revision(&self) -> Revision {
self.granted.expect("a work turn has been granted")
}
async fn next(&mut self, mode: Readiness) -> Result<bool> {
loop {
if self.changes.has_changed().is_err() {
return Ok(false);
}
let current = *self.changes.borrow_and_update();
if self.authorized_accounts != Some(current.accounts) {
let check = self.check.clone();
if !tokio::task::spawn_blocking(move || check.allowed()).await?? {
return Ok(false);
}
self.authorized_accounts = Some(current.accounts);
continue;
}
if mode == Readiness::Drain
|| self.granted.is_none_or(|previous| {
previous.commands != current.commands
|| (mode == Readiness::Page
&& (previous.styles != current.styles
|| previous.scripts != current.scripts))
})
{
self.granted = Some(current);
return Ok(true);
}
if self.changes.changed().await.is_err() {
return Ok(false);
}
}
}
}
#[derive(Clone)]
enum Check {
Viewer(Viewer),
#[cfg(test)]
Scripted(Arc<dyn Fn() -> Result<bool> + Send + Sync>),
}
impl Check {
fn allowed(&self) -> Result<bool> {
match self {
Self::Viewer(viewer) => viewer.still_allowed(),
#[cfg(test)]
Self::Scripted(check) => check(),
}
}
}
#[cfg(test)]
mod tests;