Skip to main content

syncweb_core/fs/
watcher.rs

1use std::{
2    path::{Path, PathBuf},
3    sync::mpsc::{self, Receiver, TryRecvError},
4};
5
6use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
7
8use crate::error::{Result, SyncwebError};
9
10/// A filesystem event delivered by [`FsWatcher`].
11#[derive(Clone, Debug)]
12#[non_exhaustive]
13pub struct FsEvent {
14    pub event: Event,
15    pub paths: Vec<PathBuf>,
16}
17
18/// notify-rs backed recursive filesystem watcher.
19pub struct FsWatcher {
20    watcher: RecommendedWatcher,
21    receiver: Receiver<Result<FsEvent>>,
22}
23
24impl std::fmt::Debug for FsWatcher {
25    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        formatter.debug_struct("FsWatcher").finish_non_exhaustive()
27    }
28}
29
30impl FsWatcher {
31    /// Start watching a path recursively.
32    /// # Errors
33    ///
34    /// Returns an error if the filesystem cannot be accessed or watched.
35    pub fn new(path: impl AsRef<Path>) -> Result<Self> {
36        let (sender, receiver) = mpsc::channel();
37        let watcher = notify::recommended_watcher(move |event_result: notify::Result<Event>| {
38            let result = event_result
39                .map(|event| FsEvent {
40                    event: event.clone(),
41                    paths: event.paths,
42                })
43                .map_err(|error| SyncwebError::operation("filesystem watcher event failed", error));
44            let _ = sender.send(result);
45        })
46        .map_err(|error| SyncwebError::operation("failed to create filesystem watcher", error))?;
47        let mut this = Self { watcher, receiver };
48        this.watcher
49            .watch(path.as_ref(), RecursiveMode::Recursive)
50            .map_err(|error| SyncwebError::operation("failed to watch filesystem path", error))?;
51        Ok(this)
52    }
53
54    /// Wait for the next filesystem event.
55    /// # Errors
56    ///
57    /// Returns an error if the filesystem cannot be accessed or watched.
58    pub fn recv(&self) -> Result<FsEvent> {
59        self.receiver
60            .recv()
61            .map_err(|error| SyncwebError::operation("filesystem watcher stopped", error))?
62    }
63
64    /// Poll for an event without blocking.
65    /// # Errors
66    ///
67    /// Returns an error if the filesystem cannot be accessed or watched.
68    pub fn try_recv(&self) -> Result<Option<FsEvent>> {
69        match self.receiver.try_recv() {
70            Ok(event) => event.map(Some),
71            Err(TryRecvError::Empty) => Ok(None),
72            Err(TryRecvError::Disconnected) => Err(SyncwebError::operation(
73                "filesystem watcher stopped",
74                "event channel disconnected",
75            )),
76        }
77    }
78
79    /// Stop watching the supplied path.
80    /// # Errors
81    ///
82    /// Returns an error if the filesystem cannot be accessed or watched.
83    pub fn unwatch(&mut self, path: impl AsRef<Path>) -> Result<()> {
84        self.watcher
85            .unwatch(path.as_ref())
86            .map_err(|error| SyncwebError::operation("failed to stop filesystem watcher", error))
87    }
88}