use std::sync::Arc;
use std::time::Duration;
use sofie_core::{Error, Result};
use tokio::sync::watch;
use tokio::task::JoinHandle;
use super::{Registry, Worker};
use crate::{Db, Entry, FeedSource, Trigger};
const PRUNE: &str = "sofie.feed.prune";
#[derive(Default)]
pub struct Feeds {
published: Vec<&'static str>,
pulled: Vec<Arc<dyn FeedSource>>,
}
impl Feeds {
#[must_use]
pub fn publish(mut self, event: &'static str) -> Self {
self.published.push(event);
self
}
#[must_use]
pub fn pull(mut self, sources: impl IntoIterator<Item = Arc<dyn FeedSource>>) -> Self {
self.pulled.extend(sources);
self
}
#[must_use]
pub fn attach(self, mut worker: Worker) -> Worker {
self.register(&mut worker.registry);
worker.feeds = self;
worker
}
fn register(&self, registry: &mut Registry) {
for event in &self.published {
registry.add_taking_pruned(Entry {
name: event,
trigger: Trigger::On(event),
run: crate::feed::append(event),
});
}
if !self.published.is_empty() && registry.job(PRUNE).is_none() {
registry.add(Entry {
name: PRUNE,
trigger: Trigger::Every(Duration::from_hours(1).into()),
run: crate::feed::prune(),
});
}
}
pub(super) fn refuse_unkept(&self, db: &Db) -> Result<()> {
if self.published.is_empty() && self.pulled.is_empty() {
return Ok(());
}
crate::feed::feed_sql(db.driver().dialect()).map(drop)
}
pub(super) async fn pull_all(&self, db: &Db) -> Result<()> {
for source in &self.pulled {
while crate::feed::pull_once(db, source.as_ref()).await? {}
}
Ok(())
}
pub(super) fn start(
&self,
db: &Db,
shutdown: impl Future<Output = ()> + Send,
) -> (impl Future<Output = ()> + Send, Pulling) {
let (stop, stopped) = watch::channel(false);
let pulls = self
.pulled
.iter()
.map(|source| {
let pull = crate::feed::pull(db.clone(), Arc::clone(source), stopped.clone());
tokio::spawn(pull)
})
.collect();
let shutdown = async move {
shutdown.await;
let _ = stop.send(true);
};
(shutdown, Pulling(pulls))
}
}
pub(super) struct Pulling(Vec<JoinHandle<()>>);
impl Pulling {
pub(super) async fn finish(self) -> Result<()> {
for pull in self.0 {
pull.await
.map_err(|error| Error::internal("pulling another app's events", error))?;
}
Ok(())
}
}