dirsql 0.0.1 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/.claude/CLAUDE.md +0 -120
- package/.github/workflows/minor-release.yml +0 -14
- package/.github/workflows/patch-release.yml +0 -45
- package/.github/workflows/pr-monitor.yml +0 -16
- package/.github/workflows/publish.yml +0 -306
- package/.github/workflows/python-lint.yml +0 -35
- package/.github/workflows/python-test.yml +0 -45
- package/.github/workflows/rust-test.yml +0 -41
- package/Cargo.lock +0 -851
- package/Cargo.toml +0 -27
- package/SUMMARY.md +0 -62
- package/justfile +0 -53
- package/pyproject.toml +0 -27
- package/src/db.rs +0 -312
- package/src/differ.rs +0 -372
- package/src/lib.rs +0 -15
- package/src/matcher.rs +0 -116
- package/src/scanner.rs +0 -100
- package/src/watcher.rs +0 -227
package/src/watcher.rs
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
use notify::{
|
|
2
|
-
Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher,
|
|
3
|
-
};
|
|
4
|
-
use std::path::{Path, PathBuf};
|
|
5
|
-
use std::sync::mpsc;
|
|
6
|
-
use std::time::Duration;
|
|
7
|
-
|
|
8
|
-
/// Events emitted by the file watcher.
|
|
9
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
10
|
-
pub enum FileEvent {
|
|
11
|
-
Created(PathBuf),
|
|
12
|
-
Modified(PathBuf),
|
|
13
|
-
Deleted(PathBuf),
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/// Wraps notify::RecommendedWatcher and translates raw events into FileEvent values.
|
|
17
|
-
pub struct Watcher {
|
|
18
|
-
_watcher: RecommendedWatcher,
|
|
19
|
-
rx: mpsc::Receiver<FileEvent>,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
impl Watcher {
|
|
23
|
-
/// Start watching a directory recursively. Events are buffered in an internal channel.
|
|
24
|
-
pub fn new(path: &Path) -> Result<Self, notify::Error> {
|
|
25
|
-
let (tx, rx) = mpsc::channel();
|
|
26
|
-
|
|
27
|
-
let mut watcher = RecommendedWatcher::new(
|
|
28
|
-
move |res: Result<Event, notify::Error>| {
|
|
29
|
-
if let Ok(event) = res {
|
|
30
|
-
let events = translate_event(&event);
|
|
31
|
-
for fe in events {
|
|
32
|
-
// Ignore send errors (receiver dropped)
|
|
33
|
-
let _ = tx.send(fe);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
Config::default(),
|
|
38
|
-
)?;
|
|
39
|
-
|
|
40
|
-
watcher.watch(path, RecursiveMode::Recursive)?;
|
|
41
|
-
|
|
42
|
-
Ok(Self {
|
|
43
|
-
_watcher: watcher,
|
|
44
|
-
rx,
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Receive the next event, blocking until one is available.
|
|
49
|
-
pub fn recv(&self) -> Option<FileEvent> {
|
|
50
|
-
self.rx.recv().ok()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// Try to receive an event with a timeout.
|
|
54
|
-
pub fn recv_timeout(&self, timeout: Duration) -> Option<FileEvent> {
|
|
55
|
-
self.rx.recv_timeout(timeout).ok()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// Drain all currently pending events without blocking.
|
|
59
|
-
pub fn try_recv_all(&self) -> Vec<FileEvent> {
|
|
60
|
-
let mut events = Vec::new();
|
|
61
|
-
while let Ok(event) = self.rx.try_recv() {
|
|
62
|
-
events.push(event);
|
|
63
|
-
}
|
|
64
|
-
events
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/// Translate a notify Event into zero or more FileEvents.
|
|
69
|
-
fn translate_event(event: &Event) -> Vec<FileEvent> {
|
|
70
|
-
let mut results = Vec::new();
|
|
71
|
-
|
|
72
|
-
for path in &event.paths {
|
|
73
|
-
let fe = match event.kind {
|
|
74
|
-
EventKind::Create(_) => Some(FileEvent::Created(path.clone())),
|
|
75
|
-
EventKind::Modify(_) => Some(FileEvent::Modified(path.clone())),
|
|
76
|
-
EventKind::Remove(_) => Some(FileEvent::Deleted(path.clone())),
|
|
77
|
-
_ => None,
|
|
78
|
-
};
|
|
79
|
-
if let Some(fe) = fe {
|
|
80
|
-
results.push(fe);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
results
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
#[cfg(test)]
|
|
88
|
-
mod tests {
|
|
89
|
-
use super::*;
|
|
90
|
-
use std::fs;
|
|
91
|
-
use std::thread;
|
|
92
|
-
use tempfile::TempDir;
|
|
93
|
-
|
|
94
|
-
#[test]
|
|
95
|
-
fn detects_file_creation() {
|
|
96
|
-
let dir = TempDir::new().unwrap();
|
|
97
|
-
let watcher = Watcher::new(dir.path()).unwrap();
|
|
98
|
-
|
|
99
|
-
// Small delay to let watcher initialize
|
|
100
|
-
thread::sleep(Duration::from_millis(100));
|
|
101
|
-
|
|
102
|
-
let file_path = dir.path().join("new_file.txt");
|
|
103
|
-
fs::write(&file_path, "hello").unwrap();
|
|
104
|
-
|
|
105
|
-
// Wait for events with timeout
|
|
106
|
-
let mut found_create = false;
|
|
107
|
-
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
|
108
|
-
while std::time::Instant::now() < deadline {
|
|
109
|
-
if let Some(event) = watcher.recv_timeout(Duration::from_millis(200)) {
|
|
110
|
-
if matches!(event, FileEvent::Created(_)) {
|
|
111
|
-
found_create = true;
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
assert!(found_create, "Expected a Created event");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
#[test]
|
|
120
|
-
fn detects_file_deletion() {
|
|
121
|
-
let dir = TempDir::new().unwrap();
|
|
122
|
-
let file_path = dir.path().join("to_delete.txt");
|
|
123
|
-
fs::write(&file_path, "doomed").unwrap();
|
|
124
|
-
|
|
125
|
-
let watcher = Watcher::new(dir.path()).unwrap();
|
|
126
|
-
thread::sleep(Duration::from_millis(100));
|
|
127
|
-
|
|
128
|
-
fs::remove_file(&file_path).unwrap();
|
|
129
|
-
|
|
130
|
-
let mut found_delete = false;
|
|
131
|
-
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
|
132
|
-
while std::time::Instant::now() < deadline {
|
|
133
|
-
if let Some(event) = watcher.recv_timeout(Duration::from_millis(200)) {
|
|
134
|
-
if matches!(event, FileEvent::Deleted(_)) {
|
|
135
|
-
found_delete = true;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
assert!(found_delete, "Expected a Deleted event");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
#[test]
|
|
144
|
-
fn detects_file_modification() {
|
|
145
|
-
let dir = TempDir::new().unwrap();
|
|
146
|
-
let file_path = dir.path().join("modify_me.txt");
|
|
147
|
-
fs::write(&file_path, "original").unwrap();
|
|
148
|
-
|
|
149
|
-
let watcher = Watcher::new(dir.path()).unwrap();
|
|
150
|
-
thread::sleep(Duration::from_millis(100));
|
|
151
|
-
|
|
152
|
-
fs::write(&file_path, "modified content").unwrap();
|
|
153
|
-
|
|
154
|
-
// We should get either a Modified or Created event (some backends emit Create on overwrite)
|
|
155
|
-
let mut found_event = false;
|
|
156
|
-
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
|
157
|
-
while std::time::Instant::now() < deadline {
|
|
158
|
-
if let Some(event) = watcher.recv_timeout(Duration::from_millis(200)) {
|
|
159
|
-
if matches!(event, FileEvent::Modified(_) | FileEvent::Created(_)) {
|
|
160
|
-
found_event = true;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
assert!(found_event, "Expected a Modified or Created event");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
#[test]
|
|
169
|
-
fn try_recv_all_drains_pending_events() {
|
|
170
|
-
let dir = TempDir::new().unwrap();
|
|
171
|
-
let watcher = Watcher::new(dir.path()).unwrap();
|
|
172
|
-
thread::sleep(Duration::from_millis(100));
|
|
173
|
-
|
|
174
|
-
// Create several files
|
|
175
|
-
for i in 0..3 {
|
|
176
|
-
fs::write(dir.path().join(format!("file_{i}.txt")), "data").unwrap();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Wait a bit for events to arrive
|
|
180
|
-
thread::sleep(Duration::from_millis(500));
|
|
181
|
-
|
|
182
|
-
let events = watcher.try_recv_all();
|
|
183
|
-
assert!(
|
|
184
|
-
!events.is_empty(),
|
|
185
|
-
"Expected at least one event from batch file creation"
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
#[test]
|
|
190
|
-
fn translate_event_maps_create() {
|
|
191
|
-
let event = Event {
|
|
192
|
-
kind: EventKind::Create(notify::event::CreateKind::File),
|
|
193
|
-
paths: vec![PathBuf::from("/tmp/test.txt")],
|
|
194
|
-
attrs: Default::default(),
|
|
195
|
-
};
|
|
196
|
-
let results = translate_event(&event);
|
|
197
|
-
assert_eq!(
|
|
198
|
-
results,
|
|
199
|
-
vec![FileEvent::Created(PathBuf::from("/tmp/test.txt"))]
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
#[test]
|
|
204
|
-
fn translate_event_maps_remove() {
|
|
205
|
-
let event = Event {
|
|
206
|
-
kind: EventKind::Remove(notify::event::RemoveKind::File),
|
|
207
|
-
paths: vec![PathBuf::from("/tmp/gone.txt")],
|
|
208
|
-
attrs: Default::default(),
|
|
209
|
-
};
|
|
210
|
-
let results = translate_event(&event);
|
|
211
|
-
assert_eq!(
|
|
212
|
-
results,
|
|
213
|
-
vec![FileEvent::Deleted(PathBuf::from("/tmp/gone.txt"))]
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
#[test]
|
|
218
|
-
fn translate_event_ignores_access_events() {
|
|
219
|
-
let event = Event {
|
|
220
|
-
kind: EventKind::Access(notify::event::AccessKind::Read),
|
|
221
|
-
paths: vec![PathBuf::from("/tmp/read.txt")],
|
|
222
|
-
attrs: Default::default(),
|
|
223
|
-
};
|
|
224
|
-
let results = translate_event(&event);
|
|
225
|
-
assert!(results.is_empty());
|
|
226
|
-
}
|
|
227
|
-
}
|