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 ()
97 public static bool GetInstance (out IFileWatcher watcher)
104 if (instance != null) {
109 watches = Hashtable.Synchronized (new Hashtable ());
110 requests = Hashtable.Synchronized (new Hashtable ());
118 instance = new KeventWatcher ();
123 public void StartDispatching (FileSystemWatcher fsw)
127 if (thread == null) {
128 thread = new Thread (new ThreadStart (Monitor));
129 thread.IsBackground = true;
133 data = (KeventData) watches [fsw];
137 data = new KeventData ();
139 data.Directory = fsw.FullPath;
140 data.FileMask = fsw.MangledFilter;
141 data.IncludeSubdirs = fsw.IncludeSubdirectories;
145 StartMonitoringDirectory (data);
146 watches [fsw] = data;
152 static void StartMonitoringDirectory (KeventData data)
154 DirectoryInfo dir = new DirectoryInfo (data.Directory);
155 if(data.DirEntries == null) {
156 data.DirEntries = new Hashtable();
157 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
158 data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime));
161 int fd = open(data.Directory, 0, 0);
162 kevent ev = new kevent();
163 ev.udata = IntPtr.Zero;
164 timespec nullts = new timespec();
170 ev.flags = 1 | 4 | 20;
171 ev.fflags = 20 | 2 | 1 | 8;
173 ev.udata = Marshal.StringToHGlobalAuto (data.Directory);
174 kevent outev = new kevent();
175 outev.udata = IntPtr.Zero;
176 kevent (conn, ref ev, 1, ref outev, 0, ref nullts);
178 requests [fd] = data;
181 if (!data.IncludeSubdirs)
186 public void StopDispatching (FileSystemWatcher fsw)
190 data = (KeventData) watches [fsw];
194 StopMonitoringDirectory (data);
195 watches.Remove (fsw);
196 if (watches.Count == 0)
199 if (!data.IncludeSubdirs)
205 static void StopMonitoringDirectory (KeventData data)
207 close(data.ev.ident);
214 kevent ev = new kevent();
215 ev.udata = IntPtr.Zero;
216 kevent nullev = new kevent();
217 nullev.udata = IntPtr.Zero;
218 timespec ts = new timespec();
223 haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts);
226 if (haveEvents > 0) {
227 // Restart monitoring
228 KeventData data = (KeventData) requests [ev.ident];
229 StopMonitoringDirectory (data);
230 StartMonitoringDirectory (data);
233 System.Threading.Thread.Sleep (500);
243 void ProcessEvent (kevent ev)
246 KeventData data = (KeventData) requests [ev.ident];
250 FileSystemWatcher fsw;
251 string filename = "";
255 DirectoryInfo dir = new DirectoryInfo (data.Directory);
256 FileSystemInfo changedFsi = null;
259 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
260 if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) {
261 KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName];
262 if (entry.LastWriteTime != fsi.LastWriteTime) {
264 fa = FileAction.Modified;
265 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
266 if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) {
267 data.Directory = filename;
268 requests [ev.ident] = data;
271 PostEvent(filename, fsw, fa, changedFsi);
274 } catch (Exception) {
275 // The file system infos were changed while we processed them
279 bool deleteMatched = true;
280 while(deleteMatched) {
281 foreach (KeventFileData entry in data.DirEntries.Values) {
282 if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) {
283 filename = entry.fsi.Name;
284 fa = FileAction.Removed;
285 data.DirEntries.Remove (entry.fsi.FullName);
286 PostEvent(filename, fsw, fa, changedFsi);
290 deleteMatched = false;
292 } catch (Exception) {
293 // The file system infos were changed while we processed them
297 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
298 if (!data.DirEntries.ContainsKey (fsi.FullName)) {
301 fa = FileAction.Added;
302 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
303 PostEvent(filename, fsw, fa, changedFsi);
305 } catch (Exception) {
306 // The file system infos were changed while we processed them
313 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
314 RenamedEventArgs renamed = null;
318 if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
319 if (changedFsi is DirectoryInfo) {
320 KeventData newdirdata = new KeventData ();
321 newdirdata.FSW = fsw;
322 newdirdata.Directory = changedFsi.FullName;
323 newdirdata.FileMask = fsw.MangledFilter;
324 newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
326 newdirdata.Enabled = true;
328 StartMonitoringDirectory (newdirdata);
333 if (!fsw.Pattern.IsMatch(filename, true))
337 fsw.DispatchEvents (fa, filename, ref renamed);
340 System.Threading.Monitor.PulseAll (fsw);
346 extern static int open(string path, int flags, int mode_t);
349 extern static int close(int fd);
352 extern static int kqueue();
355 extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);