2 // System.IO.KeventWatcher.cs: interface with osx kevent
5 // Geoff Norton (gnorton@customerdna.com)
7 // (c) 2004 Geoff Norton
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;
40 struct kevent : IDisposable {
48 public void Dispose ()
50 if (udata != IntPtr.Zero)
51 Marshal.FreeHGlobal (udata);
60 class KeventFileData {
61 public FileSystemInfo fsi;
62 public DateTime LastAccessTime;
63 public DateTime LastWriteTime;
65 public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
67 this.LastAccessTime = LastAccessTime;
68 this.LastWriteTime = LastWriteTime;
73 public FileSystemWatcher FSW;
74 public string Directory;
75 public string FileMask;
76 public bool IncludeSubdirs;
78 public Hashtable DirEntries;
82 class KeventWatcher : IFileWatcher
85 static KeventWatcher instance;
86 static Hashtable watches;
87 static Hashtable requests;
92 private KeventWatcher ()
96 public static bool GetInstance (out IFileWatcher watcher)
98 lock (typeof (KeventWatcher)) {
104 if (instance != null) {
109 watches = Hashtable.Synchronized (new Hashtable ());
110 requests = Hashtable.Synchronized (new Hashtable ());
118 instance = new KeventWatcher ();
124 public void StartDispatching (FileSystemWatcher fsw)
128 if (thread == null) {
129 thread = new Thread (new ThreadStart (Monitor));
130 thread.IsBackground = true;
134 data = (KeventData) watches [fsw];
138 data = new KeventData ();
140 data.Directory = fsw.FullPath;
141 data.FileMask = fsw.MangledFilter;
142 data.IncludeSubdirs = fsw.IncludeSubdirectories;
146 StartMonitoringDirectory (data);
147 watches [fsw] = data;
153 static void StartMonitoringDirectory (KeventData data)
155 DirectoryInfo dir = new DirectoryInfo (data.Directory);
156 if(data.DirEntries == null) {
157 data.DirEntries = new Hashtable();
158 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
159 data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime));
162 int fd = open(data.Directory, 0, 0);
163 kevent ev = new kevent();
164 ev.udata = IntPtr.Zero;
165 timespec nullts = new timespec();
171 ev.flags = 1 | 4 | 20;
172 ev.fflags = 20 | 2 | 1 | 8;
174 ev.udata = Marshal.StringToHGlobalAuto (data.Directory);
175 kevent outev = new kevent();
176 outev.udata = IntPtr.Zero;
177 kevent (conn, ref ev, 1, ref outev, 0, ref nullts);
179 requests [fd] = data;
182 if (!data.IncludeSubdirs)
187 public void StopDispatching (FileSystemWatcher fsw)
191 data = (KeventData) watches [fsw];
195 StopMonitoringDirectory (data);
196 watches.Remove (fsw);
197 if (watches.Count == 0)
200 if (!data.IncludeSubdirs)
206 static void StopMonitoringDirectory (KeventData data)
208 close(data.ev.ident);
215 kevent ev = new kevent();
216 ev.udata = IntPtr.Zero;
217 kevent nullev = new kevent();
218 nullev.udata = IntPtr.Zero;
219 timespec ts = new timespec();
224 haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts);
227 if (haveEvents > 0) {
228 // Restart monitoring
229 KeventData data = (KeventData) requests [ev.ident];
230 StopMonitoringDirectory (data);
231 StartMonitoringDirectory (data);
234 System.Threading.Thread.Sleep (500);
244 void ProcessEvent (kevent ev)
247 KeventData data = (KeventData) requests [ev.ident];
251 FileSystemWatcher fsw;
252 string filename = "";
256 DirectoryInfo dir = new DirectoryInfo (data.Directory);
257 FileSystemInfo changedFsi = null;
260 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
261 if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) {
262 KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName];
263 if (entry.LastWriteTime != fsi.LastWriteTime) {
265 fa = FileAction.Modified;
266 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
267 if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) {
268 data.Directory = filename;
269 requests [ev.ident] = data;
272 PostEvent(filename, fsw, fa, changedFsi);
275 } catch (Exception) {
276 // The file system infos were changed while we processed them
280 bool deleteMatched = true;
281 while(deleteMatched) {
282 foreach (KeventFileData entry in data.DirEntries.Values) {
283 if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) {
284 filename = entry.fsi.Name;
285 fa = FileAction.Removed;
286 data.DirEntries.Remove (entry.fsi.FullName);
287 PostEvent(filename, fsw, fa, changedFsi);
291 deleteMatched = false;
293 } catch (Exception) {
294 // The file system infos were changed while we processed them
298 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
299 if (!data.DirEntries.ContainsKey (fsi.FullName)) {
302 fa = FileAction.Added;
303 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
304 PostEvent(filename, fsw, fa, changedFsi);
306 } catch (Exception) {
307 // The file system infos were changed while we processed them
314 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
315 RenamedEventArgs renamed = null;
319 if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
320 if (changedFsi is DirectoryInfo) {
321 KeventData newdirdata = new KeventData ();
322 newdirdata.FSW = fsw;
323 newdirdata.Directory = changedFsi.FullName;
324 newdirdata.FileMask = fsw.MangledFilter;
325 newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
327 newdirdata.Enabled = true;
329 StartMonitoringDirectory (newdirdata);
334 if (!fsw.Pattern.IsMatch(filename, true))
338 fsw.DispatchEvents (fa, filename, ref renamed);
341 System.Threading.Monitor.PulseAll (fsw);
347 extern static int open(string path, int flags, int mode_t);
350 extern static int close(int fd);
353 extern static int kqueue();
356 extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);