1 /*******************************************************************************
2
3 File system event which can be registered with the
4 EpollSelectDispatcher. The implementation uses inotify internally, see
5 ocean.sys.Inotify and http://man7.org/linux/man-pages/man7/inotify.7.html
6
7 Copyright:
8 Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9 All rights reserved.
10
11 License:
12 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13 Alternatively, this file may be distributed under the terms of the Tango
14 3-Clause BSD License (see LICENSE_BSD.txt for details).
15
16 *******************************************************************************/
17
18 module ocean.io.select.client.FileSystemEvent;
19
20
21
22
23
24 import ocean.core.Verify;
25 import ocean.sys.Inotify;
26 import core.stdc.string;
27 import core.sys.linux.sys.inotify;
28
29 import core.sys.posix.unistd;
30
31 import ocean.io.select.EpollSelectDispatcher;
32 import ocean.io.select.client.model.ISelectClient: ISelectClient;
33 import ocean.core.Buffer;
34 import ocean.core.SmartUnion;
35 import ocean.transition;
36
37
38 /*******************************************************************************
39
40 Flags to be passed to FileSystemEvent.watch
41
42 *******************************************************************************/
43
44 enum FileEventsEnum : uint
45 {
46 /* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH. */
47 IN_ACCESS = 0x00000001, /* File was accessed. */
48 IN_MODIFY = 0x00000002, /* File was modified. */
49 IN_ATTRIB = 0x00000004, /* Metadata changed. */
50 IN_CLOSE_WRITE = 0x00000008, /* Writtable file was closed. */
51 IN_CLOSE_NOWRITE = 0x00000010, /* Unwrittable file was closed. */
52 IN_CLOSE = 0x00000018, /* Close. */
53 IN_OPEN = 0x00000020, /* File was opened. */
54 IN_MOVED_FROM = 0x00000040, /* File was moved from X. */
55 IN_MOVED_TO = 0x00000080, /* File was moved to Y. */
56 IN_MOVE = 0x000000c0, /* Moves. */
57 IN_CREATE = 0x00000100, /* Subfile was created. */
58 IN_DELETE = 0x00000200, /* Subfile was deleted. */
59 IN_DELETE_SELF = 0x00000400, /* Self was deleted. */
60 IN_MOVE_SELF = 0x00000800, /* Self was moved. */
61
62 /* Events sent by the kernel. */
63 IN_UMOUNT = 0x00002000, /* Backing fs was unmounted. */
64 IN_Q_OVERFLOW = 0x00004000, /* Event queued overflowed */
65 IN_IGNORED = 0x00008000, /* File was ignored */
66
67 /* Special flags. */
68 IN_ONLYDIR = 0x01000000, /* Only watch the path if it is a directory. */
69 IN_DONT_FOLLOW = 0x02000000, /* Do not follow a sym link. */
70 IN_MASK_ADD = 0x20000000, /* Add to the mask of an already existing watch. */
71 IN_ISDIR = 0x40000000, /* Event occurred against dir. */
72 IN_ONESHOT = 0x80000000, /* Only send event once. */
73
74 IN_ALL_EVENTS = 0x00000fff, /* All events which a program can wait on. */
75 }
76
77 class FileSystemEvent : ISelectClient
78 {
79 import ocean.core.Buffer;
80
81 /***************************************************************************
82
83 Structure carrying only path and event.
84
85 ***************************************************************************/
86
87 public struct FileEvent
88 {
89 cstring path;
90 uint event;
91 }
92
93
94 /***************************************************************************
95
96 Structure carrying path, name and event, representing event that happened
97 on a file in a watched directory.
98
99 ***************************************************************************/
100
101 public struct DirectoryFileEvent
102 {
103 cstring path;
104 cstring name;
105 uint event;
106 }
107
108
109 /***************************************************************************
110
111 Union of possible events that could happen.
112
113 ***************************************************************************/
114
115 public union EventUnion
116 {
117 FileEvent file_event;
118 DirectoryFileEvent directory_file_event;
119 }
120
121
122 /***************************************************************************
123
124 SmartUnion alias for the possible events that could happen.
125
126 ***************************************************************************/
127
128 public alias SmartUnion!(EventUnion) RaisedEvent;
129
130
131 /***************************************************************************
132
133 Alias for the notifier delegate that receives the event as a smart union.
134
135 ***************************************************************************/
136
137 public alias void delegate ( RaisedEvent ) Notifier;
138
139
140 /***************************************************************************
141
142 Inotify wrapper
143
144 ***************************************************************************/
145
146 private Inotify fd;
147
148
149 /***************************************************************************
150
151 Event notifier delegate, specified in the constructor and called whenever
152 a watched file system event fires, passing the SmartUnion describing the
153 event.
154
155 ***************************************************************************/
156
157 private Notifier notifier;
158
159
160 /***************************************************************************
161
162 Associative array which maps inotify "watch descriptor" against "path".
163 When a watch is performed in inotify, a new entry is created in this array.
164 On the other hand, every unwatch implies the removal of the entry.
165
166 Note: The array will never have 2 entries with same path. When same path
167 is provided to watch, the existing "watch descriptor" is re-used
168 See inotify manual for further details.
169
170 ***************************************************************************/
171
172 private char[][uint] watched_files;
173
174
175 /***********************************************************************
176
177 Constructor. Creates a custom event and hooks it up to the provided
178 event notifier which in addition accepts the name field.
179
180 Params:
181 notifier = event notifier
182
183 ***********************************************************************/
184
185 public this ( scope Notifier notifier )
186 {
187 this();
188 this.notifier = notifier;
189 }
190
191
192 /***************************************************************************
193
194 Constructor. Initializes a custom event.
195
196 ***************************************************************************/
197
198 private this ()
199 {
200 this.fd = new Inotify;
201 }
202
203
204 /***********************************************************************
205
206 Replace the notifier delegate
207
208 Params:
209 notifier = event notifier
210
211 ***********************************************************************/
212
213 public void setNotifier ( scope Notifier notifier )
214 {
215 this.notifier = notifier;
216 }
217
218
219 /***********************************************************************
220
221 Required by ISelectable interface.
222
223 Returns:
224 file descriptor used to manage custom event
225
226 ***********************************************************************/
227
228 public override Handle fileHandle ( )
229 {
230 return this.fd.fileHandle;
231 }
232
233
234 /***************************************************************************
235
236 Returns:
237 the epoll events to register for.
238
239 ***************************************************************************/
240
241 public override Event events ( )
242 {
243 return Event.EPOLLIN;
244 }
245
246
247 /***************************************************************************
248
249 Adds or updates the events being watched for the specified path. The
250 handler delegate previously specified will be called when one of the
251 watched events occurs.
252
253 params:
254 path = File path to watch (directories are also supported)
255 events = Inotify events that will be watched (flags)
256
257 Throws:
258 upon failure during addition of new file to watch
259
260 ***************************************************************************/
261
262 public void watch ( char[] path, FileEventsEnum events )
263 {
264 //Attention: Existing wd is returned if path is being watched
265 uint wd = this.fd.addWatch(path, events);
266
267 if ( auto existing_path = wd in this.watched_files )
268 {
269 verify(*existing_path == path);
270 }
271 else
272 {
273 this.watched_files[wd] = path;
274 }
275 }
276
277
278 /***************************************************************************
279
280 Stops watching the specified path. The handler delegate will no longer
281 be called when events on this path occur.
282
283 Returns:
284 True, if path was successfully removed
285 False, the path was not found in the list of watched paths
286
287 Throws:
288 upon failure when removing the watch of a file
289
290 ***************************************************************************/
291
292 public bool unwatch ( char[] path )
293 {
294 bool removed = false;
295
296 foreach ( wd, wd_path; this.watched_files )
297 {
298 if ( wd_path == path )
299 {
300 this.fd.rmWatch(wd);
301 this.watched_files.remove(wd);
302 removed = true;
303 break;
304 }
305 }
306
307 return removed;
308 }
309
310
311 /***************************************************************************
312
313 Event handler, invoked by the epoll select dispatcher.
314
315 Params:
316 event = event(s) reported by epoll
317
318 Returns:
319 true to stay registered in epoll or false to unregister.
320
321 ***************************************************************************/
322
323 public override bool handle ( Event event )
324 {
325 foreach ( ev; this.fd.readEvents() )
326 {
327 verify(ev.mask != typeof(ev.mask).init);
328
329 auto path = ev.wd in this.watched_files;
330 if (path is null)
331 continue;
332
333 if (this.notifier)
334 {
335 RaisedEvent event_info;
336
337 if (ev.len > 0)
338 {
339 auto name_ptr = cast(char*)&ev.name;
340 auto name = name_ptr[0..strlen(name_ptr)];
341 event_info.directory_file_event = DirectoryFileEvent(*path,
342 name, ev.mask);
343 }
344 else
345 {
346 event_info.file_event = FileEvent(*path, ev.mask);
347 }
348
349 this.notifier(event_info);
350 }
351 }
352
353 return true;
354 }
355
356 }