use std::{future::Future, pin::pin, sync::Arc, time::Duration};
use tokio::sync::{watch, Notify};
use tracing::{debug, trace};
use crate::{
action::{ActionHandler, ActionReturn},
changeable::{Changeable, ChangeableFn},
filter::{ChangeableFilterer, Filterer},
sources::fs::{WatchedPath, Watcher},
ErrorHook,
};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Config {
pub(crate) change_signal: Arc<Notify>,
pub action_handler: ChangeableFn<ActionHandler, ActionReturn>,
pub error_handler: ChangeableFn<ErrorHook, ()>,
pub pathset: Changeable<Vec<WatchedPath>>,
pub file_watcher: Changeable<Watcher>,
pub keyboard_events: Changeable<bool>,
pub throttle: Changeable<Duration>,
pub filterer: ChangeableFilterer,
pub error_channel_size: usize,
pub event_channel_size: usize,
pub(crate) fs_ready: watch::Sender<()>,
}
impl Default for Config {
fn default() -> Self {
Self {
change_signal: Default::default(),
action_handler: ChangeableFn::new(ActionReturn::Sync),
error_handler: Default::default(),
pathset: Default::default(),
file_watcher: Default::default(),
keyboard_events: Default::default(),
throttle: Changeable::new(Duration::from_millis(50)),
filterer: Default::default(),
error_channel_size: 64,
event_channel_size: 4096,
fs_ready: watch::channel(()).0,
}
}
}
impl Config {
#[allow(
clippy::must_use_candidate,
reason = "this return can explicitly be ignored"
)]
pub fn signal_change(&self) -> &Self {
self.change_signal.notify_waiters();
self
}
#[must_use]
pub(crate) fn watch(&self) -> ConfigWatched {
ConfigWatched::new(self.change_signal.clone())
}
pub fn fs_ready(&self) -> watch::Receiver<()> {
self.fs_ready.subscribe()
}
pub fn pathset<I, P>(&self, pathset: I) -> &Self
where
I: IntoIterator<Item = P>,
P: Into<WatchedPath>,
{
let pathset = pathset.into_iter().map(std::convert::Into::into).collect();
debug!(?pathset, "Config: pathset");
self.pathset.replace(pathset);
self.signal_change()
}
pub fn file_watcher(&self, watcher: Watcher) -> &Self {
debug!(?watcher, "Config: file watcher");
self.file_watcher.replace(watcher);
self.signal_change()
}
pub fn keyboard_events(&self, enable: bool) -> &Self {
debug!(?enable, "Config: keyboard");
self.keyboard_events.replace(enable);
self.signal_change()
}
pub fn throttle(&self, throttle: impl Into<Duration>) -> &Self {
let throttle = throttle.into();
debug!(?throttle, "Config: throttle");
self.throttle.replace(throttle);
self.signal_change()
}
pub fn filterer(&self, filterer: impl Filterer + 'static) -> &Self {
debug!(?filterer, "Config: filterer");
self.filterer.replace(filterer);
self.signal_change()
}
pub fn on_error(&self, handler: impl Fn(ErrorHook) + Send + Sync + 'static) -> &Self {
debug!("Config: on_error");
self.error_handler.replace(handler);
self.signal_change()
}
pub fn on_action(
&self,
handler: impl (Fn(ActionHandler) -> ActionHandler) + Send + Sync + 'static,
) -> &Self {
debug!("Config: on_action");
self.action_handler
.replace(move |action| ActionReturn::Sync(handler(action)));
self.signal_change()
}
pub fn on_action_async(
&self,
handler: impl (Fn(ActionHandler) -> Box<dyn Future<Output = ActionHandler> + Send + Sync>)
+ Send
+ Sync
+ 'static,
) -> &Self {
debug!("Config: on_action_async");
self.action_handler
.replace(move |action| ActionReturn::Async(handler(action)));
self.signal_change()
}
}
#[derive(Debug)]
pub(crate) struct ConfigWatched {
first_run: bool,
notify: Arc<Notify>,
}
impl ConfigWatched {
fn new(notify: Arc<Notify>) -> Self {
let notified = notify.notified();
pin!(notified).as_mut().enable();
Self {
first_run: true,
notify,
}
}
pub async fn next(&mut self) {
let notified = self.notify.notified();
let mut notified = pin!(notified);
notified.as_mut().enable();
if self.first_run {
trace!("ConfigWatched: first run");
self.first_run = false;
} else {
trace!(?notified, "ConfigWatched: waiting for change");
notified.await;
}
}
}