Skip to content

Commit

Permalink
feat: add polling mode for filesystem watcher
Browse files Browse the repository at this point in the history
This should allow to work around issues on windows, where the FS events
don't get detected well.
  • Loading branch information
ctron committed Sep 29, 2023
1 parent f8a7949 commit 11f879e
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 12 deletions.
4 changes: 4 additions & 0 deletions src/config/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ pub struct ConfigOptsWatch {
/// Paths to ignore [default: []]
#[arg(short, long, value_name = "path")]
pub ignore: Option<Vec<PathBuf>>,
/// Using polling mode for detecting changes
#[arg(short, long)]
pub poll: bool,
}

/// Config options for the serve system.
Expand Down Expand Up @@ -327,6 +330,7 @@ impl ConfigOpts {
let opts = ConfigOptsWatch {
watch: cli.watch,
ignore: cli.ignore,
poll: cli.poll,
};
let cfg = ConfigOpts {
build: None,
Expand Down
3 changes: 3 additions & 0 deletions src/config/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ pub struct RtcWatch {
pub paths: Vec<PathBuf>,
/// Paths to ignore.
pub ignored_paths: Vec<PathBuf>,
/// Use polling mode for detecting chnages
pub poll: bool,
}

impl RtcWatch {
Expand Down Expand Up @@ -234,6 +236,7 @@ impl RtcWatch {
build,
paths,
ignored_paths,
poll: opts.poll,
})
}
}
Expand Down
48 changes: 36 additions & 12 deletions src/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use std::time::Duration;
use anyhow::{Context, Result};
use futures_util::stream::StreamExt;
use notify::event::ModifyKind;
use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use notify::{EventKind, PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{
new_debouncer, DebounceEventResult, DebouncedEvent, Debouncer, FileIdMap,
new_debouncer_opt, DebounceEventResult, DebouncedEvent, Debouncer, FileIdMap,
};
use tokio::sync::{broadcast, mpsc};
use tokio::time::Instant;
Expand All @@ -16,8 +16,19 @@ use tokio_stream::wrappers::BroadcastStream;
use crate::build::BuildSystem;
use crate::config::RtcWatch;

/// The debouncer type used in this module.
type FsDebouncer = Debouncer<RecommendedWatcher, FileIdMap>;
pub enum FsDebouncer {
Default(Debouncer<RecommendedWatcher, FileIdMap>),
Polling(Debouncer<PollWatcher, FileIdMap>),
}

impl FsDebouncer {
pub fn watcher(&mut self) -> &mut dyn Watcher {
match self {
Self::Default(deb) => deb.watcher(),
Self::Polling(deb) => deb.watcher(),
}
}
}

/// Blacklisted path segments which are ignored by the watcher by default.
const BLACKLIST: [&str; 1] = [".git"];
Expand Down Expand Up @@ -64,7 +75,7 @@ impl WatchSystem {
let (build_tx, build_rx) = mpsc::channel(1);

// Build the watcher.
let _debouncer = build_watcher(watch_tx, cfg.paths.clone())?;
let _debouncer = build_watcher(watch_tx, cfg.paths.clone(), cfg.poll)?;

// Build dependencies.
let build = BuildSystem::new(cfg.build.clone(), Some(build_tx)).await?;
Expand Down Expand Up @@ -182,13 +193,10 @@ impl WatchSystem {
}
}

/// Build a FS watcher, when the watcher is dropped, it will stop watching for events.
fn build_watcher(
fn new_debouncer<T: Watcher>(
watch_tx: mpsc::Sender<DebouncedEvent>,
paths: Vec<PathBuf>,
) -> Result<FsDebouncer> {
// Build the filesystem watcher & debouncer.
let mut debouncer = new_debouncer(
) -> Result<Debouncer<T, FileIdMap>> {
new_debouncer_opt::<_, T, FileIdMap>(
DEBOUNCE_DURATION,
None,
move |result: DebounceEventResult| match result {
Expand All @@ -199,8 +207,24 @@ fn build_watcher(
.into_iter()
.for_each(|err| tracing::warn!(error=?err, "error from filesystem watcher")),
},
FileIdMap::new(),
notify::Config::default(),
)
.context("failed to build file system watcher")?;
.context("failed to build file system watcher")
}

/// Build a FS watcher, when the watcher is dropped, it will stop watching for events.
fn build_watcher(
watch_tx: mpsc::Sender<DebouncedEvent>,
paths: Vec<PathBuf>,
poll: bool,
) -> Result<FsDebouncer> {
// Build the filesystem watcher & debouncer.

let mut debouncer = match poll {
false => FsDebouncer::Default(new_debouncer::<RecommendedWatcher>(watch_tx)?),
true => FsDebouncer::Polling(new_debouncer::<PollWatcher>(watch_tx)?),
};

// Create a recursive watcher on each of the given paths.
// NOTE WELL: it is expected that all given paths are canonical. The Trunk config
Expand Down

0 comments on commit 11f879e

Please sign in to comment.