1
1
extern crate notify;
2
2
3
+ use std:: collections:: HashMap ;
3
4
use std:: io:: Write ;
4
5
use std:: path:: Path ;
5
6
use std:: sync:: mpsc:: { channel, RecvTimeoutError } ;
@@ -12,6 +13,7 @@ use libssh_rs::{PollStatus, SshOption};
12
13
#[ allow( unused_imports) ]
13
14
use libssh_rs:: AuthStatus ;
14
15
use notify:: { Config , Error , ErrorKind , Event , EventKind , RecommendedWatcher , RecursiveMode , Watcher } ;
16
+ use notify:: event:: CreateKind ;
15
17
16
18
jni_init ! ( "" ) ;
17
19
@@ -81,36 +83,43 @@ impl FileWatcher {
81
83
return ;
82
84
}
83
85
84
- let mut paths_cached: Vec < String > = Vec :: new ( ) ;
86
+ let mut paths_cached = HashMap :: < String , Vec < EventKind > > :: new ( ) ;
87
+
85
88
let mut last_update: u128 = 0 ;
86
89
while notifier. should_keep_looping ( ) {
87
90
match rx. recv_timeout ( Duration :: from_millis ( WATCH_TIMEOUT ) ) {
88
91
Ok ( e) => {
89
92
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
91
94
. into_iter ( )
92
95
. collect ( ) ;
93
96
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
+ }
95
107
96
108
let current_time = current_time_as_millis ( ) ;
97
109
98
110
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) ;
102
113
last_update = current_time_as_millis ( ) ;
103
114
}
104
115
105
- println ! ( "Event: {e:?}" ) ;
116
+ // println!("Event: {e:?}");
106
117
}
107
118
}
108
119
Err ( e) => {
109
120
match e {
110
121
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) ;
114
123
last_update = current_time_as_millis ( ) ;
115
124
}
116
125
RecvTimeoutError :: Disconnected => {
@@ -140,11 +149,35 @@ impl FileWatcher {
140
149
}
141
150
}
142
151
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
+ }
148
181
}
149
182
150
183
fn current_time_as_millis ( ) -> u128 {
@@ -169,12 +202,12 @@ fn error_to_code(error_kind: ErrorKind) -> i32 {
169
202
}
170
203
}
171
204
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 > > {
173
206
match event_result {
174
207
Ok ( event) => {
175
208
match event. kind {
176
209
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) ;
178
211
179
212
if events. is_empty ( ) {
180
213
None
@@ -192,7 +225,7 @@ pub fn get_paths_from_event_result(event_result: &Result<Event, Error>, git_dir_
192
225
}
193
226
}
194
227
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 > {
196
229
event
197
230
. paths
198
231
. clone ( )
@@ -213,12 +246,23 @@ fn get_event_paths(event: &Event, git_dir_path: &str) -> Vec<String> {
213
246
if path_str. starts_with ( probe_prefix. as_str ( ) ) {
214
247
None
215
248
} 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)
217
255
}
218
256
// }
219
257
} )
220
258
. collect ( )
221
259
}
260
+
261
+ pub struct FileChangeEvent {
262
+ pub path : String ,
263
+ pub event_kind : EventKind ,
264
+ }
265
+
222
266
#[ jni_interface]
223
267
pub trait WatchDirectoryNotifier {
224
268
fn should_keep_looping ( & self ) -> bool ;
0 commit comments