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