notify-rs / notify

🔭 Cross-platform filesystem notification library for Rust.
https://docs.rs/notify
2.77k stars 222 forks source link

How to stop a debouncer watcher if it's mut reference? #585

Closed Hellager closed 2 months ago

Hellager commented 7 months ago

Here's a minimal example code, the question is about function shutdown.

If direct call handler.stop(), it will errors

error[E0507]: cannot move out of `*watcher` which is behind a mutable reference
    |
200 |             watcher.stop();
    |             ^^^^^^^ ------ `*watcher` moved due to this method call
    |             |
    |             move occurs because `*watcher` has type `Debouncer<notify::ReadDirectoryChangesWatcher, FileIdMap>`, whic, which does not implement the `Copy` trait

If the method stop(self) can be stop(&mut self), it will be ok.

So what's the proper way to stop the debouncer watcher if it's mut reference?

use chrono::prelude::*;
use notify::{Error, ReadDirectoryChangesWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{
    new_debouncer, DebounceEventResult, DebouncedEvent, Debouncer, FileIdMap,
};
use std::{path::Path, time::Duration};
use tokio::{runtime::Handle, sync::mpsc::Receiver};

pub struct NotifyHandler {
    pub notify_watcher: Option<Debouncer<ReadDirectoryChangesWatcher, FileIdMap>>,
    pub receiver: Option<Receiver<Result<Vec<DebouncedEvent>, Vec<Error>>>>,
}

impl NotifyHandler {
    pub async fn initialize_notify_scheduler(&mut self) {
        let (tx, rx) = tokio::sync::mpsc::channel(1);
        let rt = Handle::current();

        let debouncer = new_debouncer(
            Duration::from_secs(3),
            None,
            move |result: DebounceEventResult| {
                let tx = tx.clone();

                println!("calling by notify -> {:?}", &result);
                rt.spawn(async move {
                    if let Err(e) = tx.send(result).await {
                        println!("Error sending event result: {:?}", e);
                    }
                });
            },
        );

        match debouncer {
            Ok(watcher) => {
                println!("Initialize notify watcher success");
                self.notify_watcher = Some(watcher);

                self.receiver = Some(rx);
            }
            Err(error) => {
                println!("{:?}", error);
            }
        }
    }

    pub async fn watch(&mut self, path: &str) -> notify::Result<()> {
        let watch_path = Path::new(path);

        if watch_path.exists() {
            let is_file = watch_path.is_file();
            println!("Valid path {} is file {}", path, is_file);
        } else {
            println!("watch path {:?} not exists", watch_path);
        }

        if let Some(watcher) = self.notify_watcher.as_mut() {
            watcher
                .watcher()
                .watch(watch_path, RecursiveMode::Recursive)?;

            watcher
                .cache()
                .add_root(watch_path, RecursiveMode::Recursive);

            if let Some(mut rx) = self.receiver.take() {
                tokio::spawn(async move {
                    while let Some(res) = rx.recv().await {
                        match res {
                            Ok(events) => {
                                println!("events: {:?}", events);
                            }
                            Err(errors) => {
                                println!("errors: {:?}", errors)
                            }
                        }
                    }
                });
            }
        }

        Ok(())
    }

    pub async fn shutdown(&mut self) -> Result<(), std::io::Error> {
          if let Some(watcher) = self.notify_watcher().await {
                 if let Some(rcv) = self.receiver.as_mut() {
                      rcv.close();
                 }

                 watcher.stop();
           }
           Ok(())
    }
}

#[tokio::main]
async fn main() {
    let mut notifier: NotifyHandler = NotifyHandler {
        notify_watcher: None,
        receiver: None,
    };

    notifier.initialize_notify_scheduler().await;
    notifier.watch("D:\\Temp\\program\\test_md.txt").await.unwrap();

    loop {
        tokio::time::sleep(Duration::from_secs(3)).await;

        let time: DateTime<Local> = Local::now();

        println!(
            "{}: Hello, world!",
            time.format("%Y-%m-%d %H:%M:%S").to_string()
        );

        notifier.shutdown().unwrap();
    }
}
dfaust commented 2 months ago

In this case you have to take the notify_watcher:

if let Some(watcher) = self.notify_watcher.take() {
    watcher.stop();
}