@@ -153,7 +153,7 @@ struct Queue {
153153
154154impl Queue {
155155 fn was_created ( & self ) -> bool {
156- self . events . front ( ) . map_or ( false , |event| {
156+ self . events . front ( ) . is_some_and ( |event| {
157157 matches ! (
158158 event. kind,
159159 EventKind :: Create ( _) | EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: To ) )
@@ -162,7 +162,7 @@ impl Queue {
162162 }
163163
164164 fn was_removed ( & self ) -> bool {
165- self . events . front ( ) . map_or ( false , |event| {
165+ self . events . front ( ) . is_some_and ( |event| {
166166 matches ! (
167167 event. kind,
168168 EventKind :: Remove ( _) | EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: From ) )
@@ -171,9 +171,48 @@ impl Queue {
171171 }
172172}
173173
174+ #[ derive( Debug ) ]
175+ pub struct BlockEntry {
176+ pub blocker_path : PathBuf ,
177+ pub blocker_time : Instant ,
178+ pub blockee_path : PathBuf ,
179+ }
180+
181+ #[ derive( Debug , Default ) ]
182+ pub struct BlockManager {
183+ entries : Vec < BlockEntry > ,
184+ }
185+
186+ impl BlockManager {
187+ pub fn new ( ) -> BlockManager {
188+ BlockManager {
189+ entries : Vec :: new ( ) ,
190+ }
191+ }
192+
193+ pub fn add_blocker ( & mut self , entry : BlockEntry ) {
194+ self . entries . push ( entry) ;
195+ }
196+
197+ pub fn remove_blocker ( & mut self , path : & PathBuf , time : Instant ) {
198+ self . entries
199+ . retain ( |entry| entry. blocker_path != * path || entry. blocker_time != time) ;
200+ }
201+
202+ pub fn is_blocked_by ( & self , path : & PathBuf ) -> Option < ( & PathBuf , Instant ) > {
203+ for entry in & self . entries {
204+ if entry. blockee_path == * path {
205+ return Some ( ( & entry. blocker_path , entry. blocker_time ) ) ;
206+ }
207+ }
208+ None
209+ }
210+ }
211+
174212#[ derive( Debug ) ]
175213pub ( crate ) struct DebounceDataInner < T > {
176214 queues : HashMap < PathBuf , Queue > ,
215+ blocking : BlockManager ,
177216 roots : Vec < ( PathBuf , RecursiveMode ) > ,
178217 cache : T ,
179218 rename_event : Option < ( DebouncedEvent , Option < FileId > ) > ,
@@ -186,6 +225,7 @@ impl<T: FileIdCache> DebounceDataInner<T> {
186225 pub ( crate ) fn new ( cache : T , timeout : Duration ) -> Self {
187226 Self {
188227 queues : HashMap :: new ( ) ,
228+ blocking : BlockManager :: new ( ) ,
189229 roots : Vec :: new ( ) ,
190230 cache,
191231 rename_event : None ,
@@ -195,11 +235,17 @@ impl<T: FileIdCache> DebounceDataInner<T> {
195235 }
196236 }
197237
238+ fn contains_event ( & self , path : & PathBuf , time : Instant ) -> bool {
239+ self . queues
240+ . get ( path)
241+ . is_some_and ( |queue| queue. events . iter ( ) . any ( |event| event. time == time) )
242+ }
243+
198244 /// Retrieve a vec of debounced events, removing them if not continuous
199245 pub fn debounced_events ( & mut self ) -> Vec < DebouncedEvent > {
200246 let now = Instant :: now ( ) ;
201- let mut events_expired = Vec :: with_capacity ( self . queues . len ( ) ) ;
202- let mut queues_remaining = HashMap :: with_capacity ( self . queues . len ( ) ) ;
247+ let events_count = self . queues . values ( ) . map ( |queue| queue . events . len ( ) ) . sum ( ) ;
248+ let mut events_expired = Vec :: with_capacity ( events_count ) ;
203249
204250 if let Some ( event) = self . rescan_event . take ( ) {
205251 if now. saturating_duration_since ( event. time ) >= self . timeout {
@@ -210,48 +256,62 @@ impl<T: FileIdCache> DebounceDataInner<T> {
210256 }
211257 }
212258
213- // TODO: perfect fit for drain_filter https://github.com/rust-lang/rust/issues/59618
214- for ( path, mut queue) in self . queues . drain ( ) {
215- let mut kind_index = HashMap :: new ( ) ;
216-
217- while let Some ( event) = queue. events . pop_front ( ) {
218- if now. saturating_duration_since ( event. time ) >= self . timeout {
219- // remove previous event of the same kind
220- if let Some ( idx) = kind_index. get ( & event. kind ) . copied ( ) {
221- events_expired. remove ( idx) ;
222-
223- kind_index. values_mut ( ) . for_each ( |i| {
224- if * i > idx {
225- * i -= 1
226- }
227- } )
228- }
259+ let mut kind_index: HashMap < PathBuf , HashMap < EventKind , usize > > = HashMap :: new ( ) ;
229260
230- kind_index. insert ( event. kind , events_expired. len ( ) ) ;
261+ while let Some ( path) = self
262+ . queues
263+ // iterate over all queues
264+ . iter ( )
265+ // get the first event of every queue
266+ . filter_map ( |( path, queue) | queue. events . front ( ) . map ( |event| ( path, event. time ) ) )
267+ // filter out all blocked events
268+ . filter ( |( path, _) | {
269+ self . blocking
270+ . is_blocked_by ( path)
271+ . map_or ( true , |( path, time) | !self . contains_event ( path, time) )
272+ } )
273+ // get the event with the earliest timestamp
274+ . min_by_key ( |( _, time) | * time)
275+ // get the path of the event
276+ . map ( |( path, _) | path. clone ( ) )
277+ {
278+ let event = self
279+ . queues
280+ . get_mut ( & path)
281+ . unwrap ( )
282+ . events
283+ . pop_front ( )
284+ . unwrap ( ) ;
231285
232- events_expired. push ( event) ;
233- } else {
234- queue. events . push_front ( event) ;
235- break ;
286+ if now. saturating_duration_since ( event. time ) >= self . timeout {
287+ // remove previous event of the same kind
288+ let kind_index = kind_index. entry ( path. clone ( ) ) . or_default ( ) ;
289+ if let Some ( idx) = kind_index. get ( & event. kind ) . copied ( ) {
290+ events_expired. remove ( idx) ;
291+
292+ kind_index. values_mut ( ) . for_each ( |i| {
293+ if * i > idx {
294+ * i -= 1
295+ }
296+ } )
236297 }
237- }
298+ kind_index . insert ( event . kind , events_expired . len ( ) ) ;
238299
239- if !queue. events . is_empty ( ) {
240- queues_remaining. insert ( path, queue) ;
300+ self . blocking . remove_blocker ( & path, event. time ) ;
301+
302+ events_expired. push ( event) ;
303+ } else {
304+ self . queues . get_mut ( & path) . unwrap ( ) . events . push_front ( event) ;
305+
306+ break ;
241307 }
242308 }
243309
244- self . queues = queues_remaining ;
310+ self . queues . retain ( |_ , queue| !queue . events . is_empty ( ) ) ;
245311
246- // order events for different files chronologically, but keep the order of events for the same file
247- events_expired. sort_by ( |event_a, event_b| {
248- // use the last path because rename events are emitted for the target path
249- if event_a. paths . last ( ) == event_b. paths . last ( ) {
250- std:: cmp:: Ordering :: Equal
251- } else {
252- event_a. time . cmp ( & event_b. time )
253- }
254- } ) ;
312+ if self . queues . is_empty ( ) {
313+ self . blocking . entries . clear ( ) ;
314+ }
255315
256316 events_expired
257317 }
@@ -426,18 +486,6 @@ impl<T: FileIdCache> DebounceDataInner<T> {
426486 source_queue. events . remove ( remove_index) ;
427487 }
428488
429- // split off remove or move out event and add it back to the events map
430- if source_queue. was_removed ( ) {
431- let event = source_queue. events . pop_front ( ) . unwrap ( ) ;
432-
433- self . queues . insert (
434- event. paths [ 0 ] . clone ( ) ,
435- Queue {
436- events : [ event] . into ( ) ,
437- } ,
438- ) ;
439- }
440-
441489 // update paths
442490 for e in & mut source_queue. events {
443491 e. paths = vec ! [ event. paths[ 0 ] . clone( ) ] ;
@@ -456,7 +504,12 @@ impl<T: FileIdCache> DebounceDataInner<T> {
456504 }
457505
458506 if let Some ( target_queue) = self . queues . get_mut ( & event. paths [ 0 ] ) {
459- if !target_queue. was_created ( ) {
507+ if target_queue. was_removed ( ) {
508+ let event = target_queue. events . pop_front ( ) . unwrap ( ) ;
509+ source_queue. events . push_front ( event) ;
510+ }
511+
512+ if !target_queue. was_created ( ) && !source_queue. was_removed ( ) {
460513 let mut remove_event = DebouncedEvent {
461514 event : Event {
462515 kind : EventKind :: Remove ( RemoveKind :: Any ) ,
@@ -474,6 +527,8 @@ impl<T: FileIdCache> DebounceDataInner<T> {
474527 } else {
475528 self . queues . insert ( event. paths [ 0 ] . clone ( ) , source_queue) ;
476529 }
530+
531+ self . find_blocked_events ( & event. paths [ 0 ] ) ;
477532 }
478533
479534 fn push_remove_event ( & mut self , event : Event , time : Instant ) {
@@ -519,6 +574,25 @@ impl<T: FileIdCache> DebounceDataInner<T> {
519574 ) ;
520575 }
521576 }
577+
578+ fn find_blocked_events ( & mut self , path : & Path ) {
579+ for queue in self . queues . values_mut ( ) {
580+ for event in & mut queue. events {
581+ if matches ! (
582+ event. event. kind,
583+ EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: Both ) )
584+ ) && event. event . paths [ 0 ] == path
585+ {
586+ self . blocking . add_blocker ( BlockEntry {
587+ blocker_path : event. event . paths [ 1 ] . clone ( ) ,
588+ blocker_time : event. time ,
589+ blockee_path : path. to_path_buf ( ) ,
590+ } ) ;
591+ break ;
592+ }
593+ }
594+ }
595+ }
522596}
523597
524598/// Debouncer guard, stops the debouncer on drop.
@@ -756,6 +830,11 @@ mod tests {
756830 "add_remove_parent_event_after_remove_child_event" ,
757831 "add_errors" ,
758832 "emit_continuous_modify_content_events" ,
833+ "emit_create_event_after_safe_save_and_backup_override" ,
834+ "emit_create_event_after_safe_save_and_backup_rotation_twice" ,
835+ "emit_create_event_after_safe_save_and_backup_rotation" ,
836+ "emit_create_event_after_safe_save_and_double_move" ,
837+ "emit_create_event_after_safe_save_and_double_move_and_recreate" ,
759838 "emit_events_in_chronological_order" ,
760839 "emit_events_with_a_prepended_rename_event" ,
761840 "emit_close_events_only_once" ,
0 commit comments