// // System.IO.FAM.cs: interface with libfam // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // // (c) 2004 Novell, Inc. (http://www.novell.com) // // // 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 FAMConnection { public int FD; public IntPtr opaque; } struct FAMRequest { public int ReqNum; } enum FAMCodes { Changed = 1, Deleted = 2, StartExecuting = 3, StopExecuting = 4, Created = 5, Moved = 6, Acknowledge = 7, Exists = 8, EndExist = 9 }; class FAMData { public FileSystemWatcher FSW; public string Directory; public string FileMask; public bool IncludeSubdirs; public bool Enabled; public FAMRequest Request; public Hashtable SubDirs; } class FAMWatcher : IFileWatcher { static bool failed; static FAMWatcher instance; static Hashtable watches; static Hashtable requests; static FAMConnection conn; static Thread thread; static bool stop; static bool use_gamin; private FAMWatcher () { } // Locked by caller public static bool GetInstance (out IFileWatcher watcher, bool gamin) { if (failed == true) { watcher = null; return false; } if (instance != null) { watcher = instance; return true; } use_gamin = gamin; watches = Hashtable.Synchronized (new Hashtable ()); requests = Hashtable.Synchronized (new Hashtable ()); if (FAMOpen (out conn) == -1) { failed = true; watcher = null; return false; } instance = new FAMWatcher (); watcher = instance; return true; } public void StartDispatching (FileSystemWatcher fsw) { FAMData data; lock (this) { if (thread == null) { thread = new Thread (new ThreadStart (Monitor)); thread.IsBackground = true; thread.Start (); } data = (FAMData) watches [fsw]; } if (data == null) { data = new FAMData (); data.FSW = fsw; data.Directory = fsw.FullPath; data.FileMask = fsw.MangledFilter; data.IncludeSubdirs = fsw.IncludeSubdirectories; if (data.IncludeSubdirs) data.SubDirs = new Hashtable (); data.Enabled = true; StartMonitoringDirectory (data); lock (this) { watches [fsw] = data; requests [data.Request.ReqNum] = data; stop = false; } } } static void StartMonitoringDirectory (FAMData data) { FAMRequest fr; if (FAMMonitorDirectory (ref conn, data.Directory, out fr, IntPtr.Zero) == -1) throw new Win32Exception (); data.Request = fr; if (!data.IncludeSubdirs) return; foreach (string directory in Directory.GetDirectories (data.Directory)) { FAMData fd = new FAMData (); fd.FSW = data.FSW; fd.Directory = directory; fd.FileMask = data.FSW.MangledFilter; fd.IncludeSubdirs = true; fd.SubDirs = new Hashtable (); fd.Enabled = true; StartMonitoringDirectory (fd); data.SubDirs [directory] = fd; requests [fd.Request.ReqNum] = fd; } } public void StopDispatching (FileSystemWatcher fsw) { FAMData data; lock (this) { data = (FAMData) watches [fsw]; if (data == null) return; StopMonitoringDirectory (data); watches.Remove (fsw); requests.Remove (data.Request.ReqNum); if (watches.Count == 0) stop = true; if (!data.IncludeSubdirs) return; foreach (FAMData fd in data.SubDirs.Values) { StopMonitoringDirectory (fd); requests.Remove (fd.Request.ReqNum); } } } static void StopMonitoringDirectory (FAMData data) { if (FAMCancelMonitor (ref conn, ref data.Request) == -1) throw new Win32Exception (); } void Monitor () { while (!stop) { int haveEvents; lock (this) { haveEvents = FAMPending (ref conn); } if (haveEvents > 0) { ProcessEvents (); } else { Thread.Sleep (500); } } lock (this) { thread = null; stop = false; } } const NotifyFilters changed = NotifyFilters.Attributes | NotifyFilters.LastAccess | NotifyFilters.Size | NotifyFilters.LastWrite; void ProcessEvents () { ArrayList newdirs = null; lock (this) { do { int code; string filename; int requestNumber; FileSystemWatcher fsw; if (InternalFAMNextEvent (ref conn, out filename, out code, out requestNumber) != 1) return; bool found = false; switch ((FAMCodes) code) { case FAMCodes.Changed: case FAMCodes.Deleted: case FAMCodes.Created: found = requests.ContainsKey (requestNumber); break; case FAMCodes.Moved: case FAMCodes.StartExecuting: case FAMCodes.StopExecuting: case FAMCodes.Acknowledge: case FAMCodes.Exists: case FAMCodes.EndExist: default: found = false; break; } if (!found) continue; FAMData data = (FAMData) requests [requestNumber]; if (!data.Enabled) continue; fsw = data.FSW; NotifyFilters flt = fsw.NotifyFilter; RenamedEventArgs renamed = null; FileAction fa = 0; if (code == (int) FAMCodes.Changed && (flt & changed) != 0) fa = FileAction.Modified; else if (code == (int) FAMCodes.Deleted) fa = FileAction.Removed; else if (code == (int) FAMCodes.Created) fa = FileAction.Added; if (fa == 0) continue; if (fsw.IncludeSubdirectories) { string full = fsw.FullPath; string datadir = data.Directory; if (datadir != full) { string reldir = datadir.Substring (full.Length + 1); datadir = Path.Combine (datadir, filename); filename = Path.Combine (reldir, filename); } else { datadir = Path.Combine (fsw.FullPath, filename); } if (fa == FileAction.Added && Directory.Exists (datadir)) { if (newdirs == null) newdirs = new ArrayList (4); FAMData fd = new FAMData (); fd.FSW = fsw; fd.Directory = datadir; fd.FileMask = fsw.MangledFilter; fd.IncludeSubdirs = true; fd.SubDirs = new Hashtable (); fd.Enabled = true; newdirs.Add (fd); newdirs.Add (data); requests [fd.Request.ReqNum] = fd; } } if (filename != data.Directory && !fsw.Pattern.IsMatch (filename)) continue; lock (fsw) { fsw.DispatchEvents (fa, filename, ref renamed); if (fsw.Waiting) { fsw.Waiting = false; System.Threading.Monitor.PulseAll (fsw); } } } while (FAMPending (ref conn) > 0); } if (newdirs != null) { int count = newdirs.Count; for (int n = 0; n < count; n++) { FAMData newdir = (FAMData) newdirs [n]; FAMData parent = (FAMData) newdirs [n + 1]; StartMonitoringDirectory (newdir); lock (parent) { parent.SubDirs [newdir.Directory] = newdir; } } newdirs.Clear (); } } ~FAMWatcher () { FAMClose (ref conn); } static int FAMOpen (out FAMConnection fc) { if (use_gamin) return gamin_Open (out fc); return fam_Open (out fc); } static int FAMClose (ref FAMConnection fc) { if (use_gamin) return gamin_Close (ref fc); return fam_Close (ref fc); } static int FAMMonitorDirectory (ref FAMConnection fc, string filename, out FAMRequest fr, IntPtr user_data) { if (use_gamin) return gamin_MonitorDirectory (ref fc, filename, out fr, user_data); return fam_MonitorDirectory (ref fc, filename, out fr, user_data); } static int FAMCancelMonitor (ref FAMConnection fc, ref FAMRequest fr) { if (use_gamin) return gamin_CancelMonitor (ref fc, ref fr); return fam_CancelMonitor (ref fc, ref fr); } static int FAMPending (ref FAMConnection fc) { if (use_gamin) return gamin_Pending (ref fc); return fam_Pending (ref fc); } [DllImport ("libfam.so.0", EntryPoint="FAMOpen")] extern static int fam_Open (out FAMConnection fc); [DllImport ("libfam.so.0", EntryPoint="FAMClose")] extern static int fam_Close (ref FAMConnection fc); [DllImport ("libfam.so.0", EntryPoint="FAMMonitorDirectory")] extern static int fam_MonitorDirectory (ref FAMConnection fc, string filename, out FAMRequest fr, IntPtr user_data); [DllImport ("libfam.so.0", EntryPoint="FAMCancelMonitor")] extern static int fam_CancelMonitor (ref FAMConnection fc, ref FAMRequest fr); [DllImport ("libfam.so.0", EntryPoint="FAMPending")] extern static int fam_Pending (ref FAMConnection fc); [DllImport ("libgamin-1.so.0", EntryPoint="FAMOpen")] extern static int gamin_Open (out FAMConnection fc); [DllImport ("libgamin-1.so.0", EntryPoint="FAMClose")] extern static int gamin_Close (ref FAMConnection fc); [DllImport ("libgamin-1.so.0", EntryPoint="FAMMonitorDirectory")] extern static int gamin_MonitorDirectory (ref FAMConnection fc, string filename, out FAMRequest fr, IntPtr user_data); [DllImport ("libgamin-1.so.0", EntryPoint="FAMCancelMonitor")] extern static int gamin_CancelMonitor (ref FAMConnection fc, ref FAMRequest fr); [DllImport ("libgamin-1.so.0", EntryPoint="FAMPending")] extern static int gamin_Pending (ref FAMConnection fc); [MethodImplAttribute(MethodImplOptions.InternalCall)] extern static int InternalFAMNextEvent (ref FAMConnection fc, out string filename, out int code, out int reqnum); } }