2006-07-25 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[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 // (c) 2004 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;
35 using System.ComponentModel;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.InteropServices;
38 using System.Threading;
39
40 namespace System.IO {
41         [DefaultEvent("Changed")]
42         public class FileSystemWatcher : Component, ISupportInitialize {
43
44                 #region Fields
45
46                 bool enableRaisingEvents;
47                 string filter;
48                 bool includeSubdirectories;
49                 int internalBufferSize;
50                 NotifyFilters notifyFilter;
51                 string path;
52                 string fullpath;
53                 ISynchronizeInvoke synchronizingObject;
54                 WaitForChangedResult lastData;
55                 bool waiting;
56                 SearchPattern2 pattern;
57                 bool disposed;
58                 string mangledFilter;
59                 static IFileWatcher watcher;
60                 static object lockobj = new object ();
61
62                 #endregion // Fields
63
64                 #region Constructors
65
66                 public FileSystemWatcher ()
67                 {
68                         this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
69                         this.enableRaisingEvents = false;
70                         this.filter = "*.*";
71                         this.includeSubdirectories = false;
72                         this.internalBufferSize = 8192;
73                         this.path = "";
74                         InitWatcher ();
75                 }
76
77                 public FileSystemWatcher (string path)
78                         : this (path, "*.*")
79                 {
80                 }
81
82                 public FileSystemWatcher (string path, string filter)
83                 {
84                         if (path == null)
85                                 throw new ArgumentNullException ("path");
86
87                         if (filter == null)
88                                 throw new ArgumentNullException ("filter");
89
90                         if (path == String.Empty)
91                                 throw new ArgumentException ("Empty path", "path");
92
93                         if (!Directory.Exists (path))
94                                 throw new ArgumentException ("Directory does not exists", "path");
95
96                         this.enableRaisingEvents = false;
97                         this.filter = filter;
98                         this.includeSubdirectories = false;
99                         this.internalBufferSize = 8192;
100                         this.notifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
101                         this.path = path;
102                         this.synchronizingObject = null;
103                         InitWatcher ();
104                 }
105
106                 void InitWatcher ()
107                 {
108                         lock (lockobj) {
109                                 if (watcher != null)
110                                         return;
111
112                                 string managed = Environment.GetEnvironmentVariable ("MONO_MANAGED_WATCHER");
113                                 int mode = 0;
114                                 if (managed == null)
115                                         mode = InternalSupportsFSW ();
116
117                                 bool ok = false;
118                                 switch (mode) {
119                                 case 1: // windows
120                                         ok = DefaultWatcher.GetInstance (out watcher);
121                                         //ok = WindowsWatcher.GetInstance (out watcher);
122                                         break;
123                                 case 2: // libfam
124                                         ok = FAMWatcher.GetInstance (out watcher, false);
125                                         break;
126                                 case 3: // kevent
127                                         ok = KeventWatcher.GetInstance (out watcher);
128                                         break;
129                                 case 4: // libgamin
130                                         ok = FAMWatcher.GetInstance (out watcher, true);
131                                         break;
132                                 case 5: // inotify
133                                         ok = InotifyWatcher.GetInstance (out watcher, true);
134                                         break;
135                                 }
136
137                                 if (mode == 0 || !ok)
138                                         DefaultWatcher.GetInstance (out watcher);
139                         }
140                 }
141
142                 #endregion // Constructors
143
144                 #region Properties
145
146                 /* If this is enabled, we Pulse this instance */
147                 internal bool Waiting {
148                         get { return waiting; }
149                         set { waiting = value; }
150                 }
151
152                 internal string MangledFilter {
153                         get {
154                                 if (filter != "*.*")
155                                         return filter;
156
157                                 if (mangledFilter != null)
158                                         return mangledFilter;
159
160                                 string filterLocal = "*.*";
161                                 if (!(watcher.GetType () == typeof (WindowsWatcher)))
162                                         filterLocal = "*";
163
164                                 return filterLocal;
165                         }
166                 }
167
168                 internal SearchPattern2 Pattern {
169                         get {
170                                 if (pattern == null) {
171                                         pattern = new SearchPattern2 (MangledFilter);
172                                 }
173                                 return pattern;
174                         }
175                 }
176
177                 internal string FullPath {
178                         get {
179                                 if (fullpath == null) {
180                                         if (path == null || path == "")
181                                                 fullpath = Environment.CurrentDirectory;
182                                         else
183                                                 fullpath = System.IO.Path.GetFullPath (path);
184                                 }
185
186                                 return fullpath;
187                         }
188                 }
189
190                 [DefaultValue(false)]
191                 [IODescription("Flag to indicate if this instance is active")]
192                 public bool EnableRaisingEvents {
193                         get { return enableRaisingEvents; }
194                         set {
195                                 if (value == enableRaisingEvents)
196                                         return; // Do nothing
197
198                                 enableRaisingEvents = value;
199                                 if (value) {
200                                         Start ();
201                                 } else {
202                                         Stop ();
203                                 }
204                         }
205                 }
206
207                 [DefaultValue("*.*")]
208                 [IODescription("File name filter pattern")]
209                 [RecommendedAsConfigurable(true)]
210                 [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
211                 public string Filter {
212                         get { return filter; }
213                         set {
214                                 if (value == null || value == "")
215                                         value = "*.*";
216
217                                 if (filter != value) {
218                                         filter = value;
219                                         pattern = null;
220                                         mangledFilter = null;
221                                 }
222                         }
223                 }
224
225                 [DefaultValue(false)]
226                 [IODescription("Flag to indicate we want to watch subdirectories")]
227                 public bool IncludeSubdirectories {
228                         get { return includeSubdirectories; }
229                         set {
230                                 if (includeSubdirectories == value)
231                                         return;
232
233                                 includeSubdirectories = value;
234                                 if (value && enableRaisingEvents) {
235                                         Stop ();
236                                         Start ();
237                                 }
238                         }
239                 }
240
241                 [Browsable(false)]
242                 [DefaultValue(8192)]
243                 public int InternalBufferSize {
244                         get { return internalBufferSize; }
245                         set {
246                                 if (internalBufferSize == value)
247                                         return;
248
249                                 if (value < 4196)
250                                         value = 4196;
251
252                                 internalBufferSize = value;
253                                 if (enableRaisingEvents) {
254                                         Stop ();
255                                         Start ();
256                                 }
257                         }
258                 }
259
260                 [DefaultValue(NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite)]
261                 [IODescription("Flag to indicate which change event we want to monitor")]
262                 public NotifyFilters NotifyFilter {
263                         get { return notifyFilter; }
264                         set {
265                                 if (notifyFilter == value)
266                                         return;
267                                         
268                                 notifyFilter = value;
269                                 if (enableRaisingEvents) {
270                                         Stop ();
271                                         Start ();
272                                 }
273                         }
274                 }
275
276                 [DefaultValue("")]
277                 [IODescription("The directory to monitor")]
278                 [RecommendedAsConfigurable(true)]
279                 [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
280                 [Editor ("System.Diagnostics.Design.FSWPathEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
281                 public string Path {
282                         get { return path; }
283                         set {
284                                 if (path == value)
285                                         return;
286
287                                 bool exists = false;
288                                 Exception exc = null;
289
290                                 try {
291                                         exists = Directory.Exists (value);
292                                 } catch (Exception e) {
293                                         exc = e;
294                                 }
295
296                                 if (exc != null)
297                                         throw new ArgumentException ("Invalid directory name", "value", exc);
298
299                                 if (!exists)
300                                         throw new ArgumentException ("Directory does not exists", "value");
301
302                                 path = value;
303                                 fullpath = null;
304                                 if (enableRaisingEvents) {
305                                         Stop ();
306                                         Start ();
307                                 }
308                         }
309                 }
310
311                 [Browsable(false)]
312                 public override ISite Site {
313                         get { return base.Site; }
314                         set { base.Site = value; }
315                 }
316
317                 [DefaultValue(null)]
318                 [IODescription("The object used to marshal the event handler calls resulting from a directory change")]
319                 public ISynchronizeInvoke SynchronizingObject {
320                         get { return synchronizingObject; }
321                         set { synchronizingObject = value; }
322                 }
323
324                 #endregion // Properties
325
326                 #region Methods
327         
328                 [MonoTODO]
329                 public void BeginInit ()
330                 {
331                         throw new NotImplementedException (); 
332                 }
333
334                 protected override void Dispose (bool disposing)
335                 {
336                         if (!disposed) {
337                                 disposed = true;
338                                 Stop ();
339                         }
340
341                         base.Dispose (disposing);
342                 }
343
344                 ~FileSystemWatcher ()
345                 {
346                         disposed = true;
347                         Stop ();
348                 }
349                 
350                 [MonoTODO]
351                 public void EndInit ()
352                 {
353                         throw new NotImplementedException (); 
354                 }
355
356                 private void RaiseEvent (Delegate ev, EventArgs arg)
357                 {
358                         if (ev == null)
359                                 return;
360
361                         object [] args = new object [] {this, arg};
362
363                         if (synchronizingObject == null) {
364                                 ev.DynamicInvoke (args);
365                                 return;
366                         }
367                         
368                         synchronizingObject.BeginInvoke (ev, args);
369                 }
370
371                 protected void OnChanged (FileSystemEventArgs e)
372                 {
373                         RaiseEvent (Changed, e);
374                 }
375
376                 protected void OnCreated (FileSystemEventArgs e)
377                 {
378                         RaiseEvent (Created, e);
379                 }
380
381                 protected void OnDeleted (FileSystemEventArgs e)
382                 {
383                         RaiseEvent (Deleted, e);
384                 }
385
386                 protected void OnError (ErrorEventArgs e)
387                 {
388                         RaiseEvent (Error, e);
389                 }
390
391                 protected void OnRenamed (RenamedEventArgs e)
392                 {
393                         RaiseEvent (Renamed, e);
394                 }
395
396                 public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
397                 {
398                         return WaitForChanged (changeType, Timeout.Infinite);
399                 }
400
401                 public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout)
402                 {
403                         WaitForChangedResult result = new WaitForChangedResult ();
404                         bool prevEnabled = EnableRaisingEvents;
405                         if (!prevEnabled)
406                                 EnableRaisingEvents = true;
407
408                         bool gotData;
409                         lock (this) {
410                                 waiting = true;
411                                 gotData = Monitor.Wait (this, timeout);
412                                 if (gotData)
413                                         result = this.lastData;
414                         }
415
416                         EnableRaisingEvents = prevEnabled;
417                         if (!gotData)
418                                 result.TimedOut = true;
419
420                         return result;
421                 }
422
423                 internal void DispatchEvents (FileAction act, string filename, ref RenamedEventArgs renamed)
424                 {
425                         if (waiting) {
426                                 lastData = new WaitForChangedResult ();
427                         }
428
429                         switch (act) {
430                         case FileAction.Added:
431                                 lastData.Name = filename;
432                                 lastData.ChangeType = WatcherChangeTypes.Created;
433                                 OnCreated (new FileSystemEventArgs (WatcherChangeTypes.Created, path, filename));
434                                 break;
435                         case FileAction.Removed:
436                                 lastData.Name = filename;
437                                 lastData.ChangeType = WatcherChangeTypes.Deleted;
438                                 OnDeleted (new FileSystemEventArgs (WatcherChangeTypes.Deleted, path, filename));
439                                 break;
440                         case FileAction.Modified:
441                                 lastData.Name = filename;
442                                 lastData.ChangeType = WatcherChangeTypes.Changed;
443                                 OnChanged (new FileSystemEventArgs (WatcherChangeTypes.Changed, path, filename));
444                                 break;
445                         case FileAction.RenamedOldName:
446                                 if (renamed != null) {
447                                         OnRenamed (renamed);
448                                 }
449                                 lastData.OldName = filename;
450                                 lastData.ChangeType = WatcherChangeTypes.Renamed;
451                                 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, null, filename);
452                                 break;
453                         case FileAction.RenamedNewName:
454                                 lastData.Name = filename;
455                                 lastData.ChangeType = WatcherChangeTypes.Renamed;
456                                 if (renamed != null) {
457                                         renamed.SetName (filename);
458                                 } else {
459                                         renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, path, filename, null);
460                                 }
461                                 OnRenamed (renamed);
462                                 renamed = null;
463                                 break;
464                         default:
465                                 break;
466                         }
467                 }
468
469                 void Start ()
470                 {
471                         watcher.StartDispatching (this);
472                 }
473
474                 void Stop ()
475                 {
476                         watcher.StopDispatching (this);
477                 }
478                 #endregion // Methods
479
480                 #region Events and Delegates
481
482                 [IODescription("Occurs when a file/directory change matches the filter")]
483                 public event FileSystemEventHandler Changed;
484
485                 [IODescription("Occurs when a file/directory creation matches the filter")]
486                 public event FileSystemEventHandler Created;
487
488                 [IODescription("Occurs when a file/directory deletion matches the filter")]
489                 public event FileSystemEventHandler Deleted;
490
491                 [Browsable(false)]
492                 public event ErrorEventHandler Error;
493
494                 [IODescription("Occurs when a file/directory rename matches the filter")]
495                 public event RenamedEventHandler Renamed;
496
497                 #endregion // Events and Delegates
498
499                 /* 0 -> not supported   */
500                 /* 1 -> windows         */
501                 /* 2 -> FAM             */
502                 /* 3 -> Kevent          */
503                 /* 4 -> gamin           */
504                 /* 5 -> inotify         */
505                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
506                 static extern int InternalSupportsFSW ();
507         }
508 }
509