Rust와 notify-rs를 이용한 파일 감시 — 동기화 앱을 위한 핫 폴더
Source: Dev.to
기본 설정
[dependencies]
notify = "6"
use notify::{RecommendedWatcher, RecursiveMode, Watcher, Config};
use std::sync::mpsc;
fn watch_directory(path: &str) -> Result {
let (tx, rx) = mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
for res in rx {
match res {
Ok(event) => handle_event(event),
Err(e) => log::error!("Watch error: {:?}", e),
}
}
Ok(())
}
RecommendedWatcher는 macOS에서 FSEvents(네이티브 파일 시스템 이벤트 API)를 사용합니다. 오버헤드가 낮고 알림이 빠릅니다.
이중 트리거 문제
파일 저장 작업은 종종 여러 이벤트를 발생시킵니다(Modify, Modify, Create, Modify 등). 일반적으로 네 번이 아니라 한 번의 동기화 트리거만 원합니다.
디바운스
use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::path::PathBuf;
struct Debouncer {
last_seen: HashMap,
delay: Duration,
}
impl Debouncer {
fn should_process(&mut self, path: &PathBuf) -> bool {
let now = Instant::now();
let last = self
.last_seen
.entry(path.clone())
.or_insert(Instant::now() - self.delay * 2);
if now.duration_since(*last) >= self.delay {
*last = now;
true
} else {
false
}
}
}
300–500 ms 정도의 디바운스 윈도우면 대부분의 편집기 저장 동작을 커버하면서도 느리게 느껴지지 않습니다.
감시할 대상 필터링
모든 파일 변경이 동기화를 일으켜서는 안 됩니다. 예시 필터:
fn should_ignore(path: &Path) -> bool {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
// 숨김 파일
name.starts_with('.')
// 임시 파일
|| name.ends_with(".tmp")
|| name.ends_with('~')
// macOS 메타데이터
|| name == ".DS_Store"
// 이미 동기화 중인 파일
|| name.ends_with(".sync")
}
Tauri에서 워처 실행하기
워처는 백그라운드 스레드에서 실행되어야 Tokio 런타임을 차단하지 않습니다:
std::thread::spawn(move || {
if let Err(e) = watch_directory(&path) {
log::error!("Watcher failed: {:?}", e);
}
});
채널이나 앱 핸들의 emit 시스템을 통해 Tauri와 통신합니다.
결론
macOS에서 FSEvents를 사용하는 notify‑rs는 견고합니다. 이중 트리거 문제는 디바운스로 해결해야 하며, 처음부터 구현하는 것이 좋습니다. 관련 없는 변경에 반응하지 않도록 필터를 적극적으로 적용하세요.
이 글이 도움이 되었다면 ❤️ 하나가 생각보다 큰 힘이 됩니다 — 감사합니다!
Hiyoko PDF Vault →
X →