BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[mono.git] / mcs / class / System / System.IO / FileSystemWatcher.cs
1 // 
2 // System.IO.FileSystemWatcher.cs
3 //
4 // Authors:
5 //      Tim Coleman (tim@timcoleman.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // Copyright (C) Tim Coleman, 2002 
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
10 // Copyright (C) 2004, 2006 Novell, Inc (http://www.novell.com)
11 //
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.ComponentModel;
35 using System.Diagnostics;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.InteropServices;
38 using System.Security.Permissions;
39 using System.Threading;
40
41 namespace System.IO {
42         [DefaultEvent("Changed")]
43 #if NET_2_0
44         [IODescription ("")]
45 #endif
46         public class FileSystemWatcher : Component, ISupportInitialize {
47
48                 #region Fields
49
50                 bool enableRaisingEvents;
51                 string filter;
52                 bool includeSubdirectories;
53                 int internalBufferSize;
54                 NotifyFilters notifyFilter;
55                 string path;
56                 string fullpath;
57                 ISynchronizeInvoke synchronizingObject;
58                 WaitForChangedResult lastData;
59                 bool waiting;
60                 SearchPattern2 pattern;
61                 bool disposed;
62                 string mangledFilter;
63                 static IFileWatcher watcher;
64                 static object lockobj = new object ();
65
66                 #endregion // Fields
67
68                 #region Constructors
69
70                 public FileSystemWatcher ()
71                 {
72                         this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
73                         this.enableRaisingEvents = false;
74                         this.filter = "*.*";
75                         this.includeSubdirectories = false;
76                         this.internalBufferSize = 8192;
77                         this.path = "";
78                         InitWatcher ();
79                 }
80
81                 public FileSystemWatcher (string path)
82                         : this (path, "*.*")
83                 {
84                 }
85
86                 public FileSystemWatcher (string path, string filter)
87                 {
88                         if (path == null)
89                                 throw new ArgumentNullException ("path");
90
91                         if (filter == null)
92                                 throw new ArgumentNullException ("filter");
93
94                         if (path == String.Empty)
95                                 throw new ArgumentException ("Empty path", "path");
96
97                         if (!Directory.Exists (path))
98                                 throw new ArgumentException ("Directory does not exists", "path");
99
100                         this.enableRaisingEvents = false;
101                         this.filter = filter;
102                         this.includeSubdirectories = false;
103                         this.internalBufferSize = 8192;
104                         this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
105                         this.path = path;
106                         this.synchronizingObject = null;
107                         InitWatcher ();
108                 }
109
110                 [EnvironmentPermission (SecurityAction.Assert, Read="MONO_MANAGED_WATCHER")]
111                 void InitWatcher ()
112                 {
113                         lock (lockobj) {
114                                 if (watcher != null)
115                                         return;
116
117                                 string managed = Environment.GetEnvironmentVariable ("MONO_MANAGED_WATCHER");
118                                 int mode = 0;
119                                 if (managed == null)
120                                         mode = InternalSupportsFSW ();
121                                 
122                                 bool ok = false;
123                                 switch (mode) {
124                                 case 1: // windows
125                                         ok = DefaultWatcher.GetInstance (out watcher);
126                                         //ok = WindowsWatcher.GetInstance (out watcher);
127                                         break;
128                                 case 2: // libfam
129                                         ok = FAMWatcher.GetInstance (out watcher, false);
130                                         break;
131                                 case 3: // kevent
132                                         ok = KeventWatcher.GetInstance (out watcher);
133                                         break;
134                                 case 4: // libgamin
135                                         ok = FAMWatcher.GetInstance (out watcher, true);
136                                         break;
137                                 case 5: // inotify
138                                         ok = InotifyWatcher.GetInstance (out watcher, true);
139                                         break;
140                                 }
141
142                                 if (mode == 0 || !ok) {
143                                         if (String.Compare (managed, "disabled", true) == 0)
144                                                 NullFileWatcher.GetInstance (out watcher);
145                                         else
146                                                 DefaultWatcher.GetInstance (out watcher);
147                                 }
148
149                                 ShowWatcherInfo ();
150                         }
151                 }
152
153                 [Conditional ("DEBUG"), Conditional ("TRACE")]
154                 void ShowWatcherInfo ()
155                 {
156                         Console.WriteLine ("Watcher implementation: {0}", watcher != null ? watcher.GetType ().ToString () : "<none>");
157                 }
158                 
159                 #endregion // Constructors
160
161                 #region Properties
162
163                 /* If this is enabled, we Pulse this instance */
164                 internal bool Waiting {
165                         get { return waiting; }
166                         set { waiting = value; }
167                 }
168
169                 internal string MangledFilter {
170                         get {
171                                 if (filter != "*.*")
172                                         return filter;
173
174                                 if (mangledFilter != null)
175                                         return mangledFilter;
176
177                                 string filterLocal = "*.*";
178                                 if (!(watcher.GetType () == typeof (WindowsWatcher)))
179                                         filterLocal = "*";
180
181                                 return filterLocal;
182                         }
183                 }
184
185                 internal SearchPattern2 Pattern {
186                         get {
187                                 if (pattern == null) {
188                                         pattern = new SearchPattern2 (MangledFilter);
189                                 }
190                                 return pattern;
191                         }
192                 }
193
194                 internal string FullPath {
195                         get {
196                                 if (fullpath == null) {
197                                         if (path == null || path == "")
198                                                 fullpath = Environment.CurrentDirectory;
199                                         else
200                                                 fullpath = System.IO.Path.GetFullPath (path);
201                                 }
202
203                                 return fullpath;
204                         }
205                 }
206
207                 [DefaultValue(false)]
208                 [IODescription("Flag to indicate if this instance is active")]
209                 public bool EnableRaisingEvents {
210                         get { return enableRaisingEvents; }
211                         set {
212                                 if (value == enableRaisingEvents)
213                                         return; // Do nothing
214
215                                 enableRaisingEvents = value;
216                                 if (value) {
217                                         Start ();
218                                 } else {
219                                         Stop ();
220                                 }
221                         }
222                 }
223
224                 [DefaultValue("*.*")]
225                 [IODescription("File name filter pattern")]
226                 [RecommendedAsConfigurable(true)]
227                 [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
228                 public string Filter {
229                         get { return filter; }
230                         set {
231                                 if (value == null || value == "")
232                                         value = "*.*";
233
234                                 if (filter != value) {
235                                         filter = value;
236                                         pattern = null;
237                                         mangledFilter = null;
238                                 }
239                         }
240                 }
241
242                 [DefaultValue(false)]
243                 [IODescription("Flag to indicate we want to watch subdirectories")]
244                 public bool IncludeSubdirectories {
245                         get { return includeSubdirectories; }
246                         set {
247                                 if (includeSubdirectories == value)
248                                         return;
249
250                                 includeSubdirectories = value;
251                                 if (value && enableRaisingEvents) {
252                                         Stop ();
253                                         Start ();
254                                 }
255                         }
256                 }
257
258                 [Browsable(false)]
259                 [DefaultValue(8192)]
260                 public int InternalBufferSize {
261                         get { return internalBufferSize; }
262                         set {
263                                 if (internalBufferSize == value)
264                                         return;
265
266                                 if (value < 4196)
267                                         value = 4196;
268
269                                 internalBufferSize = value;
270                                 if (enableRaisingEvents) {
271                                         Stop ();
272                                         Start ();
273                                 }
274                         }
275                 }
276
277                 [DefaultValue(NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite)]
278                 [IODescription("Flag to indicate which change event we want to monitor")]
279                 public NotifyFilters NotifyFilter {
280                         get { return notifyFilter; }
281                         set {
282                                 if (notifyFilter == value)
283                                         return;
284                                         
285                                 notifyFilter = value;
286                                 if (enableRaisingEvents) {
287                                         Stop ();
288                                         Start ();
289                                 }
290                         }
291                 }
292
293                 [DefaultValue("")]
294                 [IODescription("The directory to monitor")]
295                 [RecommendedAsConfigurable(true)]
296                 [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
297                 [Editor ("System.Diagnostics.Design.FSWPathEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
298                 public string Path {
299                         get { return path; }
300                         set {
301                                 if (path == value)
302                                         return;
303
304                                 bool exists = false;
305                                 Exception exc = null;
306
307                                 try {
308                                         exists = Directory.Exists (value);
309                                 } catch (Exception e) {
310                                         exc = e;
311                                 }
312
313                                 if (exc != null)
314                                         throw new ArgumentException ("Invalid directory name", "value", exc);
315
316                                 if (!exists)
317                                         throw new ArgumentException ("Directory does not exists", "value");
318
319                                 path = value;
320                                 fullpath = null;
321                                 if (enableRaisingEvents) {
322                                         Stop ();
323                                         Start ();
324                                 }
325                         }
326                 }
327
328                 [Browsable(false)]
329                 public override ISite Site {
330                         get { return base.Site; }
331                         set { base.Site = value; }
332                 }
333
334                 [DefaultValue(null)]
335                 [IODescription("The object used to marshal the event handler calls resulting from a directory change")]
336 #if NET_2_0
337                 [Browsable (false)]
338 #endif
339                 public ISynchronizeInvoke SynchronizingObject {
340                         get { return synchronizingObject; }
341                         set { synchronizingObject = value; }
342                 }
343
344                 #endregion // Properties
345
346                 #region Methods
347         
348                 public void BeginInit ()
349                 {
350                         // Not necessary in Mono
351                 }
352
353                 protected override void Dispose (bool disposing)
354                 {
355                         if (!disposed) {
356                                 disposed = true;
357                                 Stop ();
358                         }
359
360                         base.Dispose (disposing);
361                 }
362
363                 ~FileSystemWatcher ()
364                 {
365                         disposed = true;
366                         Stop ();
367                 }
368                 
369                 public void EndInit ()
370                 {
371                         // Not necessary in Mono
372                 }
373
374                 enum EventType {
375                         FileSystemEvent,
376                         ErrorEvent,
377                         RenameEvent
378                 }
379                 private void RaiseEvent (Delegate ev, EventArgs arg, EventType evtype)
380                 {
381                         if (ev == null)
382                                 return;
383
384                         if (synchronizingObject == null) {
385                                 switch (evtype) {
386                                 case EventType.RenameEvent:
387                                         ((RenamedEventHandler)ev).BeginInvoke (this, (RenamedEventArgs) arg, null, null);
388                                         break;
389                                 case EventType.ErrorEvent:
390                                         ((ErrorEventHandler)ev).BeginInvoke (this, (ErrorEventArgs) arg, null, null);
391                                         break;
392                                 case EventType.FileSystemEvent:
393                                         ((FileSystemEventHandler)ev).BeginInvoke (this, (FileSystemEventArgs) arg, null, null);
394                                         break;
395                                 }
396                                 return;
397                         }
398                         
399                         synchronizingObject.BeginInvoke (ev, new object [] {this, arg});
400                 }
401
402                 protected void OnChanged (FileSystemEventArgs e)
403                 {
404                         RaiseEvent (Changed, e, EventType.FileSystemEvent);
405                 }
406
407                 protected void OnCreated (FileSystemEventArgs e)
408                 {
409                         RaiseEvent (Created, e, EventType.FileSystemEvent);
410                 }
411
412                 protected void OnDeleted (FileSystemEventArgs e)
413                 {
414                         RaiseEvent (Deleted, e, EventType.FileSystemEvent);
415                 }
416
417                 protected void OnError (ErrorEventArgs e)
418                 {
419                         RaiseEvent (Error, e, EventType.ErrorEvent);
420                 }
421
422                 protected void OnRenamed (RenamedEventArgs e)
423                 {
424                         RaiseEvent (Renamed, e, EventType.RenameEvent);
425                 }
426
427                 public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
428                 {
429                         return WaitForChanged (changeType, Timeout.Infinite);
430                 }
431
432                 public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)
433                 {
434                         WaitForChangedResult result = new WaitForChangedResult ();
435                         bool prevEnabled = EnableRaisingEvents;
436                         if (!prevEnabled)
437                                 EnableRaisingEvents = true;
438
439                         bool gotData;
440                         lock (this) {
441                                 waiting = true;
442                                 gotData = Monitor.Wait (this, timeout);
443                                 if (gotData)
444                                         result = this.lastData;
445                         }
446
447                         EnableRaisingEvents = prevEnabled;
448                         if (!gotData)
449                                 result.TimedOut = true;
450
451                         return result;
452                 }
453
454                 internal void DispatchEvents (FileAction act, string filename, ref RenamedEventArgs renamed)
455                 {
456                         if (waiting) {
457                                 lastData = new WaitForChangedResult ();
458                         }
459
460                         switch (act) {
461                         case FileAction.Added:
462                                 lastData.Name = filename;
463                                 lastData.ChangeType = WatcherChangeTypes.Created;
464                                 OnCreated (new FileSystemEventArgs (WatcherChangeTypes.Created, path, filename));
465                                 break;
466                         case FileAction.Removed:
467                                 lastData.Name = filename;
468                                 lastData.ChangeType = WatcherChangeTypes.Deleted;
469                                 OnDeleted (new FileSystemEventArgs (WatcherChangeTypes.Deleted, path, filename));
470                                 break;
471                         case FileAction.Modified:
472                                 lastData.Name = filename;
473                                 lastData.ChangeType = WatcherChangeTypes.Changed;
474                                 OnChanged (new FileSystemEventArgs (WatcherChangeTypes.Changed, path, filename));
475                                 break;
476                         case FileAction.RenamedOldName:
477                                 if (renamed != null) {
478                                         OnRenamed (renamed);
479                                 }
480                                 lastData.OldName = filename;
481                                 lastData.ChangeType = WatcherChangeTypes.Renamed;
482                                 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, filename, "");
483                                 break;
484                         case FileAction.RenamedNewName:
485                                 lastData.Name = filename;
486                                 lastData.ChangeType = WatcherChangeTypes.Renamed;
487                                 if (renamed == null) {
488                                         renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, "", filename);
489                                 }
490                                 OnRenamed (renamed);
491                                 renamed = null;
492                                 break;
493                         default:
494                                 break;
495                         }
496                 }
497
498                 void Start ()
499                 {
500                         watcher.StartDispatching (this);
501                 }
502
503                 void Stop ()
504                 {
505                         watcher.StopDispatching (this);
506                 }
507                 #endregion // Methods
508
509                 #region Events and Delegates
510
511                 [IODescription("Occurs when a file/directory change matches the filter")]
512                 public event FileSystemEventHandler Changed;
513
514                 [IODescription("Occurs when a file/directory creation matches the filter")]
515                 public event FileSystemEventHandler Created;
516
517                 [IODescription("Occurs when a file/directory deletion matches the filter")]
518                 public event FileSystemEventHandler Deleted;
519
520                 [Browsable(false)]
521                 public event ErrorEventHandler Error;
522
523                 [IODescription("Occurs when a file/directory rename matches the filter")]
524                 public event RenamedEventHandler Renamed;
525
526                 #endregion // Events and Delegates
527
528                 /* 0 -> not supported   */
529                 /* 1 -> windows         */
530                 /* 2 -> FAM             */
531                 /* 3 -> Kevent          */
532                 /* 4 -> gamin           */
533                 /* 5 -> inotify         */
534                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
535                 static extern int InternalSupportsFSW ();
536         }
537 }
538