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;
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 changedFsi = entry.fsi;
288 PostEvent(filename, fsw, fa, changedFsi);
292 deleteMatched = false;
294 } catch (Exception) {
295 // The file system infos were changed while we processed them
299 foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
300 if (!data.DirEntries.ContainsKey (fsi.FullName)) {
303 fa = FileAction.Added;
304 data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
305 PostEvent(filename, fsw, fa, changedFsi);
307 } catch (Exception) {
308 // The file system infos were changed while we processed them
315 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
316 RenamedEventArgs renamed = null;
320 if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
321 if (changedFsi is DirectoryInfo) {
322 KeventData newdirdata = new KeventData ();
323 newdirdata.FSW = fsw;
324 newdirdata.Directory = changedFsi.FullName;
325 newdirdata.FileMask = fsw.MangledFilter;
326 newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
328 newdirdata.Enabled = true;
330 StartMonitoringDirectory (newdirdata);
335 if (!fsw.Pattern.IsMatch(filename, true))
339 if (changedFsi.FullName.StartsWith (fsw.FullPath, StringComparison.Ordinal)) {
340 if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal)) {
341 filename = changedFsi.FullName.Substring (fsw.FullPath.Length);
343 filename = changedFsi.FullName.Substring (fsw.FullPath.Length + 1);
346 fsw.DispatchEvents (fa, filename, ref renamed);
349 System.Threading.Monitor.PulseAll (fsw);
355 extern static int open(string path, int flags, int mode_t);
358 extern static int close(int fd);
361 extern static int kqueue();
364 extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);