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