1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
use actix::prelude::*;

use actix::Recipient;
use inotify::{EventMask, Inotify, WatchMask};
use std::{path::PathBuf, time::Duration};

const CHECK_INTERVAL: Duration = Duration::from_secs(1);

#[derive(Debug, Clone, Message)]
#[rtype(result = "()")]
pub struct WatchFile;

#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct FileUpdated;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("io")]
    Io(#[from] std::io::Error),
}

pub struct FileWatcher {
    inotify: Inotify,
    recipient: Recipient<FileUpdated>,
    run_interval: Option<SpawnHandle>,
}
impl FileWatcher {
    pub fn new(path: PathBuf, recipient: Recipient<FileUpdated>) -> Result<Self, Error> {
        let inotify = Inotify::init()?;
        inotify.watches().add(
            path,
            WatchMask::MODIFY | WatchMask::CREATE | WatchMask::DELETE | WatchMask::ATTRIB,
        )?;
        Ok(Self {
            inotify,
            recipient,
            run_interval: None,
        })
    }
}
impl Actor for FileWatcher {
    type Context = Context<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        tracing::info!("Starting file watcher actor");
        self.run_interval
            .replace(ctx.run_interval(CHECK_INTERVAL, |_act, ctx| {
                ctx.notify(WatchFile);
            }));
    }
}

impl Handler<WatchFile> for FileWatcher {
    type Result = ();

    fn handle(&mut self, _msg: WatchFile, _ctx: &mut Self::Context) -> Self::Result {
        let mut buffer = [0u8; 4096];
        if let Ok(mut events) = self.inotify.read_events(&mut buffer) {
            if events.any(|event| {
                event.mask.contains(EventMask::MODIFY) || event.mask.contains(EventMask::ATTRIB)
            }) {
                tracing::info!("File modified");
                self.recipient.do_send(FileUpdated);
            };
        }
    }
}