watchers 1.5.0

Git integrated file watcher
use std::{
    sync::{Arc, Condvar, Mutex},
    thread::{self, JoinHandle},
    time::Duration,
};

use log::debug;

use crate::git::EventContext;

pub struct Debouncer<F> {
    callback: Arc<Mutex<F>>,
    delay: Duration,
    cancel_signal: Arc<(Mutex<bool>, Condvar)>,
    current_thread: Option<JoinHandle<()>>,
    pending_context: Arc<Mutex<Option<EventContext>>>,
}

impl<F> Debouncer<F>
where
    F: FnMut(EventContext) + Send + 'static,
{
    pub fn new(callback: F, delay: Duration) -> Self {
        Self {
            callback: Arc::new(Mutex::new(callback)),
            delay,
            cancel_signal: Arc::new((Mutex::new(false), Condvar::new())),
            current_thread: None,
            pending_context: Arc::new(Mutex::new(None)),
        }
    }

    pub fn on_event(&mut self, context: EventContext) {
        *self.pending_context.lock().unwrap() = Some(context);
        self.cancel_current_thread();
        debug!("cancelled old thread");

        let callback = Arc::clone(&self.callback);
        let pending_context = Arc::clone(&self.pending_context);
        let cancel_signal = Arc::new((Mutex::new(false), Condvar::new()));
        self.cancel_signal = Arc::clone(&cancel_signal);
        let delay = self.delay;

        let handle = thread::spawn(move || {
            let (lock, cvar) = &*cancel_signal;
            let mut cancelled = lock.lock().unwrap();

            let result = cvar.wait_timeout(cancelled, delay).unwrap();
            cancelled = result.0;

            if (!*cancelled && result.1.timed_out())
                && let (Ok(mut cb), Ok(mut context_guard)) =
                    (callback.lock(), pending_context.lock())
                && let Some(context) = context_guard.take()
            {
                cb(context);
            }
        });

        self.current_thread = Some(handle);
    }
}

impl<F> Debouncer<F> {
    fn cancel_current_thread(&mut self) {
        if self.current_thread.is_some() {
            let (lock, cvar) = &*self.cancel_signal;
            let mut cancelled = lock.lock().unwrap();
            *cancelled = true;
            cvar.notify_all();

            let _old_handle = self.current_thread.take();
        }
    }
}

impl<F> Drop for Debouncer<F> {
    fn drop(&mut self) {
        self.cancel_current_thread();
    }
}