use std::sync::Arc;
use anyhow::Result;
use super::auth::Viewer;
use super::changes::{Notifier, Receiver};
use super::watching::Following;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum Readiness {
Wait,
Drain,
}
pub(super) struct Subscription {
changes: Receiver,
check: Check,
authorized_accounts: Option<u64>,
granted_commands: Option<u64>,
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_commands: 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
}
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_commands != Some(current.commands) {
self.granted_commands = Some(current.commands);
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;