context: // 
  // 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, false);
  				lock (this) {
  					watches [fsw] = data;
  					requests [data.Request.ReqNum] = data;
  					stop = false;
  				}
  			}
  		}
  
  		static void StartMonitoringDirectory (FAMData data, bool justcreated)
  		{
  			FAMRequest fr;
  			FileSystemWatcher fsw;
  			if (FAMMonitorDirectory (ref conn, data.Directory, out fr, IntPtr.Zero) == -1)
  				throw new Win32Exception ();
  
  			fsw = data.FSW;
  			data.Request = fr;
  
  			if (data.IncludeSubdirs) {
  				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;
  
  					if (justcreated) {
  						lock (fsw) {
  							RenamedEventArgs renamed = null;
  
  							fsw.DispatchEvents (FileAction.Added, directory, ref renamed);
  
  							if (fsw.Waiting) {
  								fsw.Waiting = false;
  								System.Threading.Monitor.PulseAll (fsw);
  							}
  						}
  					}
  
  					StartMonitoringDirectory (fd, justcreated);
  					data.SubDirs [directory] = fd;
  					requests [fd.Request.ReqNum] = fd;
  				}
  			}
  
  			if (justcreated) {
  				foreach (string filename in Directory.GetFiles(data.Directory)) {
  					lock (fsw) {
  						RenamedEventArgs renamed = null;
  
  						fsw.DispatchEvents (FileAction.Added, filename, ref renamed);
  						/* If a file has been created, then it has been written to */
  						fsw.DispatchEvents (FileAction.Modified, filename, ref renamed);
  
  						if (fsw.Waiting) {
  							fsw.Waiting = false;
  							System.Threading.Monitor.PulseAll(fsw);
  						}
  					}
  				}
  			}
  		}
  
  		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) {
  							int len = full.Length;
  							int slash = 1;
  							if (len > 1 && full [len - 1] == Path.DirectorySeparatorChar)
  								slash = 0;
  							string reldir = datadir.Substring (full.Length + slash);
  							datadir = Path.Combine (datadir, filename);
  							filename = Path.Combine (reldir, filename);
  						} else {
  							datadir = Path.Combine (full, 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);
  						}
  					}
  
  					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 += 2) {
  					FAMData newdir = (FAMData) newdirs [n];
  					FAMData parent = (FAMData) newdirs [n + 1];
  					StartMonitoringDirectory (newdir, true);
  					requests [newdir.Request.ReqNum] = newdir;
  
gt: 					lock (parent) {