1 //------------------------------------------------------------------------------
2 // <copyright file="FileSystemWatcher.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Threading;
9 using System.Runtime.InteropServices;
10 using System.Diagnostics;
11 using System.Diagnostics.CodeAnalysis;
12 using System.ComponentModel;
13 using System.ComponentModel.Design;
14 using Microsoft.Win32;
15 using Microsoft.Win32.SafeHandles;
16 using System.Security.Permissions;
17 using System.Security;
18 using System.Globalization;
19 using System.Runtime.Versioning;
22 /// <para>Listens to the system directory change notifications and
23 /// raises events when a directory or file within a directory changes.</para>
26 DefaultEvent("Changed"),
27 // Disabling partial trust scenarios
28 PermissionSet(SecurityAction.LinkDemand, Name="FullTrust"),
29 PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust"),
30 IODescription(SR.FileSystemWatcherDesc)
32 public class FileSystemWatcher : Component, ISupportInitialize {
34 /// Private instance variables
36 // Directory being monitored
37 private string directory;
39 // Filter for name matching
40 private string filter;
42 // Unmanaged handle to monitored directory
43 private SafeFileHandle directoryHandle;
45 // The watch filter for the API call.
46 private const NotifyFilters defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
47 private NotifyFilters notifyFilters = defaultNotifyFilters;
49 // Flag to watch subtree of this directory
50 private bool includeSubdirectories = false;
52 // Flag to note whether we are attached to the thread pool and responding to changes
53 private bool enabled = false;
56 private bool initializing = false;
59 private int internalBufferSize = 8192;
61 // Used for synchronization
62 private WaitForChangedResult changedResult;
63 private bool isChanged = false;
64 private ISynchronizeInvoke synchronizingObject;
65 private bool readGranted;
66 private bool disposed;
67 // Current "session" ID to ignore old events whenever we stop then
69 private int currentSession;
72 private FileSystemEventHandler onChangedHandler = null;
73 private FileSystemEventHandler onCreatedHandler = null;
74 private FileSystemEventHandler onDeletedHandler = null;
75 private RenamedEventHandler onRenamedHandler = null;
76 private ErrorEventHandler onErrorHandler = null;
78 // Thread gate holder and constats
79 private bool stopListening = false;
81 // Used for async method
82 private bool runOnce = false;
84 // To validate the input for "path"
85 private static readonly char[] wildcards = new char[] { '?', '*' };
87 private static int notifyFiltersValidMask;
89 // Additional state information to pass to callback. Note that we
90 // never return this object to users, but we do pass state in it.
91 private sealed class FSWAsyncResult : IAsyncResult
94 internal byte[] buffer;
96 public bool IsCompleted { get { throw new NotImplementedException(); } }
97 public WaitHandle AsyncWaitHandle { get { throw new NotImplementedException(); } }
98 public Object AsyncState { get { throw new NotImplementedException(); } }
99 public bool CompletedSynchronously { get { throw new NotImplementedException(); } }
102 static FileSystemWatcher() {
103 notifyFiltersValidMask = 0;
104 foreach (int enumValue in Enum.GetValues(typeof(NotifyFilters)))
105 notifyFiltersValidMask |= enumValue;
109 /// <para>Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class.</para>
111 public FileSystemWatcher() {
112 this.directory = String.Empty;
118 /// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
119 /// given the specified directory to monitor.
122 [ResourceExposure(ResourceScope.Machine)]
123 [ResourceConsumption(ResourceScope.Machine)]
124 public FileSystemWatcher(string path) : this(path, "*.*") {
130 /// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class,
131 /// given the specified directory and type of files to monitor.
134 [ResourceExposure(ResourceScope.Machine)]
135 [ResourceConsumption(ResourceScope.Machine)]
136 public FileSystemWatcher(string path, string filter) {
138 throw new ArgumentNullException("path");
141 throw new ArgumentNullException("filter");
143 // Early check for directory parameter so that an exception can be thrown as early as possible.
144 if (path.Length == 0 || !Directory.Exists(path))
145 throw new ArgumentException(SR.GetString(SR.InvalidDirName, path));
147 this.directory = path;
148 this.filter = filter;
153 /// Gets or sets the type of changes to watch for.
157 DefaultValue(defaultNotifyFilters),
158 IODescription(SR.FSW_ChangedFilter)
160 public NotifyFilters NotifyFilter {
162 return notifyFilters;
165 if (((int) value & ~notifyFiltersValidMask) != 0)
166 throw new InvalidEnumArgumentException("value", (int)value, typeof(NotifyFilters));
168 if (notifyFilters != value) {
169 notifyFilters = value;
177 /// <para>Gets or sets a value indicating whether the component is enabled.</para>
181 IODescription(SR.FSW_Enabled)
183 public bool EnableRaisingEvents {
189 if (enabled == value) {
195 if (!IsSuspended()) {
197 StartRaisingEvents();
207 /// <para>Gets or sets the filter string, used to determine what files are monitored in a directory.</para>
211 IODescription(SR.FSW_Filter),
212 TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign),
213 SettingsBindable(true),
215 public string Filter {
220 if (String.IsNullOrEmpty(value)) {
223 if (String.Compare(filter, value, StringComparison.OrdinalIgnoreCase) != 0) {
232 /// value indicating whether subdirectories within the specified path should be monitored.
237 IODescription(SR.FSW_IncludeSubdirectories)
239 public bool IncludeSubdirectories {
241 return includeSubdirectories;
244 if (includeSubdirectories != value) {
245 includeSubdirectories = value;
254 /// sets the size of the internal buffer.</para>
260 public int InternalBufferSize {
262 return internalBufferSize;
265 if (internalBufferSize != value) {
270 internalBufferSize = value;
277 private bool IsHandleInvalid {
279 return (directoryHandle == null || directoryHandle.IsInvalid);
284 /// <para>Gets or sets the path of the directory to watch.</para>
288 IODescription(SR.FSW_Path),
289 Editor("System.Diagnostics.Design.FSWPathEditor, " + AssemblyRef.SystemDesign, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
290 TypeConverter("System.Diagnostics.Design.StringValueConverter, " + AssemblyRef.SystemDesign),
291 SettingsBindable(true)
294 [ResourceExposure(ResourceScope.Machine)]
295 [ResourceConsumption(ResourceScope.Machine)]
299 [ResourceExposure(ResourceScope.Machine)]
300 [ResourceConsumption(ResourceScope.Machine)]
302 value = (value == null) ? string.Empty : value;
303 if (String.Compare(directory, value, StringComparison.OrdinalIgnoreCase) != 0) {
305 // Don't check the path if in design mode, try to do simple syntax check
306 if (value.IndexOfAny(FileSystemWatcher.wildcards) != -1 || value.IndexOfAny(System.IO.Path.GetInvalidPathChars()) != -1) {
307 throw new ArgumentException(SR.GetString(SR.InvalidDirName, value));
311 if (!Directory.Exists(value))
312 throw new ArgumentException(SR.GetString(SR.InvalidDirName, value));
325 public override ISite Site {
332 // set EnableRaisingEvents to true at design time so the user
333 // doesn't have to manually. We can't do this in
334 // the constructor because in code it should
336 if (Site != null && Site.DesignMode)
337 EnableRaisingEvents = true;
343 /// Gets or sets the object used to marshal the event handler calls issued as a
344 /// result of a directory change.
350 IODescription(SR.FSW_SynchronizingObject)
352 public ISynchronizeInvoke SynchronizingObject {
354 if (this.synchronizingObject == null && DesignMode) {
355 IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
357 object baseComponent = host.RootComponent;
358 if (baseComponent != null && baseComponent is ISynchronizeInvoke)
359 this.synchronizingObject = (ISynchronizeInvoke)baseComponent;
363 return this.synchronizingObject;
367 this.synchronizingObject = value;
373 /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
377 [IODescription(SR.FSW_Changed)]
378 public event FileSystemEventHandler Changed {
380 onChangedHandler += value;
383 onChangedHandler -= value;
389 /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
393 [IODescription(SR.FSW_Created)]
394 public event FileSystemEventHandler Created {
396 onCreatedHandler += value;
399 onCreatedHandler -= value;
405 /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
409 [IODescription(SR.FSW_Deleted)]
410 public event FileSystemEventHandler Deleted {
412 onDeletedHandler += value;
415 onDeletedHandler -= value;
421 /// Occurs when the internal buffer overflows.
425 public event ErrorEventHandler Error {
427 onErrorHandler += value;
430 onErrorHandler -= value;
436 /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
440 [IODescription(SR.FSW_Renamed)]
441 public event RenamedEventHandler Renamed {
443 onRenamedHandler += value;
446 onRenamedHandler -= value;
451 /// <para>Notifies the object that initialization is beginning and tells it to standby.</para>
453 public void BeginInit() {
454 bool oldEnabled = enabled;
456 enabled = oldEnabled;
461 /// Callback from thread pool.
464 private unsafe void CompletionStatusChanged(uint errorCode, uint numBytes, NativeOverlapped * overlappedPointer) {
466 Overlapped overlapped = Overlapped.Unpack(overlappedPointer);
467 FSWAsyncResult asyncResult = (FSWAsyncResult) overlapped.AsyncResult;
477 if (errorCode != 0) {
478 if (errorCode == 995 /* ERROR_OPERATION_ABORTED */) {
479 //Win2000 inside a service the first completion status is false
480 //cannot return without monitoring again.
481 //Because this return statement is inside a try/finally block,
482 //the finally block will execute. It does restart the monitoring.
486 OnError(new ErrorEventArgs(new Win32Exception((int)errorCode)));
487 EnableRaisingEvents = false;
492 // Ignore any events that occurred before this "session",
493 // so we don't get changed or error events after we
495 if (asyncResult.session != currentSession)
500 NotifyInternalBufferOverflowEvent();
502 else { // Else, parse each of them and notify appropriate delegates
505 Format for the buffer is the following C struct:
507 typedef struct _FILE_NOTIFY_INFORMATION {
508 DWORD NextEntryOffset;
510 DWORD FileNameLength;
512 } FILE_NOTIFY_INFORMATION;
514 NOTE1: FileNameLength is length in bytes.
515 NOTE2: The Filename is a Unicode string that's NOT NULL terminated.
516 NOTE3: A NextEntryOffset of zero means that it's the last entry
519 // Parse the file notify buffer:
521 int nextOffset, action, nameLength;
522 string oldName = null;
527 fixed (byte * buffPtr = asyncResult.buffer) {
530 nextOffset = *( (int *) (buffPtr + offset) );
533 action = *( (int *) (buffPtr + offset + 4) );
535 // Get filename length (in bytes):
536 nameLength = *( (int *) (buffPtr + offset + 8) );
537 name = new String( (char *) (buffPtr + offset + 12), 0, nameLength / 2);
541 /* A slightly convoluted piece of code follows. Here's what's happening:
543 We wish to collapse the poorly done rename notifications from the
544 ReadDirectoryChangesW API into a nice rename event. So to do that,
545 it's assumed that a FILE_ACTION_RENAMED_OLD_NAME will be followed
546 immediately by a FILE_ACTION_RENAMED_NEW_NAME in the buffer, which is
547 all that the following code is doing.
549 On a FILE_ACTION_RENAMED_OLD_NAME, it asserts that no previous one existed
550 and saves its name. If there are no more events in the buffer, it'll
551 assert and fire a RenameEventArgs with the Name field null.
553 If a NEW_NAME action comes in with no previous OLD_NAME, we assert and fire
554 a rename event with the OldName field null.
556 If the OLD_NAME and NEW_NAME actions are indeed there one after the other,
557 we'll fire the RenamedEventArgs normally and clear oldName.
559 If the OLD_NAME is followed by another action, we assert and then fire the
560 rename event with the Name field null and then fire the next action.
562 In case it's not a OLD_NAME or NEW_NAME action, we just fire the event normally.
567 // If the action is RENAMED_FROM, save the name of the file
568 if (action == Direct.FILE_ACTION_RENAMED_OLD_NAME) {
569 Debug.Assert(oldName == null, "FileSystemWatcher: Two FILE_ACTION_RENAMED_OLD_NAME " +
570 "in a row! [" + oldName + "], [ " + name + "]");
574 else if (action == Direct.FILE_ACTION_RENAMED_NEW_NAME) {
575 if (oldName != null) {
576 NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
580 Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_NEW_NAME with no" +
581 "old name! [ " + name + "]");
583 NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
588 if (oldName != null) {
589 Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_OLD_NAME with no" +
590 "new name! [" + oldName + "]");
592 NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
596 // Notify each file of change
597 NotifyFileSystemEventArgs(action, name);
601 offset += nextOffset;
602 } while (nextOffset != 0);
604 if (oldName != null) {
605 Debug.Assert(false, "FileSystemWatcher: FILE_ACTION_RENAMED_OLD_NAME with no" +
606 "new name! [" + oldName + "]");
608 NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
615 Overlapped.Free(overlappedPointer);
616 if (!stopListening && !runOnce) {
617 Monitor(asyncResult.buffer);
624 protected override void Dispose(bool disposing) {
628 //Stop raising events cleans up managed and
629 //unmanaged resources.
632 // Clean up managed resources
633 onChangedHandler = null;
634 onCreatedHandler = null;
635 onDeletedHandler = null;
636 onRenamedHandler = null;
637 onErrorHandler = null;
641 stopListening = true;
643 // Clean up unmanaged resources
644 if (!IsHandleInvalid) {
645 directoryHandle.Close();
650 this.disposed = true;
651 base.Dispose(disposing);
657 /// Notifies the object that initialization is complete.
660 public void EndInit() {
661 initializing = false;
662 // Unless user told us NOT to start after initialization, we'll start listening
664 if (directory.Length != 0 && enabled == true)
665 StartRaisingEvents();
670 /// Returns true if the component is either in a Begin/End Init block or in design mode.
674 private bool IsSuspended() {
675 return initializing || DesignMode;
679 /// Sees if the name given matches the name filter we have.
682 private bool MatchPattern(string relativePath) {
683 string name = System.IO.Path.GetFileName(relativePath);
685 return PatternMatcher.StrictMatchPattern(filter.ToUpper(CultureInfo.InvariantCulture), name.ToUpper(CultureInfo.InvariantCulture));
691 /// Calls native API and sets up handle with the directory change API.
694 [ResourceExposure(ResourceScope.None)]
695 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
696 private unsafe void Monitor(byte[] buffer) {
697 if (!enabled || IsHandleInvalid) {
701 Overlapped overlapped = new Overlapped();
702 if (buffer == null) {
704 buffer = new byte[internalBufferSize];
706 catch (OutOfMemoryException) {
707 throw new OutOfMemoryException(SR.GetString(SR.BufferSizeTooLarge, internalBufferSize.ToString(CultureInfo.CurrentCulture)));
711 // Pass "session" counter to callback:
712 FSWAsyncResult asyncResult = new FSWAsyncResult();
713 asyncResult.session = currentSession;
714 asyncResult.buffer = buffer;
716 // Pack overlapped. The buffer will be pinned by Overlapped:
717 overlapped.AsyncResult = asyncResult;
718 NativeOverlapped* overlappedPointer = overlapped.Pack(new IOCompletionCallback(this.CompletionStatusChanged), buffer);
725 // There could be a ---- in user code between calling StopRaisingEvents (where we close the handle)
726 // and when we get here from CompletionStatusChanged.
727 // We might need to take a lock to prevent ---- absolutely, instead just catch
728 // ObjectDisposedException from SafeHandle in case it is disposed
729 if (!IsHandleInvalid) {
730 // An interrupt is possible here
731 fixed (byte * buffPtr = buffer) {
732 ok = UnsafeNativeMethods.ReadDirectoryChangesW(directoryHandle,
733 new HandleRef(this, (IntPtr) buffPtr),
735 includeSubdirectories ? 1 : 0,
739 NativeMethods.NullHandleRef);
742 } catch (ObjectDisposedException ) { //Ignore
743 Debug.Assert(IsHandleInvalid, "ObjectDisposedException from something other than SafeHandle?");
744 } catch (ArgumentNullException ) { //Ignore
745 Debug.Assert(IsHandleInvalid, "ArgumentNullException from something other than SafeHandle?");
748 Overlapped.Free(overlappedPointer);
750 // If the handle was for some reason changed or closed during this call, then don't throw an
751 // exception. Else, it's a valid error.
752 if (!IsHandleInvalid) {
753 OnError(new ErrorEventArgs(new Win32Exception()));
760 /// Raises the event to each handler in the list.
763 private void NotifyFileSystemEventArgs(int action, string name) {
764 if (!MatchPattern(name)) {
769 case Direct.FILE_ACTION_ADDED:
770 OnCreated(new FileSystemEventArgs(WatcherChangeTypes.Created, directory, name));
772 case Direct.FILE_ACTION_REMOVED:
773 OnDeleted(new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, name));
775 case Direct.FILE_ACTION_MODIFIED:
776 OnChanged(new FileSystemEventArgs(WatcherChangeTypes.Changed, directory, name));
780 Debug.Fail("Unknown FileSystemEvent action type! Value: "+action);
786 /// Raises the event to each handler in the list.
789 private void NotifyInternalBufferOverflowEvent() {
790 InternalBufferOverflowException ex = new InternalBufferOverflowException(SR.GetString(SR.FSW_BufferOverflow, directory));
792 ErrorEventArgs errevent = new ErrorEventArgs(ex);
798 /// Raises the event to each handler in the list.
801 private void NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName) {
802 //filter if neither new name or old name are a match a specified pattern
803 if (!MatchPattern(name) && !MatchPattern(oldName)) {
807 RenamedEventArgs renevent = new RenamedEventArgs(action, directory, name, oldName);
813 /// Raises the <see cref='System.IO.FileSystemWatcher.Changed'/> event.
816 [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
817 protected void OnChanged(FileSystemEventArgs e) {
818 // To avoid ---- between remove handler and raising the event
819 FileSystemEventHandler changedHandler = onChangedHandler;
821 if (changedHandler != null) {
822 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
823 this.SynchronizingObject.BeginInvoke(changedHandler, new object[]{this, e});
825 changedHandler(this, e);
831 /// Raises the <see cref='System.IO.FileSystemWatcher.Created'/> event.
834 [SuppressMessage("Microsoft.Security","CA2109:ReviewVisibleEventHandlers", MessageId="0#", Justification="Changing from protected to private would be a breaking change")]
835 protected void OnCreated(FileSystemEventArgs e) {
836 // To avoid ---- between remove handler and raising the event
837 FileSystemEventHandler createdHandler = onCreatedHandler;
838 if (createdHandler != null) {
839 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
840 this.SynchronizingObject.BeginInvoke(createdHandler, new object[]{this, e});
842 createdHandler(this, e);
848 /// Raises the <see cref='System.IO.FileSystemWatcher.Deleted'/> event.
851 [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
852 protected void OnDeleted(FileSystemEventArgs e) {
853 // To avoid ---- between remove handler and raising the event
854 FileSystemEventHandler deletedHandler = onDeletedHandler;
855 if (deletedHandler != null) {
856 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
857 this.SynchronizingObject.BeginInvoke(deletedHandler, new object[]{this, e});
859 deletedHandler(this, e);
865 /// Raises the <see cref='System.IO.FileSystemWatcher.Error'/> event.
868 [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
869 protected void OnError(ErrorEventArgs e) {
870 // To avoid ---- between remove handler and raising the event
871 ErrorEventHandler errorHandler = onErrorHandler;
872 if (errorHandler != null) {
873 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
874 this.SynchronizingObject.BeginInvoke(errorHandler, new object[]{this, e});
876 errorHandler(this, e);
881 /// Internal method used for synchronous notification.
884 private void OnInternalFileSystemEventArgs(object sender, FileSystemEventArgs e) {
886 // Only change the state of the changed result if it doesn't contain a previous one.
887 if (isChanged != true) {
888 changedResult = new WaitForChangedResult(e.ChangeType, e.Name, false);
890 System.Threading.Monitor.Pulse(this);
896 /// Internal method used for synchronous notification.
899 private void OnInternalRenameEventArgs(object sender, RenamedEventArgs e) {
901 // Only change the state of the changed result if it doesn't contain a previous one.
902 if (isChanged != true) {
903 changedResult = new WaitForChangedResult(e.ChangeType, e.Name, e.OldName, false);
905 System.Threading.Monitor.Pulse(this);
912 /// Raises the <see cref='System.IO.FileSystemWatcher.Renamed'/> event.
915 [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")]
916 protected void OnRenamed(RenamedEventArgs e) {
917 RenamedEventHandler renamedHandler = onRenamedHandler;
918 if (renamedHandler != null) {
919 if (this.SynchronizingObject != null && this.SynchronizingObject.InvokeRequired)
920 this.SynchronizingObject.BeginInvoke(renamedHandler, new object[]{this, e});
922 renamedHandler(this, e);
927 /// Stops and starts this object.
930 private void Restart() {
931 if ((!IsSuspended()) && enabled) {
933 StartRaisingEvents();
939 /// Starts monitoring the specified directory.
942 [ResourceExposure(ResourceScope.None)]
943 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
944 private void StartRaisingEvents() {
945 //Cannot allocate the directoryHandle and the readBuffer if the object has been disposed; finalization has been suppressed.
947 throw new ObjectDisposedException(GetType().Name);
950 new EnvironmentPermission(PermissionState.Unrestricted).Assert();
951 if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
952 throw new PlatformNotSupportedException(SR.GetString(SR.WinNTRequired));
956 CodeAccessPermission.RevertAssert();
959 // If we're called when "Initializing" is true, set enabled to true
967 // Consider asserting path discovery permission here.
968 fullPath = System.IO.Path.GetFullPath(directory);
970 FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, fullPath);
976 // If we're attached, don't do anything.
977 if (!IsHandleInvalid) {
981 // Create handle to directory being monitored
982 directoryHandle = NativeMethods.CreateFile(directory, // Directory name
983 UnsafeNativeMethods.FILE_LIST_DIRECTORY, // access (read-write) mode
984 UnsafeNativeMethods.FILE_SHARE_READ |
985 UnsafeNativeMethods.FILE_SHARE_DELETE |
986 UnsafeNativeMethods.FILE_SHARE_WRITE, // share mode
987 null, // security descriptor
988 UnsafeNativeMethods.OPEN_EXISTING, // how to create
989 UnsafeNativeMethods.FILE_FLAG_BACKUP_SEMANTICS |
990 UnsafeNativeMethods.FILE_FLAG_OVERLAPPED, // file attributes
991 new SafeFileHandle(IntPtr.Zero, false) // file with attributes to copy
994 if (IsHandleInvalid) {
995 throw new FileNotFoundException(SR.GetString(SR.FSW_IOError, directory));
998 stopListening = false;
999 // Start ignoring all events that were initiated before this.
1000 Interlocked.Increment(ref currentSession);
1002 // Attach handle to thread pool
1004 //SECREVIEW: At this point at least FileIOPermission has already been demanded.
1005 SecurityPermission secPermission = new SecurityPermission(PermissionState.Unrestricted);
1006 secPermission.Assert();
1008 ThreadPool.BindHandle(directoryHandle);
1011 SecurityPermission.RevertAssert();
1015 // Setup IO completion port
1021 /// Stops monitoring the specified directory.
1024 private void StopRaisingEvents() {
1025 if (IsSuspended()) {
1030 // If we're not attached, do nothing.
1031 if (IsHandleInvalid) {
1035 // Close directory handle
1036 // This operation doesn't need to be atomic because the API will deal with a closed
1037 // handle appropriately.
1038 // Ensure that the directoryHandle is set to INVALID_HANDLE before closing it, so that
1039 // the Monitor() can shutdown appropriately.
1040 // If we get here while asynchronously waiting on a change notification, closing the
1041 // directory handle should cause CompletionStatusChanged be be called
1042 // thus freeing the pinned buffer.
1043 stopListening = true;
1044 directoryHandle.Close();
1045 directoryHandle = null;
1048 // Start ignoring all events occurring after this.
1049 Interlocked.Increment(ref currentSession);
1051 // Set enabled to false
1057 /// A synchronous method that returns a structure that
1058 /// contains specific information on the change that occurred, given the type
1059 /// of change that you wish to monitor.
1062 public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) {
1063 return WaitForChanged(changeType, -1);
1069 /// method that returns a structure that contains specific information on the change that occurred, given the
1070 /// type of change that you wish to monitor and the time (in milliseconds) to wait before timing out.
1073 public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) {
1074 FileSystemEventHandler dirHandler = new FileSystemEventHandler(this.OnInternalFileSystemEventArgs);
1075 RenamedEventHandler renameHandler = new RenamedEventHandler(this.OnInternalRenameEventArgs);
1077 this.isChanged = false;
1078 this.changedResult = WaitForChangedResult.TimedOutResult;
1080 // Register the internal event handler from the given change types.
1081 if ((changeType & WatcherChangeTypes.Created) != 0) {
1082 this.Created += dirHandler;
1084 if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1085 this.Deleted += dirHandler;
1087 if ((changeType & WatcherChangeTypes.Changed) != 0) {
1088 this.Changed += dirHandler;
1090 if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1091 this.Renamed += renameHandler;
1094 // Save the Enabled state of this component to revert back to it later (if needed).
1095 bool savedEnabled = EnableRaisingEvents;
1096 if (savedEnabled == false) {
1098 EnableRaisingEvents = true;
1101 // For each thread entering this wait loop, addref it and wait. When the last one
1102 // exits, reset the waiterObject.
1103 WaitForChangedResult retVal = WaitForChangedResult.TimedOutResult;
1105 if (timeout == -1) {
1106 while (!isChanged) {
1107 System.Threading.Monitor.Wait(this);
1111 System.Threading.Monitor.Wait(this, timeout, true);
1114 retVal = changedResult;
1117 // Revert the Enabled flag to its previous state.
1118 EnableRaisingEvents = savedEnabled;
1121 // Decouple the event handlers added above.
1122 if ((changeType & WatcherChangeTypes.Created) != 0) {
1123 this.Created -= dirHandler;
1125 if ((changeType & WatcherChangeTypes.Deleted) != 0) {
1126 this.Deleted -= dirHandler;
1128 if ((changeType & WatcherChangeTypes.Changed) != 0) {
1129 this.Changed -= dirHandler;
1131 if ((changeType & WatcherChangeTypes.Renamed) != 0) {
1132 this.Renamed -= renameHandler;
1135 // Return the struct.
1141 /// Helper class to hold to N/Direct call declaration and flags.
1144 System.Security.Permissions.SecurityPermissionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Flags=System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)
1146 internal static class Direct {
1147 // All possible action flags
1148 public const int FILE_ACTION_ADDED = 1;
1149 public const int FILE_ACTION_REMOVED = 2;
1150 public const int FILE_ACTION_MODIFIED = 3;
1151 public const int FILE_ACTION_RENAMED_OLD_NAME = 4;
1152 public const int FILE_ACTION_RENAMED_NEW_NAME = 5;
1155 // All possible notifications flags
1156 public const int FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001;
1157 public const int FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002;
1158 public const int FILE_NOTIFY_CHANGE_NAME = 0x00000003;
1159 public const int FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
1160 public const int FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
1161 public const int FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
1162 public const int FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020;
1163 public const int FILE_NOTIFY_CHANGE_CREATION = 0x00000040;
1164 public const int FILE_NOTIFY_CHANGE_SECURITY = 0x00000100;