Skip to content

Commit f82164d

Browse files
committed
Implemented short lived temporary files filtering
This was specially important after the implementation of Git LFS, as refreshing the LFS status would create short-lived temporary files, creating a refresh loop.
1 parent 95a32fc commit f82164d

File tree

1 file changed

+63
-19
lines changed

1 file changed

+63
-19
lines changed

rs/src/lib.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extern crate notify;
22

3+
use std::collections::HashMap;
34
use std::io::Write;
45
use std::path::Path;
56
use std::sync::mpsc::{channel, RecvTimeoutError};
@@ -12,6 +13,7 @@ use libssh_rs::{PollStatus, SshOption};
1213
#[allow(unused_imports)]
1314
use libssh_rs::AuthStatus;
1415
use notify::{Config, Error, ErrorKind, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
16+
use notify::event::CreateKind;
1517

1618
jni_init!("");
1719

@@ -81,36 +83,43 @@ impl FileWatcher {
8183
return;
8284
}
8385

84-
let mut paths_cached: Vec<String> = Vec::new();
86+
let mut paths_cached = HashMap::<String, Vec<EventKind>>::new();
87+
8588
let mut last_update: u128 = 0;
8689
while notifier.should_keep_looping() {
8790
match rx.recv_timeout(Duration::from_millis(WATCH_TIMEOUT)) {
8891
Ok(e) => {
8992
if let Some(paths) = get_paths_from_event_result(&e, &git_dir_path) {
90-
let mut paths_without_dirs: Vec<String> = paths
93+
let paths_without_dirs: Vec<FileChangeEvent> = paths
9194
.into_iter()
9295
.collect();
9396

94-
paths_cached.append(&mut paths_without_dirs);
97+
for path in paths_without_dirs.into_iter() {
98+
match paths_cached.get_mut(&path.path) {
99+
Some(v) => {
100+
v.push(path.event_kind)
101+
}
102+
None => {
103+
paths_cached.insert(path.path, vec![path.event_kind]);
104+
}
105+
}
106+
}
95107

96108
let current_time = current_time_as_millis();
97109

98110
if last_update != 0 &&
99-
current_time - last_update > MIN_TIME_IN_MS_BETWEEN_REFRESHES &&
100-
!paths_cached.is_empty() {
101-
notify_paths_changed(&mut paths_cached, notifier);
111+
current_time - last_update > MIN_TIME_IN_MS_BETWEEN_REFRESHES {
112+
process_paths_cached(&mut paths_cached, notifier);
102113
last_update = current_time_as_millis();
103114
}
104115

105-
println!("Event: {e:?}");
116+
// println!("Event: {e:?}");
106117
}
107118
}
108119
Err(e) => {
109120
match e {
110121
RecvTimeoutError::Timeout => {
111-
if !paths_cached.is_empty() {
112-
notify_paths_changed(&mut paths_cached, notifier);
113-
}
122+
process_paths_cached(&mut paths_cached, notifier);
114123
last_update = current_time_as_millis();
115124
}
116125
RecvTimeoutError::Disconnected => {
@@ -140,11 +149,35 @@ impl FileWatcher {
140149
}
141150
}
142151

143-
fn notify_paths_changed(paths_cached: &mut Vec<String>, notifier: &impl WatchDirectoryNotifier) {
144-
println!("Sending paths cached to Kotlin side");
145-
let paths = paths_cached.clone();
146-
paths_cached.clear(); // TODO Until this is executed, items are duplicated in memory, this can be easily optimized later on
147-
notifier.detected_change(paths);
152+
fn remove_temporary_files(changes: &mut HashMap<String, Vec<EventKind>>) -> Vec<String> {
153+
let paths: Vec<String> = changes
154+
.iter()
155+
.filter_map(|(key, value)| {
156+
let is_created = value.iter().any(|v| matches!(v, EventKind::Create(_)));
157+
let is_removed = value.iter().any(|v| matches!(v, EventKind::Remove(_)));
158+
159+
// If a file has been created and removed before passing it to kotlin,
160+
// filter it out, we don't care about it as it's a temporary file
161+
if is_created && is_removed {
162+
println!("Removing entry {key} as it looks like a temporary file.");
163+
None
164+
} else {
165+
Some(key.clone())
166+
}
167+
})
168+
.collect();
169+
170+
paths
171+
}
172+
173+
fn process_paths_cached(paths_cached: &mut HashMap<String, Vec<EventKind>>, notifier: &impl WatchDirectoryNotifier) {
174+
let paths_to_send: Vec<String> = remove_temporary_files(paths_cached);
175+
paths_cached.clear();
176+
177+
if !paths_to_send.is_empty() {
178+
println!("Sending a total of {} paths cached to Kotlin side", paths_to_send.len());
179+
notifier.detected_change(paths_to_send);
180+
}
148181
}
149182

150183
fn current_time_as_millis() -> u128 {
@@ -169,12 +202,12 @@ fn error_to_code(error_kind: ErrorKind) -> i32 {
169202
}
170203
}
171204

172-
pub fn get_paths_from_event_result(event_result: &Result<Event, Error>, git_dir_path: &str) -> Option<Vec<String>> {
205+
pub fn get_paths_from_event_result(event_result: &Result<Event, Error>, git_dir_path: &str) -> Option<Vec<FileChangeEvent>> {
173206
match event_result {
174207
Ok(event) => {
175208
match event.kind {
176209
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) => {
177-
let events: Vec<String> = get_event_paths(event, git_dir_path);
210+
let events: Vec<FileChangeEvent> = get_event_paths(event, git_dir_path);
178211

179212
if events.is_empty() {
180213
None
@@ -192,7 +225,7 @@ pub fn get_paths_from_event_result(event_result: &Result<Event, Error>, git_dir_
192225
}
193226
}
194227

195-
fn get_event_paths(event: &Event, git_dir_path: &str) -> Vec<String> {
228+
fn get_event_paths(event: &Event, git_dir_path: &str) -> Vec<FileChangeEvent> {
196229
event
197230
.paths
198231
.clone()
@@ -213,12 +246,23 @@ fn get_event_paths(event: &Event, git_dir_path: &str) -> Vec<String> {
213246
if path_str.starts_with(probe_prefix.as_str()) {
214247
None
215248
} else {
216-
Some(path_str)
249+
let file_change_event = FileChangeEvent {
250+
path: path_str,
251+
event_kind: event.kind,
252+
};
253+
254+
Some(file_change_event)
217255
}
218256
// }
219257
})
220258
.collect()
221259
}
260+
261+
pub struct FileChangeEvent {
262+
pub path: String,
263+
pub event_kind: EventKind,
264+
}
265+
222266
#[jni_interface]
223267
pub trait WatchDirectoryNotifier {
224268
fn should_keep_looping(&self) -> bool;

0 commit comments

Comments
 (0)