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 import std.stdio; 90 try writeln("Could not add directory: " ~ e.toString()); catch {} 91 return false; 92 } 93 94 return true; 95 } 96 97 /// Removes the directory and its files from the event watch list. 98 /// Recursive will remove all subdirectories in the watch list. 99 bool unwatchDir(string path, bool recursive) { 100 import std.algorithm : countUntil; 101 102 try { 103 path = Path(path).toString(); 104 105 bool removeWatch(string path) { 106 auto idx = m_directories[].countUntil!((a,b) => a.path == b)(Path(path)); 107 if (idx < 0) 108 return true; 109 110 if (!m_evLoop.unwatch(m_fd, m_directories[idx].wd)) 111 return false; 112 113 m_directories.linearRemove(m_directories[idx .. idx+1]); 114 return true; 115 } 116 removeWatch(path); 117 if (recursive && path.isDir) { 118 foreach (de; path.dirEntries(SpanMode.shallow)) { 119 if (de.isDir){ 120 if (!removeWatch(path)) 121 return false; 122 } 123 } 124 } 125 } catch {} 126 return true; 127 } 128 129 /// Cleans up underlying resources. 130 bool kill() 131 in { assert(m_fd != fd_t.init); } 132 body { 133 return m_evLoop.kill(this); 134 } 135 136 package: 137 version(Posix) mixin EvInfoMixins; 138 139 @property fd_t fd() const { 140 return m_fd; 141 } 142 143 @property void fd(fd_t val) { 144 m_fd = val; 145 } 146 147 } 148 149 /// Represents one event on one file in a watched directory. 150 struct DWChangeInfo { 151 /// The event triggered by the file/folder 152 DWFileEvent event; 153 /// The os-independent address of the file/folder 154 private Path m_path; 155 156 @property string path() { 157 return m_path.toNativeString(); 158 } 159 160 @property void path(Path p) { 161 m_path = p; 162 } 163 164 } 165 166 /// List of events that can be watched for. They must be 'Or'ed together 167 /// to combined them when calling watch(). OS-triggerd events are exclusive. 168 enum DWFileEvent : uint { 169 ERROR = 0, 170 MODIFIED = 0x00000002, 171 MOVED_FROM = 0x00000040, 172 MOVED_TO = 0x00000080, 173 CREATED = 0x00000100, 174 DELETED = 0x00000200, 175 ALL = MODIFIED | MOVED_FROM | MOVED_TO | CREATED | DELETED 176 } 177 178 179 package struct DWHandler { 180 AsyncDirectoryWatcher ctxt; 181 void delegate() del; 182 void opCall(){ 183 assert(ctxt !is null); 184 debug ctxt.m_dataRemaining = true; 185 del(); 186 debug { 187 assert(!ctxt.m_dataRemaining, "You must read all changes when you receive a notification for directory changes"); 188 } 189 assert(ctxt !is null); 190 return; 191 } 192 } 193 194 package struct WatchInfo { 195 DWFileEvent events; 196 Path path; 197 bool recursive; 198 uint wd; // watch descriptor 199 }