1 /// Platform independent file watcher 2 /** 3 * Interface is expected to change in order to make it less platform specific. 4 */ 5 module mecca.reactor.io.fs_watcher; 6 7 // Licensed under the Boost license. Full copyright information in the AUTHORS file 8 9 import mecca.log; 10 import mecca.reactor; 11 12 version(linux): 13 14 import mecca.reactor.io.inotify; 15 public import mecca.reactor.io.inotify : WatchDescriptor; 16 public import core.sys.linux.sys.inotify; 17 18 19 /// file watcher struct 20 struct FSWatcher { 21 /// Signature for the watch event callback delegate 22 alias void delegate(ref const(Inotifier.Event) wd) nothrow WatchEventCallback; 23 private: 24 FiberHandle watcherFiberHandle; 25 WatchEventCallback[WatchDescriptor] registeredWatchers; // XXX TODO: switch to non-GC hashing 26 Inotifier notifier; 27 28 public: 29 /** 30 * Add a watch point. 31 * 32 * Params: 33 * path = The path to watch 34 * mask = The events we should watch for. See details in `Inotifier`. 35 * callback = The delegate to be called when the events happen. Will be passed a `Inotifier.Event` struct. The code 36 * will run under a critical section, so it must not yield. 37 */ 38 @notrace WatchDescriptor addWatch(const(char)[] path, uint mask, WatchEventCallback callback) @safe { 39 if( registeredWatchers.length==0 ) 40 open(); 41 42 auto wd = notifier.watch(path, mask); 43 44 registeredWatchers[wd] = callback; 45 46 return wd; 47 } 48 49 /// Remove a watch point 50 @notrace void removeWatch(WatchDescriptor wd) @safe @nogc { 51 notifier.removeWatch(wd); 52 registeredWatchers.remove(wd); 53 } 54 55 private: 56 @notrace void open() @safe @nogc { 57 notifier.open(); 58 watcherFiberHandle = theReactor.spawnFiber( &watcherFiber ); 59 } 60 61 @notrace void closing() nothrow @safe @nogc { 62 notifier.close(); 63 watcherFiberHandle.reset(); 64 } 65 66 void watcherFiber() { 67 scope(exit) { 68 closing(); 69 } 70 71 while(true) { 72 auto event = notifier.getEvent(); 73 WatchEventCallback* cb = event.wd in registeredWatchers; 74 if( cb is null ) { 75 WARN!"Received event %s on %s with mask %x but no handler"(event.wd, event.name, event.mask); 76 continue; 77 } 78 79 with(theReactor.criticalSection) { 80 (*cb)(*event); 81 } 82 } 83 } 84 } 85 86 __gshared FSWatcher fsWatcher; 87 88 unittest { 89 import mecca.lib.exception; 90 91 WatchDescriptor wd; 92 uint numEvents; 93 94 void eventCB(ref const(Inotifier.Event) event) nothrow { 95 INFO!"handle %s mask %x cookie %s len %s name %s"( event.wd, event.mask, event.cookie, event.len, event.name ); 96 numEvents++; 97 } 98 99 void testBody() { 100 import core.sys.posix.stdlib; 101 import std..string; 102 import std.file; 103 import std.process; 104 import std.exception; 105 106 import mecca.lib.time; 107 108 string iPath = format("%s/meccaUT-XXXXXX\0", tempDir()); 109 char[] path; 110 path.length = iPath.length; 111 path[] = iPath[]; 112 errnoEnforce( mkdtemp(path.ptr) !is null ); 113 scope(exit) execute( ["rm", "-rf", path] ); 114 115 iPath.length = 0; 116 iPath ~= path[0..$-1]; 117 DEBUG!"Using directory %s for inotify tests"( iPath ); 118 119 wd = fsWatcher.addWatch( 120 path, 121 Inotifier.IN_MODIFY|Inotifier.IN_ATTRIB|Inotifier.IN_CREATE|Inotifier.IN_DELETE|Inotifier.IN_MOVED_FROM|Inotifier.IN_MOVED_TO|Inotifier.IN_ONLYDIR, 122 &eventCB); 123 DEBUG!"watch registration returned %s"(wd); 124 125 uint historicalNumEvents; 126 assertEQ(numEvents, historicalNumEvents); 127 theReactor.yield(); 128 assertEQ(numEvents, historicalNumEvents); 129 theReactor.yield(); 130 assertEQ(numEvents, historicalNumEvents); 131 132 execute( ["touch", iPath ~ "/file1"] ); 133 theReactor.sleep(1.msecs); 134 assertGT(numEvents, historicalNumEvents); 135 historicalNumEvents = numEvents; 136 theReactor.yield(); 137 assertEQ(numEvents, historicalNumEvents); 138 theReactor.yield(); 139 assertEQ(numEvents, historicalNumEvents); 140 rename( iPath ~ "/file1", iPath ~ "/file2" ); 141 theReactor.sleep(1.msecs); 142 assertGT(numEvents, historicalNumEvents); 143 historicalNumEvents = numEvents; 144 theReactor.yield(); 145 assertEQ(numEvents, historicalNumEvents); 146 theReactor.yield(); 147 assertEQ(numEvents, historicalNumEvents); 148 } 149 150 testWithReactor(&testBody); 151 }