char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1use super :: paths::{ resolve, tree}; 2use super :: * ; 3 4use std:: collections:: HashSet ; 5use tokio:: sync:: Notify ; 6 7const MAX_ROOTS : usize =128 ; 8const MAX_QUEUED_EVENTS : usize =256 ; 9const MAX_QUEUED_PATHS : usize =4096 ; 10 11const CREATE : u8 =0 ; 12const MODIFY : u8 =1 ; 13const REMOVE : u8 =2 ; 14const RENAME : u8 =3 ; 15const RESCAN : u8 =4 ; 16 17type WatchEvent =( u8 , Vec < String >); 18 19# [ derive ( Clone )] 20struct Root { 21target : PathTarget , 22path : PathBuf , 23directory : bool , 24recursive : bool , 25// A file root is watched through its parent directory, so replacing the 26// file (the usual atomic save) or recreating it stays observable at the 27// same path instead of following the old inode. 28watch_path : PathBuf , 29} 30 31# [ derive ( Clone , Debug , PartialEq , Eq )] 32struct QueuedEvent { 33kind : u8 , 34paths : Vec < PathBuf >, 35ends : Vec < usize >, 36} 37 38# [ derive ( Default )] 39struct WatchState { 40queue : VecDeque < QueuedEvent >, 41closed : bool , 42terminal : Option <( io:: ErrorKind , String )>, 43lossy : bool , 44live : Vec < bool >, 45// Root endings discarded with an overflowed queue, applied when the 46// replacement rescan is delivered instead. 47pending_ends : Vec < usize >, 48epoch : u32 , 49} 50 51struct Shared { 52caps : Arc < Capabilities >, 53roots : Vec < Root >, 54state : Mutex < WatchState >, 55notify : Notify , 56} 57 58struct WatchResource { 59shared : Arc < Shared >, 60# [ cfg_attr ( 61not ( any ( 62target_os = "linux" , 63target_os = "android" , 64target_os = "macos" , 65target_os = "windows" 66)), 67allow ( dead_code ) 68)] 69native : Mutex < Option < notify:: RecommendedWatcher >>, 70} 71 72impl WatchResource { 73fn close ( & self ) { 74{ 75let mut state =self . shared . state . lock (). unwrap (); 76 state. closed =true ; 77 state. queue . clear (); 78 state. lossy =false ; 79 state. epoch = state. epoch . wrapping_add ( 1 ); 80} 81self . shared . notify . notify_waiters (); 82let native =self . native . lock (). unwrap (). take (); 83drop ( native); 84self . shared . notify . notify_waiters (); 85} 86} 87 88struct Watchers { 89caps : Arc < Capabilities >, 90resources : Mutex < HashMap < u64 , Arc < WatchResource >>>, 91next : AtomicU64 , 92} 93 94impl Drop for Watchers { 95fn drop ( & mut self ) { 96for resourcein self 97. resources 98. get_mut () 99. unwrap () 100. drain () 101. map ( |( _, resource) | resource) 102{ 103 resource. close (); 104} 105} 106} 107 108impl Watchers { 109fn resource ( & self , id : u64 ) -> io:: Result < Arc < WatchResource >> { 110self . resources 111. lock () 112. unwrap () 113. get ( & id) 114. cloned () 115. ok_or_else ( bad_resource) 116} 117 118fn create ( & self , labels : Vec < String >, recursive : bool ) -> io:: Result < u64 > { 119if labels. is_empty () || labels. len () >MAX_ROOTS { 120return Err ( io:: Error :: new ( 121 io:: ErrorKind :: InvalidInput , 122"Expected between one and 128 filesystem watch paths" , 123)); 124} 125let roots =roots ( & self . caps , labels, recursive) ?; 126let live =vec! [ true ; roots. len ()]; 127let shared =Arc :: new ( Shared { 128caps : self . caps . clone (), 129 roots, 130state : Mutex :: new ( WatchState { live, ..WatchState :: default () }), 131notify : Notify :: new (), 132}); 133let native =register ( & shared) ?; 134check_roots_unchanged ( & shared) ?; 135let id =self 136. next 137. fetch_update ( Ordering :: AcqRel , Ordering :: Acquire , |id|{ 138( id <=super :: MAX_ID ). then_some ( id +1 ) 139}) 140. map_err ( |_| io:: Error :: other ( "Filesystem watcher identifiers exhausted" )) ?; 141self . resources . lock (). unwrap (). insert ( 142 id, 143Arc :: new ( WatchResource { 144 shared, 145native : Mutex :: new ( Some ( native)), 146}), 147); 148Ok ( id) 149} 150 151fn close ( & self , id : u64 , quiet : bool ) -> io:: Result < bool > { 152let resource =self . resources . lock (). unwrap (). remove ( & id); 153match resource{ 154Some ( resource) =>{ 155 resource. close (); 156Ok ( true ) 157} 158None if quiet =>Ok ( true ), 159None =>Err ( bad_resource ()), 160} 161} 162} 163 164fn roots ( caps : & Capabilities , labels : Vec < String >, recursive : bool ) -> io:: Result < Vec < Root >> { 165let mut roots =Vec :: new (); 166let mut seen =HashSet :: new (); 167for labelin labels{ 168let target =resolve ( caps, & label, false , false ) ?; 169let path = target. as_path (). to_owned (); 170if !seen. insert ( path. clone ()) { 171continue ; 172} 173let ( directory, name) =parent ( & path) ?; 174let metadata = directory. symlink_metadata ( & name) ?; 175if metadata. is_symlink () { 176return Err ( io:: Error :: new ( 177 io:: ErrorKind :: WouldBlock , 178"Filesystem watch root changed while it was registered" , 179)); 180} 181let ( directory, watch_path) =if metadata. is_dir () { 182( true , path. clone ()) 183} else if metadata. is_file () { 184let parent = path. parent (). ok_or_else ( ||{ 185 io:: Error :: new ( io:: ErrorKind :: InvalidInput , "Watched file has no parent" ) 186}) ?; 187( false , parent. to_owned ()) 188} else { 189return Err ( io:: Error :: new ( 190 io:: ErrorKind :: Unsupported , 191"Only regular files and directories can be watched" , 192)); 193}; 194if directory && recursive{ 195tree ( caps, & target, & label, false ) ?; 196} 197 roots. push ( Root { 198 target, 199 path, 200 directory, 201recursive : directory && recursive, 202 watch_path, 203}); 204} 205Ok ( roots) 206} 207 208fn check_roots_unchanged ( shared : & Shared ) -> io:: Result <()> { 209for rootin & shared. roots { 210let current =PathTarget :: for_creation ( & root. path ). map_err ( |_|{ 211 io:: Error :: new ( 212 io:: ErrorKind :: WouldBlock , 213"Filesystem watch root changed while it was registered" , 214) 215}) ?; 216if current. as_path () != root. path { 217return Err ( io:: Error :: new ( 218 io:: ErrorKind :: WouldBlock , 219"Filesystem watch root changed while it was registered" , 220)); 221} 222let ( directory, name) =parent ( & root. path ) ?; 223let metadata = directory. symlink_metadata ( & name) ?; 224if metadata. is_symlink () || metadata. is_dir () != root. directory { 225return Err ( io:: Error :: new ( 226 io:: ErrorKind :: WouldBlock , 227"Filesystem watch root changed while it was registered" , 228)); 229} 230} 231Ok (()) 232} 233 234# [ cfg ( any ( 235target_os = "linux" , 236target_os = "android" , 237target_os = "macos" , 238target_os = "windows" 239))] 240fn register ( shared : & Arc < Shared >) -> io:: Result < notify:: RecommendedWatcher > { 241use notify:: Watcher ; 242 243let weak =Arc :: downgrade ( shared); 244let handler =move |result : notify:: Result < notify:: Event > |{ 245if let Some ( shared) = weak. upgrade () { 246 shared. handle ( result); 247} 248}; 249let config = notify:: Config :: default (). with_follow_symlinks ( false ); 250let mut watcher = 251 <notify:: RecommendedWatcher as Watcher >:: new ( handler, config). map_err ( watch_error) ?; 252// Roots can share a registration path (a watched file and its parent 253// directory); the recursive registration must win when both are present. 254let mut registrations: HashMap < PathBuf , bool > =HashMap :: new (); 255for rootin & shared. roots { 256let recursive = root. directory && root. recursive ; 257 registrations 258. entry ( root. watch_path . clone ()) 259. and_modify ( |existing|* existing |= recursive) 260. or_insert ( recursive); 261} 262for ( path, recursive) in registrations{ 263let mode =if recursive{ 264 notify:: RecursiveMode :: Recursive 265} else { 266 notify:: RecursiveMode :: NonRecursive 267}; 268 watcher. watch ( & path, mode). map_err ( watch_error) ?; 269} 270Ok ( watcher) 271} 272 273# [ cfg ( not ( any ( 274target_os = "linux" , 275target_os = "android" , 276target_os = "macos" , 277target_os = "windows" 278)))] 279fn register ( _shared : & Arc < Shared >) -> io:: Result < notify:: RecommendedWatcher > { 280Err ( io:: Error :: new ( 281 io:: ErrorKind :: Unsupported , 282"Filesystem watching is not supported on this platform" , 283)) 284} 285 286fn watch_error ( error : notify:: Error ) -> io:: Error { 287let message = error. to_string (); 288match error. kind { 289 notify:: ErrorKind :: PathNotFound | notify:: ErrorKind :: WatchNotFound =>{ 290 io:: Error :: new ( io:: ErrorKind :: NotFound , message) 291} 292 notify:: ErrorKind :: Io ( inner) => io:: Error :: new ( inner. kind (), message), 293 notify:: ErrorKind :: InvalidConfig ( _) => io:: Error :: new ( io:: ErrorKind :: InvalidInput , message), 294 notify:: ErrorKind :: MaxFilesWatch => io:: Error :: new ( io:: ErrorKind :: WouldBlock , message), 295 notify:: ErrorKind :: Generic ( _) => io:: Error :: other ( message), 296} 297} 298 299impl Shared { 300fn handle ( self : & Arc < Self >, result : notify:: Result < notify:: Event >) { 301match result{ 302Ok ( event) =>{ 303if event. need_rescan () { 304self . enqueue ( QueuedEvent { 305kind : RESCAN , 306paths : self . event_roots ( & event. paths ), 307ends : Vec :: new (), 308}); 309return ; 310} 311let Some ( kind) =map_kind ( & event. kind , event. paths . len ()) else { 312return ; 313}; 314if event. paths . is_empty () { 315return ; 316} 317let mut paths =Vec :: new (); 318let mut unknown =false ; 319for pathin event. paths { 320if self . root_index ( & path). is_some () || kind ==RENAME { 321 paths. push ( path); 322} else if self . in_scope ( & path) { 323// The backend also sees siblings of a watched file and, 324// on some platforms, descendants of a non-recursive 325// directory. Those are in scope but not reportable. 326continue ; 327} else { 328 unknown =true ; 329} 330} 331if unknown && kind !=RENAME { 332self . enqueue ( QueuedEvent { 333kind : RESCAN , 334paths : self . roots . iter (). map ( |root| root. path . clone ()). collect (), 335ends : Vec :: new (), 336}); 337return ; 338} 339if paths. is_empty () { 340return ; 341} 342let ends =self . event_ends ( kind, & paths); 343self . enqueue ( QueuedEvent { kind, paths, ends}); 344} 345Err ( error) =>{ 346let error =watch_error ( error); 347let mut state =self . state . lock (). unwrap (); 348if !state. closed && state. terminal . is_none () { 349 state. terminal =Some (( error. kind (), error. to_string ())); 350drop ( state); 351self . notify . notify_waiters (); 352} 353} 354} 355} 356 357fn event_roots ( & self , paths : & [ PathBuf ]) ->Vec < PathBuf > { 358let mut roots =Vec :: new (); 359for pathin paths{ 360if let Some ( index) =self . root_index ( path) { 361let root =& self . roots [ index]; 362if !roots. contains ( & root. path ) { 363 roots. push ( root. path . clone ()); 364} 365} 366} 367if roots. is_empty () { 368 roots =self . roots . iter (). map ( |root| root. path . clone ()). collect (); 369} 370 roots 371} 372 373fn event_ends ( & self , kind : u8 , paths : & [ PathBuf ]) ->Vec < usize > { 374let mut ends =Vec :: new (); 375if kind !=REMOVE && kind !=RENAME { 376return ends; 377} 378for ( index, root) in self . roots . iter (). enumerate () { 379if !root. directory { 380// A watched file is anchored at its path, not its inode, and 381// may be removed and recreated without ending the watch. 382continue ; 383} 384let ends_at_root =if kind ==REMOVE { 385 paths. iter (). any ( |path| path ==& root. path ) 386} else { 387 paths. first () ==Some ( & root. path ) 388}; 389if ends_at_root{ 390 ends. push ( index); 391} 392} 393 ends 394} 395 396fn root_index ( & self , path : & Path ) ->Option < usize > { 397self . roots 398. iter () 399. enumerate () 400. filter ( |( _, root) |{ 401if !root. directory { 402return path == root. path ; 403} 404if path == root. path { 405return true ; 406} 407let Ok ( relative) = path. strip_prefix ( & root. path ) else { 408return false ; 409}; 410 root. recursive || relative. components (). count () ==1 411}) 412. max_by_key ( |( _, root) | root. path . components (). count ()) 413. map ( |( index, _) | index) 414} 415 416fn in_scope ( & self , path : & Path ) ->bool { 417self . roots 418. iter () 419. any ( |root| path. starts_with ( & root. watch_path )) 420} 421 422fn enqueue ( & self , event : QueuedEvent ) { 423let mut state =self . state . lock (). unwrap (); 424if state. closed { 425return ; 426} 427if state. lossy { 428 state. pending_ends . extend ( event. ends . iter (). copied ()); 429return ; 430} 431if event. kind ==RESCAN { 432if let Some ( existing) = state. queue . iter_mut (). find ( |queued| queued. kind ==RESCAN ) { 433for pathin event. paths { 434if !existing. paths . contains ( & path) { 435 existing. paths . push ( path); 436} 437} 438drop ( state); 439self . notify . notify_waiters (); 440return ; 441} 442} else if event. kind !=RENAME 443 && state. queue . back (). is_some_and ( |last|{ 444 last. kind == event. kind && last. ends . is_empty () && event. ends . is_empty () 445}) 446{ 447let queued_paths = state 448. queue 449. iter () 450. map ( |queued| queued. paths . len ()) 451. sum ::< usize >(); 452let new_paths ={ 453let last = state. queue . back (). unwrap (); 454 event 455. paths 456. iter () 457. filter ( |path| !last. paths . contains ( path)) 458. count () 459}; 460if queued_paths + new_paths <=MAX_QUEUED_PATHS { 461let last = state. queue . back_mut (). unwrap (); 462for pathin event. paths { 463if !last. paths . contains ( & path) { 464 last. paths . push ( path); 465} 466} 467drop ( state); 468self . notify . notify_waiters (); 469return ; 470} 471} 472let queued_paths = state 473. queue 474. iter () 475. map ( |queued| queued. paths . len ()) 476. sum ::< usize >(); 477if state. queue . len () >=MAX_QUEUED_EVENTS 478 || queued_paths + event. paths . len () >MAX_QUEUED_PATHS 479{ 480let paths =self 481. roots 482. iter () 483. enumerate () 484. filter ( |( index, _) | state. live . get ( * index). copied (). unwrap_or ( false )) 485. map ( |( _, root) | root. path . clone ()) 486. collect ::< Vec < _ >>(); 487let mut discarded: Vec < usize > = state 488. queue 489. drain ( ..) 490. flat_map ( |queued| queued. ends ) 491. collect (); 492 discarded. extend ( event. ends . iter (). copied ()); 493 state. pending_ends . extend ( discarded); 494 state. queue . push_back ( QueuedEvent { 495kind : RESCAN , 496 paths, 497ends : Vec :: new (), 498}); 499 state. lossy =true ; 500} else { 501 state. queue . push_back ( event); 502} 503drop ( state); 504self . notify . notify_waiters (); 505} 506 507async fn next ( & self ) -> io:: Result < Option < WatchEvent >> { 508loop { 509let notified =self . notify . notified (); 510 tokio:: pin!( notified); 511 notified. as_mut (). enable (); 512let candidate ={ 513let mut state =self . state . lock (). unwrap (); 514if state. closed { 515return Ok ( None ); 516} 517if let Some ( mut event) = state. queue . pop_front () { 518if event. kind ==RESCAN { 519 state. lossy =false ; 520 event. ends . append ( & mut state. pending_ends ); 521} 522Some ( event) 523} else if let Some (( kind, message)) = state. terminal . take () { 524 state. closed =true ; 525return Err ( io:: Error :: new ( kind, message)); 526} else if state. live . iter (). all ( |live| !* live) { 527 state. closed =true ; 528return Ok ( None ); 529} else { 530None 531} 532}; 533if let Some ( candidate) = candidate{ 534let ends = candidate. ends . clone (); 535let event =self . authorize ( candidate); 536{ 537let mut state =self . state . lock (). unwrap (); 538for indexin ends{ 539if let Some ( live) = state. live . get_mut ( index) { 540* live =false ; 541} 542} 543} 544if let Some ( event) = event{ 545return Ok ( Some ( event)); 546} 547if !self . any_root_authorized () { 548let mut state =self . state . lock (). unwrap (); 549 state. closed =true ; 550return Err ( denied ( 551& self 552. roots 553. first () 554. map ( |root| root. path . display (). to_string ()) 555. unwrap_or_default (), 556false , 557)); 558} 559continue ; 560} 561 notified. await ; 562} 563} 564 565fn authorize ( & self , event : QueuedEvent ) ->Option < WatchEvent > { 566if event. kind ==RESCAN { 567let mut paths =Vec :: new (); 568for pathin event. paths { 569let Some ( index) =self . root_index ( & path) else { 570continue ; 571}; 572if !self . state . lock (). unwrap (). live [ index] { 573continue ; 574} 575let root =& self . roots [ index]; 576if self . caps . check_read ( & root. target ). is_err () { 577continue ; 578} 579if let Ok ( path) = path. into_os_string (). into_string () 580 && !paths. contains ( & path) 581{ 582 paths. push ( path); 583} 584} 585return ( !paths. is_empty ()). then_some (( RESCAN , paths)); 586} 587 588let mut paths =Vec :: new (); 589for pathin & event. paths { 590if !self . path_authorized ( path) { 591continue ; 592} 593match path. clone (). into_os_string (). into_string () { 594Ok ( path) =>{ 595if !paths. contains ( & path) { 596 paths. push ( path); 597} 598} 599Err ( _) =>return self . rescan_for ( event. paths . first ()), 600} 601} 602if paths. is_empty () { 603return None ; 604} 605let kind =if event. kind ==RENAME && paths. len () ==1 { 606let retained =& paths[ 0 ]; 607if event. paths . first (). and_then ( |from| from. to_str ()) ==Some ( retained. as_str ()) { 608REMOVE 609} else { 610CREATE 611} 612} else { 613 event. kind 614}; 615Some (( kind, paths)) 616} 617 618fn rescan_for ( & self , _path : Option < & PathBuf >) ->Option < WatchEvent > { 619let mut paths =Vec :: new (); 620for rootin & self . roots { 621if self . caps . check_read ( & root. target ). is_err () { 622continue ; 623} 624if let Ok ( path) = root. path . clone (). into_os_string (). into_string () { 625 paths. push ( path); 626} 627} 628( !paths. is_empty ()). then_some (( RESCAN , paths)) 629} 630 631fn path_authorized ( & self , path : & Path ) ->bool { 632let Some ( index) =self . root_index ( path) else { 633return false ; 634}; 635let root =& self . roots [ index]; 636if !self . state . lock (). unwrap (). live [ index] { 637return false ; 638} 639let mut target = root. target . clone (); 640if path != root. path { 641let Ok ( relative) = path. strip_prefix ( & root. path ) else { 642return false ; 643}; 644for componentin relative. components () { 645let Component :: Normal ( name) = componentelse { 646return false ; 647}; 648let Ok ( child) = target. child ( name) else { 649return false ; 650}; 651 target = child; 652} 653} 654self . caps . check_read ( & target). is_ok () 655} 656 657fn any_root_authorized ( & self ) ->bool { 658let state =self . state . lock (). unwrap (); 659self . roots . iter (). enumerate (). any ( |( index, root) |{ 660 state. live . get ( index). copied (). unwrap_or ( false ) 661 &&self . caps . check_read ( & root. target ). is_ok () 662}) 663} 664 665async fn wait_epoch ( & self , epoch : u32 ) ->bool { 666loop { 667let notified =self . notify . notified (); 668 tokio:: pin!( notified); 669 notified. as_mut (). enable (); 670{ 671let state =self . state . lock (). unwrap (); 672if state. closed || state. epoch != epoch{ 673return false ; 674} 675} 676 notified. await ; 677} 678} 679 680fn bump_epoch ( & self ) ->u32 { 681let epoch ={ 682let mut state =self . state . lock (). unwrap (); 683 state. epoch = state. epoch . wrapping_add ( 1 ); 684 state. epoch 685}; 686self . notify . notify_waiters (); 687 epoch 688} 689} 690 691fn map_kind ( kind : & notify:: EventKind , path_count : usize ) ->Option < u8 > { 692use notify:: event::{ EventKind , ModifyKind , RenameMode }; 693 694match kind{ 695EventKind :: Access ( _) =>None , 696EventKind :: Create ( _) =>Some ( CREATE ), 697EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: Both )) if path_count ==2 =>Some ( RENAME ), 698EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: From )) =>Some ( REMOVE ), 699EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: To )) =>Some ( CREATE ), 700EventKind :: Modify ( _) =>Some ( MODIFY ), 701EventKind :: Remove ( _) =>Some ( REMOVE ), 702EventKind :: Any |EventKind :: Other =>Some ( MODIFY ), 703} 704} 705 706pub ( super ) fn ops ( files : Arc < Files >) ->Vec < Op > { 707let watchers =Arc :: new ( Watchers { 708caps : files. caps . clone (), 709resources : Mutex :: new ( HashMap :: new ()), 710next : AtomicU64 :: new ( 1 ), 711}); 712let create = watchers. clone (); 713let next = watchers. clone (); 714let next_unref = watchers. clone (); 715let keepalive = watchers. clone (); 716let reference = watchers. clone (); 717let close = watchers. clone (); 718vec! [ 719Op :: new ( 720"op_file_watch" , 721move |( paths, recursive): ( Vec < String >, bool ) |{ 722Ok ::< _ , Infallible >( response ( create. create ( paths, recursive))) 723}, 724), 725Op :: asynchronous ( "op_file_watch_next" , move |id : u64 |{ 726let resource = next. resource ( id); 727async move { Ok ::< _ , Infallible >( receive_watch ( resource). await ) } 728}), 729Op :: asynchronous_unreferenced ( "op_file_watch_next_unref" , move |id : u64 |{ 730let resource = next_unref. resource ( id); 731async move { Ok ::< _ , Infallible >( receive_watch ( resource). await ) } 732}), 733Op :: asynchronous ( "op_file_watch_keepalive" , move |( id, epoch): ( u64 , u32 ) |{ 734let resource = keepalive. resource ( id); 735async move { 736let result =match resource{ 737Ok ( resource) =>Ok ( resource. shared . wait_epoch ( epoch). await ), 738Err ( error) =>Err ( error), 739}; 740Ok ::< _ , Infallible >( response ( result)) 741} 742}), 743Op :: new ( "op_file_watch_ref" , move |id : u64 |{ 744let result = reference 745. resource ( id) 746. map ( |resource| resource. shared . bump_epoch ()); 747Ok ::< _ , Infallible >( response ( result)) 748}), 749Op :: new ( "op_file_watch_close" , move |( id, quiet): ( u64 , bool ) |{ 750Ok ::< _ , Infallible >( response ( close. close ( id, quiet))) 751}), 752] 753} 754 755async fn receive_watch ( resource : io:: Result < Arc < WatchResource >>) ->Response < Option < WatchEvent >> { 756match resource{ 757Ok ( resource) =>response ( resource. shared . next (). await ), 758Err ( error) =>response ( Err ( error)), 759} 760} 761 762# [ cfg ( test )] 763mod tests{ 764use super :: * ; 765 766# [ test ] 767fn maps_notify_kinds_to_public_kinds () { 768use notify:: event::{ CreateKind , EventKind , ModifyKind , RemoveKind , RenameMode }; 769 770assert_eq! ( 771map_kind ( & EventKind :: Create ( CreateKind :: File ), 1 ), 772Some ( CREATE ) 773); 774assert_eq! ( 775map_kind ( & EventKind :: Remove ( RemoveKind :: File ), 1 ), 776Some ( REMOVE ) 777); 778assert_eq! ( 779map_kind ( & EventKind :: Modify ( ModifyKind :: Any ), 1 ), 780Some ( MODIFY ) 781); 782assert_eq! ( 783map_kind ( & EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: Both )), 2 ), 784Some ( RENAME ) 785); 786assert_eq! ( 787map_kind ( & EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: Both )), 1 ), 788Some ( MODIFY ) 789); 790assert_eq! ( 791map_kind ( & EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: From )), 1 ), 792Some ( REMOVE ) 793); 794assert_eq! ( 795map_kind ( & EventKind :: Modify ( ModifyKind :: Name ( RenameMode :: To )), 1 ), 796Some ( CREATE ) 797); 798} 799 800fn shared ( root : & Path ) ->Arc < Shared > { 801Arc :: new ( Shared { 802caps : Capabilities :: new ( avril_caps:: Initial :: new ( false ), root. to_path_buf ()), 803roots : vec! [ Root { 804target : PathTarget :: resolve ( root). unwrap (), 805path : root. to_path_buf (), 806directory : true , 807recursive : true , 808watch_path : root. to_path_buf (), 809}], 810state : Mutex :: new ( WatchState { 811live : vec! [ true ], 812 ..WatchState :: default () 813}), 814notify : Notify :: new (), 815}) 816} 817 818# [ test ] 819fn coalesces_adjacent_events_without_reordering_kinds () { 820let directory = tempfile:: tempdir (). unwrap (); 821let root = directory. path (); 822let shared =shared ( root); 823 shared. enqueue ( QueuedEvent { 824kind : MODIFY , 825paths : vec! [ root. join ( "a" )], 826ends : Vec :: new (), 827}); 828 shared. enqueue ( QueuedEvent { 829kind : MODIFY , 830paths : vec! [ root. join ( "b" ), root. join ( "a" )], 831ends : Vec :: new (), 832}); 833 shared. enqueue ( QueuedEvent { 834kind : CREATE , 835paths : vec! [ root. join ( "c" )], 836ends : Vec :: new (), 837}); 838let state = shared. state . lock (). unwrap (); 839assert_eq! ( state. queue . len (), 2 ); 840assert_eq! ( state. queue [ 0 ]. paths , [ root. join ( "a" ), root. join ( "b" )]); 841assert_eq! ( state. queue [ 1 ]. kind , CREATE ); 842} 843 844# [ test ] 845fn queue_overflow_collapses_to_one_rescan () { 846let directory = tempfile:: tempdir (). unwrap (); 847let root = directory. path (); 848let shared =shared ( root); 849for indexin 0 ..=MAX_QUEUED_PATHS { 850 shared. enqueue ( QueuedEvent { 851kind : MODIFY , 852paths : vec! [ root. join ( index. to_string ())], 853ends : Vec :: new (), 854}); 855} 856{ 857let state = shared. state . lock (). unwrap (); 858assert! ( state. lossy ); 859assert_eq! ( state. queue . len (), 1 ); 860assert_eq! ( state. queue [ 0 ]. kind , RESCAN ); 861assert_eq! ( state. queue [ 0 ]. paths , [ root. to_path_buf ()]); 862} 863 shared. enqueue ( QueuedEvent { 864kind : CREATE , 865paths : vec! [ root. join ( "late" )], 866ends : Vec :: new (), 867}); 868assert_eq! ( shared. state . lock (). unwrap (). queue . len (), 1 ); 869} 870 871# [ test ] 872fn overflow_preserves_pending_root_ends () { 873let directory = tempfile:: tempdir (). unwrap (); 874let root = directory. path (); 875let shared =shared ( root); 876 shared. enqueue ( QueuedEvent { 877kind : REMOVE , 878paths : vec! [ root. to_path_buf ()], 879ends : vec! [ 0 ], 880}); 881for indexin 0 ..=MAX_QUEUED_PATHS { 882 shared. enqueue ( QueuedEvent { 883kind : MODIFY , 884paths : vec! [ root. join ( index. to_string ())], 885ends : Vec :: new (), 886}); 887} 888let state = shared. state . lock (). unwrap (); 889assert_eq! ( state. queue [ 0 ]. kind , RESCAN ); 890assert_eq! ( state. pending_ends , [ 0 ]); 891} 892 893# [ test ] 894fn rename_with_an_unauthorized_destination_becomes_remove () { 895let directory = tempfile:: tempdir (). unwrap (); 896let root = directory. path (); 897let shared =shared ( root); 898let secret = root. join ( "secret" ); 899 shared 900. caps 901. pledge ( vec! [( 902 avril_caps:: Category :: Read , 903None , 904vec! [ secret. display (). to_string ()], 905)]) 906. unwrap (); 907let event = shared. authorize ( QueuedEvent { 908kind : RENAME , 909paths : vec! [ root. join ( "old" ), secret. join ( "new" )], 910ends : Vec :: new (), 911}); 912assert_eq! ( 913 event, 914Some (( REMOVE , vec! [ root. join ( "old" ). display (). to_string ()])) 915); 916} 917}