//****************************************************************************************************** // SafeFileWatcher.cs - Gbtc // // Copyright © 2015, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 07/28/2015 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security; namespace GSF.IO { /// /// Represents a wrapper around the native .NET that avoids problems with /// dangling references when using a file watcher instance as a class member that never gets disposed. /// /// /// /// The design goal of the SafeFileWatcher is to avoid accidental memory leaks caused by use of .NET's native /// file system watcher when used as a member of a class that consumers fail to properly dispose. If a class /// has a reference to a file watcher as a member variable and attaches to the file watcher's events, the /// file watcher will maintain a reference the parent class so it can call its event handler. If the parent /// class is not disposed properly, the file watcher will thusly not be disposed and will maintain the /// reference to the parent class - the garbage collector will never collect the parent because it has a /// valid reference and no collection means the parent finalizer will never get called and the file system /// watcher will never get disposed. Creating multiple instances of parent class and not disposing of them /// will cause a memory leak even with a properly designed disposable pattern. Using the SafeFileWatcher /// instead of directly using the FileSystemWatcher will resolve this potential issue. /// /// /// Note that component model implementation is not fully replicated - if you are using a file system watcher /// on a design surface, this safety wrapper will usually not be needed. This class has benefit when a class /// will dynamically use a file watcher and needs to make sure any unmanaged resources get properly released /// even if a consumer neglects to call the dispose function. /// /// [SecurityCritical] public class SafeFileWatcher : IDisposable { #region [ Members ] // Events /// /// Occurs when a file or directory in the specified is changed. /// public event FileSystemEventHandler Changed; /// /// Occurs when a file or directory in the specified is created. /// public event FileSystemEventHandler Created; /// /// Occurs when a file or directory in the specified is deleted. /// public event FileSystemEventHandler Deleted; /// /// Occurs when a file or directory in the specified is renamed. /// public event RenamedEventHandler Renamed; /// /// Occurs when the internal buffer overflows. /// public event ErrorEventHandler Error; // Fields private readonly FileSystemWatcher m_fileSystemWatcher; private bool m_disposed; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// public SafeFileWatcher() { m_fileSystemWatcher = new FileSystemWatcher(); InitializeFileSystemWatcher(); } /// /// Initializes a new instance of the class, given the specified directory to monitor. /// /// The directory to monitor, in standard or Universal Naming Convention (UNC) notation. /// /// The parameter is null. /// /// The parameter is an empty string (""). -or- /// The path specified through the parameter does not exist. /// public SafeFileWatcher(string path) { m_fileSystemWatcher = new FileSystemWatcher(path); InitializeFileSystemWatcher(); } /// /// Initializes a new instance of the class, given the specified directory and type of files to monitor. /// /// The directory to monitor, in standard or Universal Naming Convention (UNC) notation. /// The type of files to watch. For example, "*.txt" watches for changes to all text files. /// /// The parameter is null. -or- /// The parameter is null. /// /// /// The parameter is an empty string (""). -or- /// The path specified through the parameter does not exist. /// public SafeFileWatcher(string path, string filter) { m_fileSystemWatcher = new FileSystemWatcher(path, filter); InitializeFileSystemWatcher(); } // Attach to file system watcher events via lambda function using a weak reference to this class instance // connected through static method so that file watcher cannot hold onto a reference to this class - this // way even if consumer neglects to dispose this class, it will get properly garbage collected and finalized // because there will be no remaining references to this class instance. Also, even though the following // intermediate lambda classes that get created will be attached to the file system watcher event handlers, // they will also be freed because this class will make sure the file system watcher instance is handled // like an unmanaged resource, i.e., it always gets disposed, via the finalizer if need be. private void InitializeFileSystemWatcher() { WeakReference reference = new WeakReference(this); m_fileSystemWatcher.Changed += (_, e) => OnChanged(reference, e); m_fileSystemWatcher.Created += (_, e) => OnCreated(reference, e); m_fileSystemWatcher.Deleted += (_, e) => OnDeleted(reference, e); m_fileSystemWatcher.Renamed += (_, e) => OnRenamed(reference, e); m_fileSystemWatcher.Error += (_, e) => OnError(reference, e); } /// /// Terminates instance making sure to release unmanaged resources. /// ~SafeFileWatcher() { // Finalizer desired because we want to dispose FileSystemWatcher to force release of pinned buffers // and thus release reference to any associated event delegates allowing garbage collection Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets the path of the directory to watch. /// /// /// The path to monitor. The default is an empty string (""). /// /// /// The specified path does not exist or could not be found. -or- /// The specified path contains wildcard characters. -or- /// The specified path contains invalid path characters. /// public string Path { get => m_fileSystemWatcher.Path; set => m_fileSystemWatcher.Path = value; } /// /// Gets or sets the filter string used to determine what files are monitored in a directory. /// /// /// The filter string. The default is "*.*" (Watches all files.) /// public string Filter { get => m_fileSystemWatcher.Filter; set => m_fileSystemWatcher.Filter = value; } /// /// Gets or sets the type of changes to watch for. /// /// /// One of the values. The default is the bitwise OR combination of LastWrite, FileName, and DirectoryName. /// The value is not a valid bitwise OR combination of the values. /// The value that is being set is not valid. public NotifyFilters NotifyFilter { get => m_fileSystemWatcher.NotifyFilter; set => m_fileSystemWatcher.NotifyFilter = value; } /// /// Gets or sets a value indicating whether the component is enabled. /// /// /// true if the component is enabled; otherwise, false. The default is false. If you are using the component on a designer in Visual Studio 2005, the default is true. /// The object has been disposed. /// The current operating system is not Microsoft Windows NT or later. /// The directory specified in could not be found. /// has not been set or is invalid. public bool EnableRaisingEvents { get => m_fileSystemWatcher.EnableRaisingEvents; set => m_fileSystemWatcher.EnableRaisingEvents = value; } /// /// Gets or sets a value indicating whether subdirectories within the specified path should be monitored. /// /// /// true if you want to monitor subdirectories; otherwise, false. The default is false. /// public bool IncludeSubdirectories { get => m_fileSystemWatcher.IncludeSubdirectories; set => m_fileSystemWatcher.IncludeSubdirectories = value; } /// /// Gets or sets the size of the internal buffer. /// /// /// The internal buffer size. The default is 8192 (8K). /// public int InternalBufferSize { get => m_fileSystemWatcher.InternalBufferSize; set => m_fileSystemWatcher.InternalBufferSize = value; } /// /// Gets or sets the object used to marshal the event handler calls issued as a result of a directory change. /// /// /// The that represents the object used to marshal the event handler calls issued as a result of a directory change. The default is null. /// public ISynchronizeInvoke SynchronizingObject { get => m_fileSystemWatcher.SynchronizingObject; set => m_fileSystemWatcher.SynchronizingObject = value; } /// /// Gets or sets an for the . /// /// /// An for the . /// public ISite Site { get => m_fileSystemWatcher.Site; set => m_fileSystemWatcher.Site = value; } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_fileSystemWatcher")] protected virtual void Dispose(bool disposing) { if (m_disposed) return; try { // Treating file system watcher like an unmanaged resource m_fileSystemWatcher?.Dispose(); } finally { m_disposed = true; } } /// /// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor. /// /// A that contains specific information on the change that occurred. /// The to watch for. public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType) => m_fileSystemWatcher.WaitForChanged(changeType); /// /// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor and the time (in milliseconds) to wait before timing out. /// /// A that contains specific information on the change that occurred. /// The to watch for. /// The time (in milliseconds) to wait before timing out. public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout) => m_fileSystemWatcher.WaitForChanged(changeType, timeout); /// /// Begins the initialization of a used on a form or used by another component. The initialization occurs at run time. /// public void BeginInit() => m_fileSystemWatcher.BeginInit(); /// /// Ends the initialization of a used on a form or used by another component. The initialization occurs at run time. /// public void EndInit() => m_fileSystemWatcher.EndInit(); private void OnChanged(FileSystemEventArgs e) => Changed?.Invoke(this, e); private void OnCreated(FileSystemEventArgs e) => Created?.Invoke(this, e); private void OnDeleted(FileSystemEventArgs e) => Deleted?.Invoke(this, e); private void OnRenamed(RenamedEventArgs e) => Renamed?.Invoke(this, e); private void OnError(ErrorEventArgs e) => Error?.Invoke(this, e); #endregion #region [ Static ] // Static Methods private static void OnChanged(WeakReference reference, FileSystemEventArgs e) { if (reference.TryGetTarget(out SafeFileWatcher instance)) instance.OnChanged(e); } private static void OnCreated(WeakReference reference, FileSystemEventArgs e) { if (reference.TryGetTarget(out SafeFileWatcher instance)) instance.OnCreated(e); } private static void OnDeleted(WeakReference reference, FileSystemEventArgs e) { if (reference.TryGetTarget(out SafeFileWatcher instance)) instance.OnDeleted(e); } private static void OnRenamed(WeakReference reference, RenamedEventArgs e) { if (reference.TryGetTarget(out SafeFileWatcher instance)) instance.OnRenamed(e); } private static void OnError(WeakReference reference, ErrorEventArgs e) { if (reference.TryGetTarget(out SafeFileWatcher instance)) instance.OnError(e); } #endregion } }