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,
}