// // System.IO.KeventWatcher.cs: interface with osx kevent // // Authors: // Geoff Norton (gnorton@customerdna.com) // // (c) 2004 Geoff Norton // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; namespace System.IO { struct kevent : IDisposable { public int ident; public short filter; public ushort flags; public uint fflags; public int data; public IntPtr udata; public void Dispose () { if (udata != IntPtr.Zero) Marshal.FreeHGlobal (udata); } } struct timespec { public int tv_sec; public int tv_usec; } class KeventFileData { public FileSystemInfo fsi; public DateTime LastAccessTime; public DateTime LastWriteTime; public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) { this.fsi = fsi; this.LastAccessTime = LastAccessTime; this.LastWriteTime = LastWriteTime; } } class KeventData { public FileSystemWatcher FSW; public string Directory; public string FileMask; public bool IncludeSubdirs; public bool Enabled; public Hashtable DirEntries; public kevent ev; } class KeventWatcher : IFileWatcher { static bool failed; static KeventWatcher instance; static Hashtable watches; static Hashtable requests; static Thread thread; static int conn; static bool stop; private KeventWatcher () { } // Locked by caller public static bool GetInstance (out IFileWatcher watcher) { if (failed == true) { watcher = null; return false; } if (instance != null) { watcher = instance; return true; } watches = Hashtable.Synchronized (new Hashtable ()); requests = Hashtable.Synchronized (new Hashtable ()); conn = kqueue(); if (conn == -1) { failed = true; watcher = null; return false; } instance = new KeventWatcher (); watcher = instance; return true; } public void StartDispatching (FileSystemWatcher fsw) { KeventData data; lock (this) { if (thread == null) { thread = new Thread (new ThreadStart (Monitor)); thread.IsBackground = true; thread.Start (); } data = (KeventData) watches [fsw]; } if (data == null) { data = new KeventData (); data.FSW = fsw; data.Directory = fsw.FullPath; data.FileMask = fsw.MangledFilter; data.IncludeSubdirs = fsw.IncludeSubdirectories; data.Enabled = true; lock (this) { StartMonitoringDirectory (data); watches [fsw] = data; stop = false; } } } static void StartMonitoringDirectory (KeventData data) { DirectoryInfo dir = new DirectoryInfo (data.Directory); if(data.DirEntries == null) { data.DirEntries = new Hashtable(); foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() ) data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime)); } int fd = open(data.Directory, 0, 0); kevent ev = new kevent(); ev.udata = IntPtr.Zero; timespec nullts = new timespec(); nullts.tv_sec = 0; nullts.tv_usec = 0; if (fd > 0) { ev.ident = fd; ev.filter = -4; ev.flags = 1 | 4 | 20; ev.fflags = 20 | 2 | 1 | 8; ev.data = 0; ev.udata = Marshal.StringToHGlobalAuto (data.Directory); kevent outev = new kevent(); outev.udata = IntPtr.Zero; kevent (conn, ref ev, 1, ref outev, 0, ref nullts); data.ev = ev; requests [fd] = data; } if (!data.IncludeSubdirs) return; } public void StopDispatching (FileSystemWatcher fsw) { KeventData data; lock (this) { data = (KeventData) watches [fsw]; if (data == null) return; StopMonitoringDirectory (data); watches.Remove (fsw); if (watches.Count == 0) stop = true; if (!data.IncludeSubdirs) return; } } static void StopMonitoringDirectory (KeventData data) { close(data.ev.ident); } void Monitor () { while (!stop) { kevent ev = new kevent(); ev.udata = IntPtr.Zero; kevent nullev = new kevent(); nullev.udata = IntPtr.Zero; timespec ts = new timespec(); ts.tv_sec = 0; ts.tv_usec = 0; int haveEvents; lock (this) { haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts); } if (haveEvents > 0) { // Restart monitoring KeventData data = (KeventData) requests [ev.ident]; StopMonitoringDirectory (data); StartMonitoringDirectory (data); ProcessEvent (ev); } else { System.Threading.Thread.Sleep (500); } } lock (this) { thread = null; stop = false; } } void ProcessEvent (kevent ev) { lock (this) { KeventData data = (KeventData) requests [ev.ident]; if (!data.Enabled) return; FileSystemWatcher fsw; string filename = ""; fsw = data.FSW; FileAction fa = 0; DirectoryInfo dir = new DirectoryInfo (data.Directory); FileSystemInfo changedFsi = null; try { foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() ) if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) { KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName]; if (entry.LastWriteTime != fsi.LastWriteTime) { filename = fsi.Name; fa = FileAction.Modified; data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime); if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) { data.Directory = filename; requests [ev.ident] = data; ProcessEvent(ev); } PostEvent(filename, fsw, fa, changedFsi); } } } catch (Exception) { // The file system infos were changed while we processed them } // Deleted try { bool deleteMatched = true; while(deleteMatched) { foreach (KeventFileData entry in data.DirEntries.Values) { if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) { filename = entry.fsi.Name; fa = FileAction.Removed; data.DirEntries.Remove (entry.fsi.FullName); PostEvent(filename, fsw, fa, changedFsi); break; } } deleteMatched = false; } } catch (Exception) { // The file system infos were changed while we processed them } // Added try { foreach (FileSystemInfo fsi in dir.GetFileSystemInfos()) if (!data.DirEntries.ContainsKey (fsi.FullName)) { changedFsi = fsi; filename = fsi.Name; fa = FileAction.Added; data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime); PostEvent(filename, fsw, fa, changedFsi); } } catch (Exception) { // The file system infos were changed while we processed them } } } private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) { RenamedEventArgs renamed = null; if (fa == 0) return; if (fsw.IncludeSubdirectories && fa == FileAction.Added) { if (changedFsi is DirectoryInfo) { KeventData newdirdata = new KeventData (); newdirdata.FSW = fsw; newdirdata.Directory = changedFsi.FullName; newdirdata.FileMask = fsw.MangledFilter; newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories; newdirdata.Enabled = true; lock (this) { StartMonitoringDirectory (newdirdata); } } } if (!fsw.Pattern.IsMatch(filename, true)) return; lock (fsw) { fsw.DispatchEvents (fa, filename, ref renamed); if (fsw.Waiting) { fsw.Waiting = false; System.Threading.Monitor.PulseAll (fsw); } } } [DllImport ("libc")] extern static int open(string path, int flags, int mode_t); [DllImport ("libc")] extern static int close(int fd); [DllImport ("libc")] extern static int kqueue(); [DllImport ("libc")] extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts); } }