2 // System.IO.KeventWatcher.cs: interface with osx kevent
5 // Geoff Norton (gnorton@customerdna.com)
7 // (c) 2004 Geoff Norton
8 // Copyright 2014 Xamarin Inc
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
32 using System.ComponentModel;
33 using System.Runtime.CompilerServices;
34 using System.Runtime.InteropServices;
36 using System.Threading;
41 enum EventFlags : ushort {
53 SystemFlags = unchecked (0xf000),
60 enum EventFilter : short {
74 enum FilterFlags : uint {
75 ReadPoll = EventFlags.Flag0,
76 ReadOutOfBand = EventFlags.Flag1,
77 ReadLowWaterMark = 0x00000001,
79 WriteLowWaterMark = ReadLowWaterMark,
81 NoteTrigger = 0x01000000,
82 NoteFFNop = 0x00000000,
83 NoteFFAnd = 0x40000000,
84 NoteFFOr = 0x80000000,
85 NoteFFCopy = 0xc0000000,
86 NoteFFCtrlMask = 0xc0000000,
87 NoteFFlagsMask = 0x00ffffff,
89 VNodeDelete = 0x00000001,
90 VNodeWrite = 0x00000002,
91 VNodeExtend = 0x00000004,
92 VNodeAttrib = 0x00000008,
93 VNodeLink = 0x00000010,
94 VNodeRename = 0x00000020,
95 VNodeRevoke = 0x00000040,
96 VNodeNone = 0x00000080,
98 ProcExit = 0x80000000,
99 ProcFork = 0x40000000,
100 ProcExec = 0x20000000,
101 ProcReap = 0x10000000,
102 ProcSignal = 0x08000000,
103 ProcExitStatus = 0x04000000,
104 ProcResourceEnd = 0x02000000,
107 ProcAppactive = 0x00800000,
108 ProcAppBackground = 0x00400000,
109 ProcAppNonUI = 0x00200000,
110 ProcAppInactive = 0x00100000,
111 ProcAppAllStates = 0x00f00000,
114 ProcPDataMask = 0x000fffff,
115 ProcControlMask = 0xfff00000,
117 VMPressure = 0x80000000,
118 VMPressureTerminate = 0x40000000,
119 VMPressureSuddenTerminate = 0x20000000,
120 VMError = 0x10000000,
121 TimerSeconds = 0x00000001,
122 TimerMicroSeconds = 0x00000002,
123 TimerNanoSeconds = 0x00000004,
124 TimerAbsolute = 0x00000008,
127 struct kevent : IDisposable {
129 public EventFilter filter;
130 public EventFlags flags;
131 public FilterFlags fflags;
135 public void Dispose ()
137 if (udata != IntPtr.Zero)
138 Marshal.FreeHGlobal (udata);
147 class KeventFileData {
148 public FileSystemInfo fsi;
149 public DateTime LastAccessTime;
150 public DateTime LastWriteTime;
152 public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
154 this.LastAccessTime = LastAccessTime;
155 this.LastWriteTime = LastWriteTime;
160 public FileSystemWatcher FSW;
161 public string Directory;
162 public string FileMask;
163 public bool IncludeSubdirs;
165 public Hashtable DirEntries;
169 class KeventWatcher : IFileWatcher
172 static KeventWatcher instance;
173 static Hashtable watches;
174 static Hashtable requests;
175 static Thread thread;
179 private KeventWatcher ()
184 public static bool GetInstance (out IFileWatcher watcher)
186 if (failed == true) {
191 if (instance != null) {
196 watches = Hashtable.Synchronized (new Hashtable ());
197 requests = Hashtable.Synchronized (new Hashtable ());
205 instance = new KeventWatcher ();
210 public void StartDispatching (FileSystemWatcher fsw)
214 if (thread == null) {
215 thread = new Thread (new ThreadStart (Monitor));
216 thread.IsBackground = true;
220 data = (KeventData) watches [fsw];
224 data = new KeventData ();
226 data.Directory = fsw.FullPath;
227 data.FileMask = fsw.MangledFilter;
228 data.IncludeSubdirs = fsw.IncludeSubdirectories;
232 StartMonitoringDirectory (data);
233 watches [fsw] = data;
239 static void StartMonitoringDirectory (KeventData data)
241 DirectoryInfo dir = new DirectoryInfo (data.Directory);
242 if(data.DirEntries == null) {
243 data.DirEntries = new Hashtable();
244 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
245 data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime));
248 int fd = open(data.Directory, 0, 0);
249 kevent ev = new kevent();
250 ev.udata = IntPtr.Zero;
251 timespec nullts = new timespec();
256 ev.filter = EventFilter.Vnode;
257 ev.flags = EventFlags.Add | EventFlags.Enable | EventFlags.OneShot;
258 ev.fflags = // 20 | 2 | 1 | 8;
259 FilterFlags.VNodeDelete |
260 FilterFlags.VNodeWrite |
261 FilterFlags.VNodeAttrib |
262 // The following two values are the equivalent of the original value "20", but we suspect the original author meant
263 // 0x20, we will review later with some test cases
264 FilterFlags.VNodeLink |
265 FilterFlags.VNodeExtend;
267 ev.udata = Marshal.StringToHGlobalAuto (data.Directory);
268 kevent outev = new kevent();
269 outev.udata = IntPtr.Zero;
270 kevent (conn, ref ev, 1, ref outev, 0, ref nullts);
272 requests [fd] = data;
275 if (!data.IncludeSubdirs)
280 public void StopDispatching (FileSystemWatcher fsw)
284 data = (KeventData) watches [fsw];
288 StopMonitoringDirectory (data);
289 watches.Remove (fsw);
290 if (watches.Count == 0)
293 if (!data.IncludeSubdirs)
299 static void StopMonitoringDirectory (KeventData data)
301 close(data.ev.ident);
308 kevent ev = new kevent();
309 ev.udata = IntPtr.Zero;
310 kevent nullev = new kevent();
311 nullev.udata = IntPtr.Zero;
312 timespec ts = new timespec();
317 haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts);
320 if (haveEvents > 0) {
321 // Restart monitoring
322 KeventData data = (KeventData) requests [ev.ident];
323 StopMonitoringDirectory (data);
324 StartMonitoringDirectory (data);
327 System.Threading.Thread.Sleep (500);
337 void ProcessEvent (kevent ev)
340 KeventData data = (KeventData) requests [ev.ident];
344 FileSystemWatcher fsw;
345 string filename = "";
349 DirectoryInfo dir = new DirectoryInfo (data.Directory);
350 FileSystemInfo changedFsi = null;
353 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
354 if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) {
355 KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName];
356 if (entry.LastWriteTime != fsi.LastWriteTime) {
358 fa = FileAction.Modified;
359 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
360 if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) {
361 data.Directory = filename;
362 requests [ev.ident] = data;
366 PostEvent(filename, fsw, fa, changedFsi);
369 } catch (Exception) {
370 // The file system infos were changed while we processed them
374 bool deleteMatched = true;
375 while(deleteMatched) {
376 foreach (KeventFileData entry in data.DirEntries.Values) {
377 if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) {
378 filename = entry.fsi.Name;
379 fa = FileAction.Removed;
380 data.DirEntries.Remove (entry.fsi.FullName);
381 changedFsi = entry.fsi;
382 PostEvent(filename, fsw, fa, changedFsi);
386 deleteMatched = false;
388 } catch (Exception) {
389 // The file system infos were changed while we processed them
393 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
394 if (!data.DirEntries.ContainsKey (fsi.FullName)) {
397 fa = FileAction.Added;
398 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
399 PostEvent(filename, fsw, fa, changedFsi);
401 } catch (Exception) {
402 // The file system infos were changed while we processed them
409 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
410 RenamedEventArgs renamed = null;
414 if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
415 if (changedFsi is DirectoryInfo) {
416 KeventData newdirdata = new KeventData ();
417 newdirdata.FSW = fsw;
418 newdirdata.Directory = changedFsi.FullName;
419 newdirdata.FileMask = fsw.MangledFilter;
420 newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
422 newdirdata.Enabled = true;
424 StartMonitoringDirectory (newdirdata);
429 if (!fsw.Pattern.IsMatch(filename, true))
433 if (changedFsi.FullName.StartsWith (fsw.FullPath, StringComparison.Ordinal)) {
434 if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal)) {
435 filename = changedFsi.FullName.Substring (fsw.FullPath.Length);
437 filename = changedFsi.FullName.Substring (fsw.FullPath.Length + 1);
440 fsw.DispatchEvents (fa, filename, ref renamed);
443 System.Threading.Monitor.PulseAll (fsw);
449 extern static int open(string path, int flags, int mode_t);
452 extern static int close(int fd);
455 extern static int kqueue();
458 extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);