//******************************************************************************************************
// 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
}
}