Documentation
use futures_util::{future::Future, ready, stream::Stream};
use std::{
    path::{Path, PathBuf},
    pin::Pin,
    task::{Context, Poll},
    time::{Duration, SystemTime},
};
use tokio::{
    fs::File,
    io::Result,
    time::{interval, Interval},
};

pub struct Watch {
    path: PathBuf,
    state: Option<Pin<Box<dyn Future<Output = Result<SystemTime>>>>>,
    modified: Result<SystemTime>,
    timer: Interval,
}

impl Watch {
    pub async fn new<P: AsRef<Path>>(path: P, duration: Duration) -> Watch {
        let path = path.as_ref().to_path_buf();
        Watch {
            path: path.clone(),
            state: None,
            modified: Self::modified(path).await,
            timer: interval(duration),
        }
    }

    async fn modified(p: PathBuf) -> Result<SystemTime> {
        let file = File::open(p).await?;
        file.metadata().await?.modified()
    }

    fn eq(a: &Result<SystemTime>, b: &Result<SystemTime>) -> bool {
        if a.is_ok() && b.is_ok() {
            if a.as_ref().ok() == b.as_ref().ok() {
                return true;
            }
        } else if a.is_err() && b.is_err() {
            if let (Some(left), Some(right)) = (a.as_ref().err(), b.as_ref().err()) {
                if left.kind() == right.kind() && left.raw_os_error() == right.raw_os_error() {
                    return true;
                }
            }
        }
        false
    }
}

impl Stream for Watch {
    type Item = ();
    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        loop {
            if let Some(state) = &mut self.state {
                let modified: Result<SystemTime> = ready!(Pin::new(state).poll(cx));
                self.state = None;

                if !Self::eq(&self.modified, &modified) {
                    self.modified = modified;
                    return Poll::Ready(Some(()));
                }
            } else {
                ready!(self.timer.poll_tick(cx));
                self.state = Some(Box::pin(Self::modified(self.path.clone())));
            }
        }
    }
}