@@ -95,7 +95,8 @@ pub use notify_types::debouncer_full::DebouncedEvent;
9595use file_id:: FileId ;
9696use notify:: {
9797 event:: { ModifyKind , RemoveKind , RenameMode } ,
98- Error , ErrorKind , Event , EventKind , RecommendedWatcher , RecursiveMode , Watcher , WatcherKind ,
98+ Error , ErrorKind , Event , EventKind , PathOp , RecommendedWatcher , RecursiveMode ,
99+ UpdatePathsError , Watcher , WatcherKind ,
99100} ;
100101
101102/// The set of requirements for watcher debounce event handling functions.
@@ -627,6 +628,76 @@ impl<T: Watcher, C: FileIdCache> Debouncer<T, C> {
627628 Ok ( ( ) )
628629 }
629630
631+ /// Add/remove paths to watch in batch.
632+ ///
633+ /// For some [`Watcher`] implementations this method provides better performance than multiple
634+ /// calls to [`Watcher::watch`] and [`Watcher::unwatch`] if you want to add/remove many paths at once.
635+ ///
636+ /// # Errors
637+ ///
638+ /// Returns [`UpdatePathsError`] if any operation fails. Operations are applied sequentially.
639+ /// When an error occurs, processing stops: operations before `origin` have been applied,
640+ /// `origin` is the operation that failed (if known), and `remaining` are the operations that
641+ /// were not attempted. `remaining` does not include `origin`.
642+ ///
643+ /// # Examples
644+ ///
645+ /// ```
646+ /// # use notify::{Watcher, RecursiveMode, PathOp};
647+ /// # use notify_debouncer_full::{RecommendedCache, new_debouncer_opt};
648+ /// # use std::path::{Path, PathBuf};
649+ /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
650+ /// # let mut debouncer = new_debouncer_opt::<_, notify::NullWatcher, _>(
651+ /// # std::time::Duration::from_secs(1),
652+ /// # None,
653+ /// # |e| {},
654+ /// # RecommendedCache::new(),
655+ /// # Default::default()
656+ /// # )?;
657+ /// debouncer.update_paths([
658+ /// PathOp::watch_recursive("path/to/file"),
659+ /// PathOp::unwatch("path/to/file2"),
660+ /// ])?;
661+ /// # Ok(())
662+ /// # }
663+ /// ```
664+ pub fn update_paths < Op : Into < PathOp > > (
665+ & mut self ,
666+ ops : impl IntoIterator < Item = Op > ,
667+ ) -> std:: result:: Result < ( ) , UpdatePathsError > {
668+ let mut paths = Vec :: new ( ) ;
669+ let ops: Vec < _ > = ops
670+ . into_iter ( )
671+ . map ( Into :: into)
672+ . inspect ( |op| {
673+ paths. push ( (
674+ op. as_path ( ) . to_path_buf ( ) ,
675+ match op {
676+ PathOp :: Watch ( _, config) => Some ( config. recursive_mode ( ) ) ,
677+ PathOp :: Unwatch ( _) => None ,
678+ } ,
679+ ) ) ;
680+ } )
681+ . collect ( ) ;
682+
683+ let res = self . watcher . update_paths ( ops) ;
684+ let updated_len = match res. as_ref ( ) {
685+ Ok ( ( ) ) => paths. len ( ) ,
686+ Err ( e) => {
687+ let failed = usize:: from ( e. origin . is_some ( ) ) ;
688+ paths. len ( ) . saturating_sub ( e. remaining . len ( ) + failed)
689+ }
690+ } ;
691+ let updated_paths = & paths[ ..updated_len] ;
692+ for ( path, watch_mode) in updated_paths {
693+ match watch_mode {
694+ Some ( recursive_mode) => self . add_root ( path, * recursive_mode) ,
695+ None => self . remove_root ( path) ,
696+ }
697+ }
698+ res
699+ }
700+
630701 pub fn configure ( & mut self , option : notify:: Config ) -> notify:: Result < bool > {
631702 self . watcher . configure ( option)
632703 }
@@ -789,7 +860,10 @@ fn sort_events(events: Vec<DebouncedEvent>) -> Vec<DebouncedEvent> {
789860
790861#[ cfg( test) ]
791862mod tests {
792- use std:: { fs, path:: Path } ;
863+ use std:: {
864+ fs,
865+ path:: { Path , PathBuf } ,
866+ } ;
793867
794868 use super :: * ;
795869
@@ -799,6 +873,42 @@ mod tests {
799873 use testing:: TestCase ;
800874 use time:: MockTime ;
801875
876+ #[ derive( Debug ) ]
877+ struct FailingWatcher {
878+ fail_path : PathBuf ,
879+ }
880+
881+ impl Watcher for FailingWatcher {
882+ fn new < F : notify:: EventHandler > (
883+ _event_handler : F ,
884+ _config : notify:: Config ,
885+ ) -> notify:: Result < Self > {
886+ Ok ( Self {
887+ fail_path : PathBuf :: from ( "bad" ) ,
888+ } )
889+ }
890+
891+ fn watch ( & mut self , path : & Path , _recursive_mode : RecursiveMode ) -> notify:: Result < ( ) > {
892+ if path == self . fail_path {
893+ Err ( Error :: path_not_found ( ) )
894+ } else {
895+ Ok ( ( ) )
896+ }
897+ }
898+
899+ fn unwatch ( & mut self , path : & Path ) -> notify:: Result < ( ) > {
900+ if path == self . fail_path {
901+ Err ( Error :: path_not_found ( ) )
902+ } else {
903+ Ok ( ( ) )
904+ }
905+ }
906+
907+ fn kind ( ) -> WatcherKind {
908+ WatcherKind :: NullWatcher
909+ }
910+ }
911+
802912 #[ rstest]
803913 fn state (
804914 #[ values(
@@ -1014,4 +1124,89 @@ mod tests {
10141124 . expect ( "No event" )
10151125 . expect ( "error" ) ;
10161126 }
1127+
1128+ #[ test]
1129+ fn update_paths ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1130+ let dir1 = tempdir ( ) ?;
1131+ let dir2 = tempdir ( ) ?;
1132+
1133+ // set up the watcher
1134+ let ( tx, rx) = std:: sync:: mpsc:: channel ( ) ;
1135+ let mut debouncer = new_debouncer ( Duration :: from_millis ( 10 ) , None , tx) ?;
1136+ debouncer. update_paths ( [
1137+ PathOp :: watch_recursive ( dir1. path ( ) ) ,
1138+ PathOp :: watch_recursive ( dir2. path ( ) ) ,
1139+ ] ) ?;
1140+
1141+ // create a new file
1142+ let file_path1 = dir1. path ( ) . join ( "file.txt" ) ;
1143+ let file_path2 = dir2. path ( ) . join ( "file.txt" ) ;
1144+ fs:: write ( & file_path1, b"Lorem ipsum1" ) ?;
1145+ fs:: write ( & file_path2, b"Lorem ipsum1" ) ?;
1146+
1147+ println ! (
1148+ "waiting for events at {:?} and {:?}" ,
1149+ file_path1, file_path2
1150+ ) ;
1151+
1152+ // wait for up to 10 seconds for the create event, ignore all other events
1153+ let deadline = Instant :: now ( ) + Duration :: from_secs ( 10 ) ;
1154+ let mut received = ( false , false ) ;
1155+ while deadline > Instant :: now ( ) {
1156+ let events = rx
1157+ . recv_timeout ( deadline - Instant :: now ( ) )
1158+ . expect ( "did not receive expected event" )
1159+ . expect ( "received an error" ) ;
1160+
1161+ for event in events {
1162+ println ! ( "event {event:?}" ) ;
1163+ if event. event . paths == vec ! [ file_path1. clone( ) ]
1164+ || event. event . paths == vec ! [ file_path1. canonicalize( ) ?]
1165+ {
1166+ received. 0 = true ;
1167+ }
1168+
1169+ if event. event . paths == vec ! [ file_path2. clone( ) ]
1170+ || event. event . paths == vec ! [ file_path2. canonicalize( ) ?]
1171+ {
1172+ received. 1 = true ;
1173+ }
1174+
1175+ if received == ( true , true ) {
1176+ return Ok ( ( ) ) ;
1177+ }
1178+ }
1179+ }
1180+
1181+ panic ! ( "did not receive expected event" ) ;
1182+ }
1183+
1184+ #[ test]
1185+ fn update_paths_error_does_not_add_failed_root ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1186+ let mut debouncer = new_debouncer_opt :: < _ , FailingWatcher , NoCache > (
1187+ Duration :: from_millis ( 20 ) ,
1188+ Some ( Duration :: from_millis ( 5 ) ) ,
1189+ |_| { } ,
1190+ NoCache :: new ( ) ,
1191+ notify:: Config :: default ( ) ,
1192+ ) ?;
1193+
1194+ let err = debouncer
1195+ . update_paths ( [
1196+ PathOp :: watch_recursive ( "ok1" ) ,
1197+ PathOp :: watch_recursive ( "bad" ) ,
1198+ PathOp :: watch_recursive ( "ok2" ) ,
1199+ ] )
1200+ . unwrap_err ( ) ;
1201+ assert ! ( err. origin. is_some( ) ) ;
1202+ assert_eq ! ( err. remaining. len( ) , 1 ) ;
1203+
1204+ let roots = debouncer. data . lock ( ) . unwrap ( ) . roots . clone ( ) ;
1205+ assert_eq ! (
1206+ roots,
1207+ vec![ ( PathBuf :: from( "ok1" ) , RecursiveMode :: Recursive ) ]
1208+ ) ;
1209+
1210+ Ok ( ( ) )
1211+ }
10171212}
0 commit comments