2004-01-27 Nick Drochak <ndrochak@ieee.org>
[mono.git] / mcs / class / System / System.IO / FileSystemWatcher.cs
index d5003bb912f21d36575fbe4f10622d6a7763b82d..883c81b76d537e1a94236d52e06e35dd15c141ce 100644 (file)
-// \r
-// System.IO.FileSystemWatcher.cs\r
-//\r
-// Author:\r
-//   Tim Coleman (tim@timcoleman.com)\r
-//\r
-// Copyright (C) Tim Coleman, 2002\r
-//\r
-\r
-using System;\r
-using System.ComponentModel;\r
-\r
-namespace System.IO {\r
-       public class FileSystemWatcher : Component, ISupportInitialize {\r
-\r
-               #region Fields\r
-\r
-               bool enableRaisingEvents;\r
-               string filter;\r
-               bool includeSubdirectories;\r
-               int internalBufferSize;\r
-               NotifyFilters notifyFilter;\r
-               string path;\r
-               ISite site;\r
-               ISynchronizeInvoke synchronizingObject;\r
-\r
-               #endregion // Fields\r
-\r
-               #region Constructors\r
-\r
-               public FileSystemWatcher ()\r
-                       : this (String.Empty, String.Empty)\r
-               {\r
-               }\r
-\r
-               public FileSystemWatcher (string path)\r
-                       : this (path, String.Empty)\r
-               {\r
-               }\r
-\r
-               [MonoTODO]\r
-               public FileSystemWatcher (string path, string filter)\r
-               {\r
-                       if (path == null)\r
-                               throw new ArgumentNullException ();\r
-                       if (filter == null)\r
-                               throw new ArgumentNullException ();\r
-                       if (path == String.Empty)\r
-                               throw new ArgumentException ();\r
-\r
-                       // if the path does not exist throw an ArgumentException\r
-\r
-                       this.enableRaisingEvents = false;\r
-                       this.filter = filter;\r
-                       this.includeSubdirectories = false;\r
-                       this.internalBufferSize = 8192;\r
-                       this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;\r
-                       this.path = path;\r
-                       this.synchronizingObject = null;\r
-               }\r
-\r
-               #endregion // Constructors\r
-\r
-               #region Properties\r
-\r
-               public bool EnableRaisingEvents {\r
-                       get { return enableRaisingEvents; }\r
-                       set { enableRaisingEvents = value; }\r
-               }\r
-\r
-               public string Filter {\r
-                       get { return filter; }\r
-                       set { filter = value; }\r
-               }\r
-\r
-               public bool IncludeSubdirectories {\r
-                       get { return includeSubdirectories; }\r
-                       set { includeSubdirectories = value; }\r
-               }\r
-\r
-               public int InternalBufferSize {\r
-                       get { return internalBufferSize; }\r
-                       set { internalBufferSize = value; }\r
-               }\r
-\r
-               public NotifyFilters NotifyFilter {\r
-                       get { return notifyFilter; }\r
-                       [MonoTODO ("Perform validation.")]\r
-                       set { notifyFilter = value; }\r
-               }\r
-\r
-               public string Path {\r
-                       get { return path; }\r
-                       [MonoTODO ("Perform validation.")]\r
-                       set { path = value; }\r
-               }\r
-\r
-               public override ISite Site {\r
-                       get { return site; }\r
-                       set { site = value; }\r
-               }\r
-\r
-               public ISynchronizeInvoke SynchronizingObject {\r
-                       get { return synchronizingObject; }\r
-                       set { synchronizingObject = value; }\r
-               }\r
-\r
-               #endregion // Properties\r
-\r
-               #region Methods\r
-       \r
-               [MonoTODO]\r
-               public void BeginInit ()\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected override void Dispose (bool disposing)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               public void EndInit ()\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected void OnChanged (FileSystemEventArgs e)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected void OnCreated (FileSystemEventArgs e)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected void OnDeleted (FileSystemEventArgs e)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected void OnError (ErrorEventArgs e)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               protected void OnRenamed (RenamedEventArgs e)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-               [MonoTODO]\r
-               public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)\r
-               {\r
-                       throw new NotImplementedException (); \r
-               }\r
-\r
-\r
-               #endregion // Methods\r
-\r
-               #region Events and Delegates\r
-\r
-               public event FileSystemEventHandler Changed;\r
-               public event FileSystemEventHandler Created;\r
-               public event FileSystemEventHandler Deleted;\r
-               public event ErrorEventHandler Error;\r
-               public event RenamedEventHandler Renamed;\r
-\r
-               #endregion // Events and Delegates\r
-       }\r
-}\r
+// 
+// System.IO.FileSystemWatcher.cs
+//
+// Authors:
+//     Tim Coleman (tim@timcoleman.com)
+//     Gonzalo Paniagua Javier (gonzalo@ximian.com)
+//
+// Copyright (C) Tim Coleman, 2002 
+// (c) 2003 Ximian, Inc. (http://www.ximian.com)
+// (c) 2004 Novell, Inc. (http://www.novell.com)
+//
+
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.IO {
+       [DefaultEvent("Changed")]
+       public class FileSystemWatcher : Component, ISupportInitialize {
+
+               #region Fields
+
+               bool enableRaisingEvents;
+               string filter;
+               bool includeSubdirectories;
+               int internalBufferSize;
+               NotifyFilters notifyFilter;
+               string path;
+               string fullpath;
+               ISynchronizeInvoke synchronizingObject;
+               WaitForChangedResult lastData;
+               bool waiting;
+               SearchPattern2 pattern;
+               static IFileWatcher watcher;
+
+               #endregion // Fields
+
+               #region Constructors
+
+               public FileSystemWatcher ()
+               {
+                       this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
+                       this.enableRaisingEvents = false;
+                       this.filter = "*.*";
+                       this.includeSubdirectories = false;
+                       this.internalBufferSize = 8192;
+                       this.path = "";
+                       InitWatcher ();
+               }
+
+               public FileSystemWatcher (string path)
+                       : this (path, "*.*")
+               {
+               }
+
+               public FileSystemWatcher (string path, string filter)
+               {
+                       if (path == null)
+                               throw new ArgumentNullException ("path");
+
+                       if (filter == null)
+                               throw new ArgumentNullException ("filter");
+
+                       if (path == String.Empty)
+                               throw new ArgumentException ("Empty path", "path");
+
+                       if (!Directory.Exists (path))
+                               throw new ArgumentException ("Directory does not exists", "path");
+
+                       this.enableRaisingEvents = false;
+                       this.filter = filter;
+                       this.includeSubdirectories = false;
+                       this.internalBufferSize = 8192;
+                       this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
+                       this.path = path;
+                       this.synchronizingObject = null;
+                       InitWatcher ();
+               }
+
+               void InitWatcher ()
+               {
+                       lock (typeof (FileSystemWatcher)) {
+                               if (watcher != null)
+                                       return;
+
+                               string managed = Environment.GetEnvironmentVariable ("MONO_MANAGED_WATCHER");
+                               int mode = 0;
+                               if (managed == null)
+                                       mode = InternalSupportsFSW ();
+
+                               bool ok = false;
+                               if (mode == 2)
+                                       ok = FAMWatcher.GetInstance (out watcher);
+                               else if (mode == 1)
+                                       ok = DefaultWatcher.GetInstance (out watcher);
+                                       //ok = WindowsWatcher.GetInstance (out watcher);
+
+                               if (mode == 0 || !ok)
+                                       DefaultWatcher.GetInstance (out watcher);
+                       }
+               }
+
+               #endregion // Constructors
+
+               #region Properties
+
+               /* If this is enabled, we Pulse this instance */
+               internal bool Waiting {
+                       get { return waiting; }
+                       set { waiting = value; }
+               }
+
+               internal SearchPattern2 Pattern {
+                       get {
+                               if (pattern == null) {
+                                       string f = Filter;
+                                       if (f == "*.*" && !(watcher.GetType () == typeof (WindowsWatcher)))
+                                               f = "*";
+
+                                       pattern = new SearchPattern2 (f);
+                               }
+                               return pattern;
+                       }
+               }
+
+               internal string FullPath {
+                       get {
+                               if (fullpath == null) {
+                                       if (path == null || path == "")
+                                               fullpath = Environment.CurrentDirectory;
+                                       else
+                                               fullpath = System.IO.Path.GetFullPath (path);
+                               }
+
+                               return fullpath;
+                       }
+               }
+
+               [DefaultValue(false)]
+               [IODescription("Flag to indicate if this instance is active")]
+               public bool EnableRaisingEvents {
+                       get { return enableRaisingEvents; }
+                       set {
+                               if (value == enableRaisingEvents)
+                                       return; // Do nothing
+
+                               enableRaisingEvents = value;
+                               if (value) {
+                                       Start ();
+                               } else {
+                                       Stop ();
+                               }
+                       }
+               }
+
+               [DefaultValue("*.*")]
+               [IODescription("File name filter pattern")]
+               [RecommendedAsConfigurable(true)]
+               [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
+               public string Filter {
+                       get { return filter; }
+                       set {
+                               if (value == null || value == "")
+                                       value = "*.*";
+
+                               if (filter != value) {
+                                       filter = value;
+                                       pattern = null;
+                               }
+                       }
+               }
+
+               [DefaultValue(false)]
+               [IODescription("Flag to indicate we want to watch subdirectories")]
+               public bool IncludeSubdirectories {
+                       get { return includeSubdirectories; }
+                       set {
+                               if (includeSubdirectories == value)
+                                       return;
+
+                               includeSubdirectories = value;
+                               if (value && enableRaisingEvents) {
+                                       Stop ();
+                                       Start ();
+                               }
+                       }
+               }
+
+               [Browsable(false)]
+               [DefaultValue(8192)]
+               public int InternalBufferSize {
+                       get { return internalBufferSize; }
+                       set {
+                               if (internalBufferSize == value)
+                                       return;
+
+                               if (value < 4196)
+                                       value = 4196;
+
+                               internalBufferSize = value;
+                               if (enableRaisingEvents) {
+                                       Stop ();
+                                       Start ();
+                               }
+                       }
+               }
+
+               [DefaultValue(NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite)]
+               [IODescription("Flag to indicate which change event we want to monitor")]
+               public NotifyFilters NotifyFilter {
+                       get { return notifyFilter; }
+                       set {
+                               if (notifyFilter == value)
+                                       return;
+                                       
+                               notifyFilter = value;
+                               if (enableRaisingEvents) {
+                                       Stop ();
+                                       Start ();
+                               }
+                       }
+               }
+
+               [DefaultValue("")]
+               [IODescription("The directory to monitor")]
+               [RecommendedAsConfigurable(true)]
+               [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
+               [Editor ("System.Diagnostics.Design.FSWPathEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
+               public string Path {
+                       get { return path; }
+                       set {
+                               if (path == value)
+                                       return;
+
+                               bool exists = false;
+                               Exception exc = null;
+
+                               try {
+                                       exists = Directory.Exists (value);
+                               } catch (Exception e) {
+                                       exc = e;
+                               }
+
+                               if (exc != null)
+                                       throw new ArgumentException ("Invalid directory name", "value", exc);
+
+                               if (!exists)
+                                       throw new ArgumentException ("Directory does not exists", "value");
+
+                               path = value;
+                               fullpath = null;
+                               if (enableRaisingEvents) {
+                                       Stop ();
+                                       Start ();
+                               }
+                       }
+               }
+
+               [Browsable(false)]
+               public override ISite Site {
+                       get { return base.Site; }
+                       set { base.Site = value; }
+               }
+
+               [DefaultValue(null)]
+               [IODescription("The object used to marshal the event handler calls resulting from a directory change")]
+               public ISynchronizeInvoke SynchronizingObject {
+                       get { return synchronizingObject; }
+                       set { synchronizingObject = value; }
+               }
+
+               #endregion // Properties
+
+               #region Methods
+       
+               [MonoTODO]
+               public void BeginInit ()
+               {
+                       throw new NotImplementedException (); 
+               }
+
+               protected override void Dispose (bool disposing)
+               {
+                       if (disposing) {
+                               Stop ();
+                       }
+                       base.Dispose (disposing);
+               }
+
+               [MonoTODO]
+               public void EndInit ()
+               {
+                       throw new NotImplementedException (); 
+               }
+
+               private void RaiseEvent (Delegate ev, EventArgs arg)
+               {
+                       if (ev == null)
+                               return;
+
+                       object [] args = new object [] {this, arg};
+
+                       if (synchronizingObject == null) {
+                               ev.DynamicInvoke (args);
+                               return;
+                       }
+                       
+                       synchronizingObject.BeginInvoke (ev, args);
+               }
+
+               protected void OnChanged (FileSystemEventArgs e)
+               {
+                       RaiseEvent (Changed, e);
+               }
+
+               protected void OnCreated (FileSystemEventArgs e)
+               {
+                       RaiseEvent (Created, e);
+               }
+
+               protected void OnDeleted (FileSystemEventArgs e)
+               {
+                       RaiseEvent (Deleted, e);
+               }
+
+               protected void OnError (ErrorEventArgs e)
+               {
+                       RaiseEvent (Error, e);
+               }
+
+               protected void OnRenamed (RenamedEventArgs e)
+               {
+                       RaiseEvent (Renamed, e);
+               }
+
+               public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
+               {
+                       return WaitForChanged (changeType, Timeout.Infinite);
+               }
+
+               public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)
+               {
+                       WaitForChangedResult result = new WaitForChangedResult ();
+                       bool prevEnabled = EnableRaisingEvents;
+                       if (!prevEnabled)
+                               EnableRaisingEvents = true;
+
+                       bool gotData;
+                       lock (this) {
+                               waiting = true;
+                               gotData = Monitor.Wait (this, timeout);
+                               if (gotData)
+                                       result = this.lastData;
+                       }
+
+                       EnableRaisingEvents = prevEnabled;
+                       if (!gotData)
+                               result.TimedOut = true;
+
+                       return result;
+               }
+
+               internal void DispatchEvents (FileAction act, string filename, ref RenamedEventArgs renamed)
+               {
+                       if (waiting) {
+                               lastData = new WaitForChangedResult ();
+                       }
+
+                       switch (act) {
+                       case FileAction.Added:
+                               lastData.Name = filename;
+                               lastData.ChangeType = WatcherChangeTypes.Created;
+                               OnCreated (new FileSystemEventArgs (WatcherChangeTypes.Created, path, filename));
+                               break;
+                       case FileAction.Removed:
+                               lastData.Name = filename;
+                               lastData.ChangeType = WatcherChangeTypes.Deleted;
+                               OnDeleted (new FileSystemEventArgs (WatcherChangeTypes.Deleted, path, filename));
+                               break;
+                       case FileAction.Modified:
+                               lastData.Name = filename;
+                               lastData.ChangeType = WatcherChangeTypes.Changed;
+                               OnChanged (new FileSystemEventArgs (WatcherChangeTypes.Changed, path, filename));
+                               break;
+                       case FileAction.RenamedOldName:
+                               if (renamed != null) {
+                                       OnRenamed (renamed);
+                               }
+                               lastData.OldName = filename;
+                               lastData.ChangeType = WatcherChangeTypes.Renamed;
+                               renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, null, filename);
+                               break;
+                       case FileAction.RenamedNewName:
+                               lastData.Name = filename;
+                               lastData.ChangeType = WatcherChangeTypes.Renamed;
+                               if (renamed != null) {
+                                       renamed.SetName (filename);
+                               } else {
+                                       renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, filename, null);
+                               }
+                               OnRenamed (renamed);
+                               renamed = null;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               void Start ()
+               {
+                       watcher.StartDispatching (this);
+               }
+
+               void Stop ()
+               {
+                       watcher.StopDispatching (this);
+               }
+               #endregion // Methods
+
+               #region Events and Delegates
+
+               [IODescription("Occurs when a file/directory change matches the filter")]
+               public event FileSystemEventHandler Changed;
+
+               [IODescription("Occurs when a file/directory creation matches the filter")]
+               public event FileSystemEventHandler Created;
+
+               [IODescription("Occurs when a file/directory deletion matches the filter")]
+               public event FileSystemEventHandler Deleted;
+
+               [Browsable(false)]
+               public event ErrorEventHandler Error;
+
+               [IODescription("Occurs when a file/directory rename matches the filter")]
+               public event RenamedEventHandler Renamed;
+
+               #endregion // Events and Delegates
+
+               /* 0 -> not supported   */
+               /* 1 -> windows         */
+               /* 2 -> FAM             */
+               [MethodImplAttribute(MethodImplOptions.InternalCall)]
+               static extern int InternalSupportsFSW ();
+               
+               /*[MethodImplAttribute(MethodImplOptions.InternalCall)]
+               static extern IntPtr InternalOpenDirectory (string path, IntPtr reserved);
+
+               [MethodImplAttribute(MethodImplOptions.InternalCall)]
+               static extern IntPtr InternalCloseDirectory (IntPtr handle);
+
+               [MethodImplAttribute(MethodImplOptions.InternalCall)]
+               static extern bool InternalReadDirectoryChanges (IntPtr handle,
+                                                                byte [] buffer,
+                                                                bool includeSubdirectories,
+                                                                NotifyFilters notifyFilter,
+                                                                out NativeOverlapped overlap,
+                                                                OverlappedHandler overlappedCallback);
+
+               */
+       }
+}