Enhanced FileSystemWatcher






4.45/5 (18 votes)
Enhanced FileSystemWatcher class, which can be used to suppress duplicate events that fire on a single change to the file
Introduction
This article discusses an enhanced FileSystemWatcher
class which can be used to suppress duplicate events that fire on a single change to the file.
Background
System.IO.FileSystemWatcher
class helps the user to monitor a directory and multiple or single file within a directory. Whenever a change (Creation, Modification, Deletion or Renaming) is detected, an appropriate event is raised. However, duplicate events fire depending on the software that is being used to modify the file.
Observation
Using Notepad, modifying the contents of a file results in 2 Changed events being fired. Doing the same using Textpad results in 4 Changed events being fired.
Using Textpad, creating a file in a directory that was being watched resulted in 1 Created and 3 Changed events being fired. In case of Notepad, Created followed by Deleted!!, followed by Created and 3 Changed Events were observed.
My guess is that multiple events are fired depending on number of times the file system is accessed by particular software while performing any given operation (Save, Delete, Save As, etc.).
Proposed Solution
We need to keep track of the event as they get fired and suppress subsequent events that occur within pre-determined interval.
We create a class "MyFileSystemWatcher
" that inherits from System.IO.FileSystemWatcher
. We also create a dictionary to keep track of when the last event occurred for a file, along with some other private
members.
public class MyFileSystemWatcher : FileSystemWatcher, IDisposable
{
#region Private Members
// This Dictionary keeps the track of when an event occurred
// last for a particular file
private Dictionary _lastFileEvent;
// Interval in Millisecond
private int _interval;
//Timespan created when interval is set
private TimeSpan _recentTimeSpan;
#endregion
........
}
We delegate the construction of the object to FileSystemWatcher
Base class and initialize the private
members in the InitializeMembers private
method.
// Constructors delegate to the base class constructors and
// call private InitializeMember method
public MyFileSystemWatcher() : base()
{
InitializeMembers();
}
public MyFileSystemWatcher(string Path) : base(Path)
{
InitializeMembers();
}
public MyFileSystemWatcher(string Path, string Filter) : base(Path, Filter)
{
InitializeMembers();
}
///
/// This Method Initializes the private members.
/// Interval is set to its default value of 100 millisecond
/// FilterRecentEvents is set to true, _lastFileEvent dictionary is initialized
/// We subscribe to the base class events.
///
private void InitializeMembers()
{
Interval = 100;
FilterRecentEvents = true;
_lastFileEvent = new Dictionary();
base.Created += new FileSystemEventHandler(OnCreated);
base.Changed += new FileSystemEventHandler(OnChanged);
base.Deleted += new FileSystemEventHandler(OnDeleted);
base.Renamed += new RenamedEventHandler(OnRenamed);
}
We create a method to determine if a recent event has occurred for a particular file.
/// <summary>
/// This method searches the dictionary to find out when the last event occurred
/// for a particular file. If that event occurred within the specified timespan
/// it returns true, else false
/// </summary>
/// <param name="FileName">The filename to be checked</param>
/// <returns>True if an event has occurred within the specified interval,
/// False otherwise</returns>
private bool HasAnotherFileEventOccuredRecently(string FileName)
{
bool retVal = false;
// Check dictionary only if user wants to filter recent events
// otherwise return Value stays False
if (FilterRecentEvents)
{
if (_lastFileEvent.ContainsKey(FileName))
{
// If dictionary contains the filename, check how much time has elapsed
// since the last event occurred. If the timespan is less that the
// specified interval, set return value to true
// and store current datetime in dictionary for this file
DateTime lastEventTime = _lastFileEvent[FileName];
DateTime currentTime = DateTime.Now;
TimeSpan timeSinceLastEvent = currentTime - lastEventTime;
retVal = timeSinceLastEvent < _recentTimeSpan;
_lastFileEvent[FileName] = currentTime;
// If dictionary does not contain the filename,
// no event has occurred in past for this file, so set return value to false
// and filename alongwith current datetime to the dictionary
_lastFileEvent.Add(FileName, DateTime.Now);
retVal = false;
}
}
return retVal;
}
We create Event Handler for base class event; check if a recent event has occurred and if not we raise the event. The code snippet shows this approach for "Changed
" event only, see attached code for other events like "Created
", "Renamed
" and "Deleted
".
// These events hide the events from the base class.
// We want to raise these events appropriately and we do not want the
// users of this class subscribing to these events of the base class accidentally
public new event FileSystemEventHandler Changed;
// Base class Event Handlers. Check if an event has occurred recently and call method
// to raise appropriate event only if no recent event is detected
private void OnChanged(object sender, FileSystemEventArgs e)
{
if (!HasAnotherFileEventOccuredRecently(e.FullPath))
this.OnChanged(e);
}
// Protected Methods to raise the Events for this class
protected new virtual void OnChanged(FileSystemEventArgs e)
{
if (Changed != null) Changed(this, e);
}
Output Comparison
Conclusion
The public
interface of this class matches that of a file system watcher class so it can be replaced easily across code, especially if you are using a Factory
to create a FileSystemWatcher
object.
Other situations might arise that this code does not handle (based on what software is being used to edit the file). Constructive criticism is welcome. Hope this is useful for some readers.
History
- 16th August, 2010: Initial post