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.meta.types.Qualifiers; 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 }