From f427f24586deae4a7340f7f2ae08aaeb9ceab869 Mon Sep 17 00:00:00 2001 From: KFears Date: Sat, 18 Nov 2023 14:14:45 +0400 Subject: [PATCH 1/2] Use PollWatcher to always get filesystem updates See https://github.com/rust-lang/mdBook/issues/2102#issuecomment-1810982299 for explanation on this Closes #2102, #2035, #383 and #1441 --- src/cmd/watch.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index 80b9ff1b19..53d54ae6e0 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -4,6 +4,7 @@ use ignore::gitignore::Gitignore; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; +use notify_debouncer_mini::Config; use pathdiff::diff_paths; use std::path::{Path, PathBuf}; use std::sync::mpsc::channel; @@ -117,8 +118,17 @@ where // Create a channel to receive the events. let (tx, rx) = channel(); - - let mut debouncer = match notify_debouncer_mini::new_debouncer(Duration::from_secs(1), tx) { + // Notify backend configuration + let backend_config = notify::Config::default().with_poll_interval(Duration::from_secs(1)); + // Debouncer configuration + let debouncer_config = Config::default() + .with_timeout(Duration::from_secs(1)) + .with_notify_config(backend_config); + + let mut debouncer = match notify_debouncer_mini::new_debouncer_opt::<_, notify::PollWatcher>( + debouncer_config, + tx, + ) { Ok(d) => d, Err(e) => { error!("Error while trying to watch the files:\n\n\t{:?}", e); From 43464c6d82ae7756f2c097e852743d4f142695d6 Mon Sep 17 00:00:00 2001 From: KFears Date: Wed, 6 Dec 2023 20:16:22 +0400 Subject: [PATCH 2/2] feat: add flag to choose backend --- src/cmd/serve.rs | 62 +++++++++++++++++++++++++++++++++++------------- src/cmd/watch.rs | 56 ++++++++++++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index eeb19cb371..6485deed93 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -9,6 +9,8 @@ use mdbook::errors::*; use mdbook::utils; use mdbook::utils::fs::get_404_output_file; use mdbook::MDBook; +use notify::PollWatcher; +use notify::RecommendedWatcher; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::PathBuf; use tokio::sync::broadcast; @@ -43,6 +45,9 @@ pub fn make_subcommand() -> Command { .help("Port to use for HTTP connections"), ) .arg_open() + .arg(arg!(--compat "Watch files in compatibility mode.\n\ + Use this if your environment doesn't support filesystem events (Windows, Docker, NFS), + or if you encounter issues otherwise")) } // Serve command implementation @@ -97,23 +102,48 @@ pub fn execute(args: &ArgMatches) -> Result<()> { } #[cfg(feature = "watch")] - watch::trigger_on_change(&book, move |paths, book_dir| { - info!("Files changed: {:?}", paths); - info!("Building book..."); - - // FIXME: This area is really ugly because we need to re-set livereload :( - let result = MDBook::load(book_dir).and_then(|mut b| { - update_config(&mut b); - b.build() - }); + let polling = args.get_flag("compat"); - if let Err(e) = result { - error!("Unable to load the book"); - utils::log_backtrace(&e); - } else { - let _ = tx.send(Message::text("reload")); - } - }); + #[cfg(feature = "watch")] + if polling { + debug!("Using PollWatcher backend"); + watch::trigger_on_change::<_, PollWatcher>(&book, move |paths, book_dir| { + info!("Files changed: {:?}", paths); + info!("Building book..."); + + // FIXME: This area is really ugly because we need to re-set livereload :( + let result = MDBook::load(book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to load the book"); + utils::log_backtrace(&e); + } else { + let _ = tx.send(Message::text("reload")); + } + }); + } else { + debug!("Using RecommendWatcher backend"); + watch::trigger_on_change::<_, RecommendedWatcher>(&book, move |paths, book_dir| { + info!("Files changed: {:?}", paths); + info!("Building book..."); + + // FIXME: This area is really ugly because we need to re-set livereload :( + let result = MDBook::load(book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to load the book"); + utils::log_backtrace(&e); + } else { + let _ = tx.send(Message::text("reload")); + } + }); + } let _ = thread_handle.join(); diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index 53d54ae6e0..4385a9dfe9 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -4,6 +4,9 @@ use ignore::gitignore::Gitignore; use mdbook::errors::Result; use mdbook::utils; use mdbook::MDBook; +use notify::PollWatcher; +use notify::RecommendedWatcher; +use notify::Watcher; use notify_debouncer_mini::Config; use pathdiff::diff_paths; use std::path::{Path, PathBuf}; @@ -18,6 +21,9 @@ pub fn make_subcommand() -> Command { .arg_dest_dir() .arg_root_dir() .arg_open() + .arg(arg!(--compat "Watch files in compatibility mode.\n\ + Use this if your environment doesn't support filesystem events (Windows, Docker, NFS), + or if you encounter issues otherwise")) } // Watch command implementation @@ -42,18 +48,37 @@ pub fn execute(args: &ArgMatches) -> Result<()> { open(path); } - trigger_on_change(&book, |paths, book_dir| { - info!("Files changed: {:?}\nBuilding book...\n", paths); - let result = MDBook::load(book_dir).and_then(|mut b| { - update_config(&mut b); - b.build() - }); + let polling = args.get_flag("compat"); - if let Err(e) = result { - error!("Unable to build the book"); - utils::log_backtrace(&e); - } - }); + if polling { + debug!("Using PollWatcher backend"); + trigger_on_change::<_, PollWatcher>(&book, |paths, book_dir| { + info!("Files changed: {:?}\nBuilding book...\n", paths); + let result = MDBook::load(book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to build the book"); + utils::log_backtrace(&e); + } + }); + } else { + debug!("Using RecommendWatcher backend"); + trigger_on_change::<_, RecommendedWatcher>(&book, |paths, book_dir| { + info!("Files changed: {:?}\nBuilding book...\n", paths); + let result = MDBook::load(book_dir).and_then(|mut b| { + update_config(&mut b); + b.build() + }); + + if let Err(e) = result { + error!("Unable to build the book"); + utils::log_backtrace(&e); + } + }); + }; Ok(()) } @@ -110,9 +135,10 @@ fn filter_ignored_files(ignore: Gitignore, paths: &[PathBuf]) -> Vec { } /// Calls the closure when a book source file is changed, blocking indefinitely. -pub fn trigger_on_change(book: &MDBook, closure: F) +pub fn trigger_on_change(book: &MDBook, closure: F) where F: Fn(Vec, &Path), + W: 'static + Watcher + Send, { use notify::RecursiveMode::*; @@ -125,10 +151,8 @@ where .with_timeout(Duration::from_secs(1)) .with_notify_config(backend_config); - let mut debouncer = match notify_debouncer_mini::new_debouncer_opt::<_, notify::PollWatcher>( - debouncer_config, - tx, - ) { + let mut debouncer = match notify_debouncer_mini::new_debouncer_opt::<_, W>(debouncer_config, tx) + { Ok(d) => d, Err(e) => { error!("Error while trying to watch the files:\n\n\t{:?}", e);