fs_change_notifier/
lib.rs1#![deny(warnings, missing_docs, clippy::todo, clippy::unimplemented)]
25
26use notify::event::{CreateKind, ModifyKind};
27use notify::{Event, EventKind, Watcher};
28use std::collections::HashSet;
29use std::path::{Path, PathBuf};
30use tokio::sync::mpsc;
31
32pub use notify::RecursiveMode;
33
34pub fn create_watcher(
36 err_handler: impl Fn(notify::Error) + Send + 'static,
37) -> anyhow::Result<(Box<dyn Watcher + Send>, mpsc::Receiver<Event>)> {
38 let (tx, rx) = mpsc::channel::<Event>(1000);
39 let mut watcher = notify::recommended_watcher(move |ev: notify::Result<Event>| match ev {
40 Ok(ev) => {
41 let _ = tx.blocking_send(ev);
42 }
43 Err(e) => err_handler(e),
44 })?;
45
46 watcher.configure(notify::Config::default().with_follow_symlinks(false))?;
47
48 Ok((Box::new(watcher) as Box<dyn Watcher + Send>, rx))
49}
50
51pub async fn match_event(root: &Path, mut rx: mpsc::Receiver<Event>, exclude: &HashSet<PathBuf>) {
56 loop {
57 let event = if let Some(ev) = rx.recv().await {
58 ev
59 } else {
60 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
61 continue;
62 };
63
64 let has_non_excluded = event.paths.iter().any(|event_path| {
65 !exclude.iter().any(|exclude_path| {
66 let event = event_path.to_string_lossy();
67 let exclude = exclude_path.to_string_lossy();
68
69 if exclude.contains('*') {
70 let parts = exclude.split('*').collect::<Vec<_>>();
71 if parts.len() != 2 {
72 false
73 } else if let Ok(relative_event) = event_path.strip_prefix(root).map(|p| p.to_string_lossy()) {
74 relative_event.starts_with(parts[0]) && relative_event.ends_with(parts[1])
75 } else {
76 event.contains(parts[0]) && event.ends_with(parts[1])
77 }
78 } else {
79 event.contains(exclude.as_ref())
80 }
81 })
82 });
83
84 if has_non_excluded {
85 match event.kind {
86 EventKind::Create(CreateKind::File) => return,
87 EventKind::Modify(ModifyKind::Name(_)) | EventKind::Modify(ModifyKind::Data(_)) => return,
88 EventKind::Remove(_) => return,
89 _ => {}
90 }
91 }
92 }
93}
94
95pub async fn fetch_changed(root: &Path, mut rx: mpsc::Receiver<Event>, exclude: &HashSet<PathBuf>) -> Vec<PathBuf> {
100 loop {
101 let event = if let Some(ev) = rx.recv().await {
102 ev
103 } else {
104 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
105 continue;
106 };
107
108 let mut included = vec![];
109 event.paths.iter().for_each(|event_path| {
110 exclude.iter().for_each(|exclude_path| {
111 let event = event_path.to_string_lossy();
112 let exclude = exclude_path.to_string_lossy();
113
114 let exclude = if exclude.contains('*') {
115 let parts = exclude.split('*').collect::<Vec<_>>();
116 if parts.len() != 2 {
117 false
118 } else if let Ok(relative_event) = event_path.strip_prefix(root).map(|p| p.to_string_lossy()) {
119 relative_event.starts_with(parts[0]) && relative_event.ends_with(parts[1])
120 } else {
121 event.contains(parts[0]) && event.ends_with(parts[1])
122 }
123 } else {
124 event.contains(exclude.as_ref())
125 };
126
127 if !exclude {
128 included.push(event_path.to_path_buf());
129 }
130 })
131 });
132
133 if !included.is_empty() {
134 match event.kind {
135 EventKind::Create(CreateKind::File) => return included,
136 EventKind::Modify(ModifyKind::Name(_)) | EventKind::Modify(ModifyKind::Data(_)) => return included,
137 EventKind::Remove(_) => return included,
138 _ => {}
139 }
140 }
141 }
142}