1 module libasync.watcher; 2 3 import libasync.types; 4 5 import libasync.events; 6 import libasync.internals.path; 7 import std.container : Array; 8 import std.file; 9 10 /// Watches one or more directories in the local filesystem for the specified events 11 /// by calling a custom event handler asynchroneously when they occur. 12 /// 13 /// Usage: run() the object, start watching directories, receive an event in your handler, 14 /// read the changes by draining the buffer. 15 final nothrow class AsyncDirectoryWatcher 16 { 17 nothrow: 18 private: 19 EventLoop m_evLoop; 20 Array!WatchInfo m_directories; 21 fd_t m_fd; 22 debug bool m_dataRemaining; 23 public: 24 this(EventLoop evl) 25 in { assert(evl !is null); } 26 body { m_evLoop = evl; } 27 28 mixin DefStatus; 29 30 /// Fills the buffer with file/folder events and returns the number 31 /// of events consumed. Returns 0 when the buffer is drained. 32 uint readChanges(ref DWChangeInfo[] dst) { 33 uint cnt = m_evLoop.readChanges(m_fd, dst); 34 35 debug { 36 if (cnt < dst.length) 37 m_dataRemaining = false; 38 } 39 return cnt; 40 } 41 42 /// Registers the object in the underlying event loop and sends notifications 43 /// related to buffer activity by calling the specified handler. 44 bool run(void delegate() del) { 45 DWHandler handler; 46 handler.del = del; 47 handler.ctxt = this; 48 49 m_fd = m_evLoop.run(this, handler); 50 // import std.stdio; 51 // try writeln("Running with FD: ", m_fd); catch {} 52 53 if (m_fd == fd_t.init) 54 return false; 55 return true; 56 } 57 58 /// Starts watching for file events in the specified directory, 59 /// recursing into subdirectories will add those and its files 60 /// to the watch list as well. 61 bool watchDir(string path, DWFileEvent ev = DWFileEvent.ALL, bool recursive = false) { 62 63 try 64 { 65 path = Path(path).toNativeString(); 66 //import std.stdio; 67 //writeln("Watching ", path); 68 bool addWatch() { 69 WatchInfo info; 70 info.events = ev; 71 try info.path = Path(path); catch {} 72 info.recursive = recursive; 73 74 //writeln("Watch: ", info.path.toNativeString()); 75 uint wd = m_evLoop.watch(m_fd, info); 76 //writeln("Watching WD: ", wd); 77 if (wd == 0 && m_evLoop.status.code != Status.OK) 78 return false; 79 info.wd = wd; 80 try m_directories.insert(info); catch {} 81 return true; 82 } 83 84 if (!addWatch()) 85 return false; 86 87 } 88 catch (Exception e) { 89 static if (DEBUG) { 90 import std.stdio; 91 try writeln("Could not add directory: " ~ e.toString()); catch {} 92 } 93 return false; 94 } 95 96 return true; 97 } 98 99 /// Removes the directory and its files from the event watch list. 100 /// Recursive will remove all subdirectories in the watch list. 101 bool unwatchDir(string path, bool recursive) { 102 import std.algorithm : countUntil; 103 104 try { 105 path = Path(path).toString(); 106 107 bool removeWatch(string path) { 108 auto idx = m_directories[].countUntil!((a,b) => a.path == b)(Path(path)); 109 if (idx < 0) 110 return true; 111 112 if (!m_evLoop.unwatch(m_fd, m_directories[idx].wd)) 113 return false; 114 115 m_directories.linearRemove(m_directories[idx .. idx+1]); 116 return true; 117 } 118 removeWatch(path); 119 if (recursive && path.isDir) { 120 foreach (de; path.dirEntries(SpanMode.shallow)) { 121 if (de.isDir){ 122 if (!removeWatch(path)) 123 return false; 124 } 125 } 126 } 127 } catch {} 128 return true; 129 } 130 131 /// Cleans up underlying resources. 132 bool kill() 133 in { assert(m_fd != fd_t.init); } 134 body { 135 return m_evLoop.kill(this); 136 } 137 138 @property fd_t fd() const { 139 return m_fd; 140 } 141 142 package: 143 version(Posix) mixin EvInfoMixins; 144 145 @property void fd(fd_t val) { 146 m_fd = val; 147 } 148 149 } 150 151 /// Represents one event on one file in a watched directory. 152 struct DWChangeInfo { 153 /// The event triggered by the file/folder 154 DWFileEvent event; 155 /// The os-independent address of the file/folder 156 private Path m_path; 157 158 @property string path() { 159 return m_path.toNativeString(); 160 } 161 162 @property void path(Path p) { 163 m_path = p; 164 } 165 166 } 167 168 /// List of events that can be watched for. They must be 'Or'ed together 169 /// to combined them when calling watch(). OS-triggerd events are exclusive. 170 enum DWFileEvent : uint { 171 ERROR = 0, 172 MODIFIED = 0x00000002, 173 MOVED_FROM = 0x00000040, 174 MOVED_TO = 0x00000080, 175 CREATED = 0x00000100, 176 DELETED = 0x00000200, 177 ALL = MODIFIED | MOVED_FROM | MOVED_TO | CREATED | DELETED 178 } 179 180 181 package struct DWHandler { 182 AsyncDirectoryWatcher ctxt; 183 void delegate() del; 184 void opCall(){ 185 assert(ctxt !is null); 186 debug ctxt.m_dataRemaining = true; 187 del(); 188 debug { 189 assert(!ctxt.m_dataRemaining, "You must read all changes when you receive a notification for directory changes"); 190 } 191 assert(ctxt !is null); 192 return; 193 } 194 } 195 196 package struct WatchInfo { 197 DWFileEvent events; 198 Path path; 199 bool recursive; 200 uint wd; // watch descriptor 201 }