KeventWatcher.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. //
  2. // System.IO.KeventWatcher.cs: interface with osx kevent
  3. //
  4. // Authors:
  5. // Geoff Norton ([email protected])
  6. //
  7. // (c) 2004 Geoff Norton
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. using System;
  29. using System.Collections;
  30. using System.ComponentModel;
  31. using System.Runtime.CompilerServices;
  32. using System.Runtime.InteropServices;
  33. using System.Text;
  34. using System.Threading;
  35. namespace System.IO {
  36. struct kevent {
  37. public int ident;
  38. public short filter;
  39. public ushort flags;
  40. public uint fflags;
  41. public int data;
  42. public string udata;
  43. }
  44. struct timespec {
  45. public int tv_sec;
  46. public int tv_usec;
  47. }
  48. class KeventFileData {
  49. public FileSystemInfo fsi;
  50. public DateTime LastAccessTime;
  51. public DateTime LastWriteTime;
  52. public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
  53. this.fsi = fsi;
  54. this.LastAccessTime = LastAccessTime;
  55. this.LastWriteTime = LastWriteTime;
  56. }
  57. }
  58. class KeventData {
  59. public FileSystemWatcher FSW;
  60. public string Directory;
  61. public string FileMask;
  62. public bool IncludeSubdirs;
  63. public bool Enabled;
  64. public Hashtable DirEntries;
  65. public kevent ev;
  66. }
  67. class KeventWatcher : IFileWatcher
  68. {
  69. static bool failed;
  70. static KeventWatcher instance;
  71. static Hashtable watches;
  72. static Hashtable requests;
  73. static Thread thread;
  74. static int conn;
  75. static bool stop;
  76. private KeventWatcher ()
  77. {
  78. }
  79. public static bool GetInstance (out IFileWatcher watcher)
  80. {
  81. lock (typeof (KeventWatcher)) {
  82. if (failed == true) {
  83. watcher = null;
  84. return false;
  85. }
  86. if (instance != null) {
  87. watcher = instance;
  88. return true;
  89. }
  90. watches = Hashtable.Synchronized (new Hashtable ());
  91. requests = Hashtable.Synchronized (new Hashtable ());
  92. conn = kqueue();
  93. if (conn == -1) {
  94. failed = true;
  95. watcher = null;
  96. return false;
  97. }
  98. instance = new KeventWatcher ();
  99. watcher = instance;
  100. return true;
  101. }
  102. }
  103. public void StartDispatching (FileSystemWatcher fsw)
  104. {
  105. KeventData data;
  106. lock (this) {
  107. if (thread == null) {
  108. thread = new Thread (new ThreadStart (Monitor));
  109. thread.IsBackground = true;
  110. thread.Start ();
  111. }
  112. data = (KeventData) watches [fsw];
  113. }
  114. if (data == null) {
  115. data = new KeventData ();
  116. data.FSW = fsw;
  117. data.Directory = fsw.FullPath;
  118. data.FileMask = fsw.MangledFilter;
  119. data.IncludeSubdirs = fsw.IncludeSubdirectories;
  120. data.Enabled = true;
  121. lock (this) {
  122. StartMonitoringDirectory (data);
  123. watches [fsw] = data;
  124. stop = false;
  125. }
  126. }
  127. }
  128. static void StartMonitoringDirectory (KeventData data)
  129. {
  130. DirectoryInfo dir = new DirectoryInfo (data.Directory);
  131. if(data.DirEntries == null) {
  132. data.DirEntries = new Hashtable();
  133. foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
  134. data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime));
  135. }
  136. int fd = open(data.Directory, 0, 0);
  137. kevent ev = new kevent();
  138. timespec nullts = new timespec();
  139. nullts.tv_sec = 0;
  140. nullts.tv_usec = 0;
  141. if (fd > 0) {
  142. ev.ident = fd;
  143. ev.filter = -4;
  144. ev.flags = 1 | 4 | 20;
  145. ev.fflags = 20 | 2 | 1 | 8;
  146. ev.data = 0;
  147. ev.udata = data.Directory;
  148. kevent outev = new kevent();
  149. outev.udata = "";
  150. kevent (conn, ref ev, 1, ref outev, 0, ref nullts);
  151. data.ev = ev;
  152. requests [fd] = data;
  153. }
  154. if (!data.IncludeSubdirs)
  155. return;
  156. }
  157. public void StopDispatching (FileSystemWatcher fsw)
  158. {
  159. KeventData data;
  160. lock (this) {
  161. data = (KeventData) watches [fsw];
  162. if (data == null)
  163. return;
  164. StopMonitoringDirectory (data);
  165. watches.Remove (fsw);
  166. if (watches.Count == 0)
  167. stop = true;
  168. if (!data.IncludeSubdirs)
  169. return;
  170. }
  171. }
  172. static void StopMonitoringDirectory (KeventData data)
  173. {
  174. close(data.ev.ident);
  175. }
  176. void Monitor ()
  177. {
  178. while (!stop) {
  179. kevent ev = new kevent();
  180. ev.udata = "";
  181. kevent nullev = new kevent();
  182. nullev.udata = "";
  183. timespec ts = new timespec();
  184. ts.tv_sec = 0;
  185. ts.tv_usec = 0;
  186. int haveEvents;
  187. lock (this) {
  188. haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts);
  189. }
  190. if (haveEvents > 0) {
  191. // Restart monitoring
  192. KeventData data = (KeventData) requests [ev.ident];
  193. StartMonitoringDirectory(data);
  194. ProcessEvent (ev);
  195. } else {
  196. System.Threading.Thread.Sleep (500);
  197. }
  198. }
  199. lock (this) {
  200. thread = null;
  201. stop = false;
  202. }
  203. }
  204. void ProcessEvent (kevent ev)
  205. {
  206. lock (this) {
  207. KeventData data = (KeventData) requests [ev.ident];
  208. if (!data.Enabled)
  209. return;
  210. FileSystemWatcher fsw;
  211. string filename = "";
  212. fsw = data.FSW;
  213. FileAction fa = 0;
  214. DirectoryInfo dir = new DirectoryInfo (data.Directory);
  215. FileSystemInfo changedFsi = null;
  216. try {
  217. foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
  218. if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) {
  219. KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName];
  220. if ( (entry.LastWriteTime != fsi.LastWriteTime) || (entry.LastAccessTime != fsi.LastAccessTime) ) {
  221. filename = fsi.FullName;
  222. fa = FileAction.Modified;
  223. data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
  224. if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) {
  225. data.Directory = filename;
  226. requests [ev.ident] = data;
  227. ProcessEvent(ev);
  228. }
  229. PostEvent(filename, fsw, fa, changedFsi);
  230. }
  231. }
  232. } catch (Exception) {
  233. // The file system infos were changed while we processed them
  234. }
  235. // Deleted
  236. try {
  237. bool deleteMatched = true;
  238. while(deleteMatched) {
  239. foreach (KeventFileData entry in data.DirEntries.Values) {
  240. if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) {
  241. filename = entry.fsi.FullName;
  242. fa = FileAction.Removed;
  243. data.DirEntries.Remove (entry.fsi.FullName);
  244. PostEvent(filename, fsw, fa, changedFsi);
  245. break;
  246. }
  247. }
  248. deleteMatched = false;
  249. }
  250. } catch (Exception) {
  251. // The file system infos were changed while we processed them
  252. }
  253. // Added
  254. try {
  255. foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
  256. if (!data.DirEntries.ContainsKey (fsi.FullName)) {
  257. changedFsi = fsi;
  258. filename = fsi.FullName;
  259. fa = FileAction.Added;
  260. data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
  261. PostEvent(filename, fsw, fa, changedFsi);
  262. }
  263. } catch (Exception) {
  264. // The file system infos were changed while we processed them
  265. }
  266. }
  267. }
  268. private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
  269. RenamedEventArgs renamed = null;
  270. if (fa == 0)
  271. return;
  272. if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
  273. if (changedFsi is DirectoryInfo) {
  274. KeventData newdirdata = new KeventData ();
  275. newdirdata.FSW = fsw;
  276. newdirdata.Directory = changedFsi.FullName;
  277. newdirdata.FileMask = fsw.MangledFilter;
  278. newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
  279. newdirdata.Enabled = true;
  280. lock (this) {
  281. StartMonitoringDirectory (newdirdata);
  282. }
  283. }
  284. }
  285. if (!fsw.Pattern.IsMatch(filename))
  286. return;
  287. lock (fsw) {
  288. fsw.DispatchEvents (fa, filename, ref renamed);
  289. if (fsw.Waiting) {
  290. fsw.Waiting = false;
  291. System.Threading.Monitor.PulseAll (fsw);
  292. }
  293. }
  294. }
  295. [DllImport ("libc")]
  296. extern static int open(string path, int flags, int mode_t);
  297. [DllImport ("libc")]
  298. extern static int close(int fd);
  299. [DllImport ("libc")]
  300. extern static int kqueue();
  301. [DllImport ("libc")]
  302. extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);
  303. }
  304. }