felicia 0.5.1

service for accessing and sharing lists of bad actors
Documentation
use std::time::Instant;

use tokio::sync::{mpsc, oneshot};

use crate::models::List;

use super::Source;

pub async fn spawn<S>(source: S) -> Result<SourceManagerHandle, Error>
where
    S: Source + Send + Sync + 'static,
{
    let mut list = match source.fetch().await {
        Ok(ls) => ls,
        Err(err) => {
            tracing::error!(
                "could not fetch from source -- {err} -- retrying during next request after {} seconds",
                source.lifetime()
            );
            List::empty()
        }
    };
    let mut last_updated = Instant::now();

    let (tx, mut rx) = mpsc::channel(128);
    tokio::spawn(async move {
        while let Some(msg) = rx.recv().await {
            match msg {
                Message::Fetch(sender) => {
                    if Instant::now().duration_since(last_updated).as_secs() > source.lifetime() {
                        match source.fetch().await {
                            Ok(res) => {
                                list = res;
                                last_updated = Instant::now();

                                let _ = sender.send(Ok(list.clone()));
                            }
                            Err(err) => {
                                tracing::error!(
                                    "could not fetch from source -- {err} -- retrying during next request after {} seconds",
                                    source.lifetime()
                                );
                                let _ = sender.send(Err(Error::Fetch));
                            }
                        }
                    } else {
                        let _ = sender.send(Ok(list.clone()));
                    }
                }
                Message::Update => match source.fetch().await {
                    Ok(res) => {
                        list = res;
                        last_updated = Instant::now();
                    }
                    Err(err) => tracing::error!("failed to refresh list: {err}"),
                },
            }
        }
    });

    Ok(SourceManagerHandle { sender: tx })
}

#[derive(Debug, Clone)]
pub struct SourceManagerHandle {
    sender: mpsc::Sender<Message>,
}

impl SourceManagerHandle {
    pub async fn fetch(&self) -> Result<List, Error> {
        let (tx, rx) = oneshot::channel();
        self.sender.send(Message::Fetch(tx)).await.map_err(|err| {
            tracing::error!("failed to contact actor: {err}");
            Error::Channel
        })?;

        rx.await.map_err(|err| {
            tracing::error!("failed to receive response from actor: {err}");
            Error::Channel
        })?
    }

    pub async fn refresh(&self) -> Result<(), Error> {
        self.sender.send(Message::Update).await.map_err(|err| {
            tracing::error!("failed to contact actor: {err}");
            Error::Channel
        })?;

        Ok(())
    }
}

#[derive(Debug)]
pub enum Message {
    Fetch(oneshot::Sender<Result<List, Error>>),
    Update,
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("could not fetch from source")]
    Fetch,

    #[error("channel error")]
    Channel,
}