// DISCLAIMER: Use this code at your own risk.
// No support is provided and this code has NOT been tested.
using System;
using System.IO;
using System.Timers;
using System.Collections;
using System.ComponentModel;
namespace SampleFileSystemWatcher
{
///
/// This class wraps FileSystemEventArgs and RenamedEventArgs
/// objects and detection of duplicate events.
///
internal class DelayedEvent
{
private readonly FileSystemEventArgs _args;
///
/// Only delayed events that are unique will be fired.
///
private bool _delayed = false;
public DelayedEvent(FileSystemEventArgs args)
{
this._args = args;
}
public FileSystemEventArgs Args
{
get
{
return this._args;
}
}
public bool Delayed
{
get
{
return this._delayed;
}
set
{
this._delayed = value;
}
}
public virtual bool IsDuplicate(object obj)
{
DelayedEvent delayedEvent = obj as DelayedEvent;
if (delayedEvent == null)
{
return false; // this is not null so they are different
}
else
{
FileSystemEventArgs eO1 = this._args;
RenamedEventArgs reO1 = this._args as RenamedEventArgs;
FileSystemEventArgs eO2 = delayedEvent._args;
RenamedEventArgs reO2 = delayedEvent._args as RenamedEventArgs;
// The events are equal only if they are of the same type (reO1 and reO2
// are both null or NOT NULL) and have all properties equal.
// We also eliminate Changed events that follow recent Created events
// because many apps create new files by creating an empty file and then
// they update the file with the file content.
return ((eO1 != null && eO2 != null && eO1.ChangeType == eO2.ChangeType
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name) &&
((reO1 == null & reO2 == null) || (reO1 != null && reO2 != null &&
reO1.OldFullPath == reO2.OldFullPath && reO1.OldName == reO2.OldName))) ||
(eO1 != null && eO2 != null && eO1.ChangeType == WatcherChangeTypes.Created
&& eO2.ChangeType == WatcherChangeTypes.Changed
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name);
}
}
}
///
/// This class wraps a FileSystemWatcher object. The class is not derived
/// from FileSystemWatcher because most of the FileSystemWatcher methods
/// are not virtual. The class was designed to resemble FileSystemWatcher class
/// as much as possible so that you can use DelayedFileSystemWatcher instead
/// of FileSystemWatcher objects.
/// DelayedFileSystemWatcher will capture all events from the FileSystemWatcher object.
/// The captured events will be delayed by at least ConsolidationInterval milliseconds in order
/// to be able to eliminate duplicate events. When duplicate events are found, the last event
/// is droped and the first event is fired (the reverse is not recomended because it could
/// cause some events not be fired at all since the last event will become the first event and
/// it won't fire a if a new similar event arrives imediately afterwards).
///
public class DelayedFileSystemWatcher
{
private FileSystemWatcher _fileSystemWatcher = null;
// Lock order is _enterThread, _events.SyncRoot
private object _enterThread = new object(); // Only one timer event is processed at any given moment
private ArrayList _events = null;
private System.Timers.Timer _serverTimer = null;
private int _msConsolidationInterval = 1000; // milliseconds
#region Delegate to FileSystemWatcher
public DelayedFileSystemWatcher()
{
this._fileSystemWatcher = new FileSystemWatcher();
this.Initialize();
}
public DelayedFileSystemWatcher(string path)
{
this._fileSystemWatcher = new FileSystemWatcher(path);
this.Initialize();
}
public DelayedFileSystemWatcher(string path, string filter)
{
this._fileSystemWatcher = new FileSystemWatcher(path, filter);
this.Initialize();
}
// Summary:
// Gets or sets a value indicating whether the component is enabled.
//
// Returns:
// 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.
public bool EnableRaisingEvents
{
get
{
return this._fileSystemWatcher.EnableRaisingEvents;
}
set
{
this._fileSystemWatcher.EnableRaisingEvents = value;
if (value)
{
this._serverTimer.Start();
}
else
{
this._serverTimer.Stop();
this._events.Clear();
}
}
}
//
// Summary:
// Gets or sets the filter string, used to determine what files are monitored
// in a directory.
//
// Returns:
// The filter string. The default is "*.*" (Watches all files.)
public string Filter
{
get
{
return this._fileSystemWatcher.Filter;
}
set
{
this._fileSystemWatcher.Filter = value;
}
}
//
// Summary:
// Gets or sets a value indicating whether subdirectories within the specified
// path should be monitored.
//
// Returns:
// true if you want to monitor subdirectories; otherwise, false. The default
// is false.
public bool IncludeSubdirectories
{
get
{
return this._fileSystemWatcher.IncludeSubdirectories;
}
set
{
this._fileSystemWatcher.IncludeSubdirectories = value;
}
}
//
// Summary:
// Gets or sets the size of the internal buffer.
//
// Returns:
// The internal buffer size. The default is 8192 (8K).
public int InternalBufferSize
{
get
{
return this._fileSystemWatcher.InternalBufferSize;
}
set
{
this._fileSystemWatcher.InternalBufferSize = value;
}
}
//
// Summary:
// Gets or sets the type of changes to watch for.
//
// Returns:
// One of the System.IO.NotifyFilters values. The default is the bitwise OR
// combination of LastWrite, FileName, and DirectoryName.
//
// Exceptions:
// System.ArgumentException:
// The value is not a valid bitwise OR combination of the System.IO.NotifyFilters
// values.
public NotifyFilters NotifyFilter
{
get
{
return this._fileSystemWatcher.NotifyFilter;
}
set
{
this._fileSystemWatcher.NotifyFilter = value;
}
}
//
// Summary:
// Gets or sets the path of the directory to watch.
//
// Returns:
// The path to monitor. The default is an empty string ("").
//
// Exceptions:
// System.ArgumentException:
// The specified path contains wildcard characters.-or- The specified path contains
// invalid path characters.
public string Path
{
get
{
return this._fileSystemWatcher.Path;
}
set
{
this._fileSystemWatcher.Path = value;
}
}
//
// Summary:
// Gets or sets the object used to marshal the event handler calls issued as
// a result of a directory change.
//
// Returns:
// The System.ComponentModel.ISynchronizeInvoke 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
{
return this._fileSystemWatcher.SynchronizingObject;
}
set
{
this._fileSystemWatcher.SynchronizingObject = value;
}
}
// Summary:
// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path
// is changed.
public event FileSystemEventHandler Changed;
//
// Summary:
// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path
// is created.
public event FileSystemEventHandler Created;
//
// Summary:
// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path
// is deleted.
public event FileSystemEventHandler Deleted;
//
// Summary:
// Occurs when the internal buffer overflows.
public event ErrorEventHandler Error;
//
// Summary:
// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path
// is renamed.
public event RenamedEventHandler Renamed;
// Summary:
// Begins the initialization of a System.IO.FileSystemWatcher used on a form
// or used by another component. The initialization occurs at run time.
public void BeginInit()
{
this._fileSystemWatcher.BeginInit();
}
//
// Summary:
// Releases the unmanaged resources used by the System.IO.FileSystemWatcher
// and optionally releases the managed resources.
//
// Parameters:
// disposing:
// true to release both managed and unmanaged resources; false to release only
// unmanaged resources.
public void Dispose()
{
this.Uninitialize();
}
//
// Summary:
// Ends the initialization of a System.IO.FileSystemWatcher used on a form or
// used by another component. The initialization occurs at run time.
public void EndInit()
{
this._fileSystemWatcher.EndInit();
}
//
// Summary:
// Raises the System.IO.FileSystemWatcher.Changed event.
//
// Parameters:
// e:
// A System.IO.FileSystemEventArgs that contains the event data.
protected void OnChanged(FileSystemEventArgs e)
{
if (this.Changed != null)
{
this.Changed(this, e);
}
}
//
// Summary:
// Raises the System.IO.FileSystemWatcher.Created event.
//
// Parameters:
// e:
// A System.IO.FileSystemEventArgs that contains the event data.
protected void OnCreated(FileSystemEventArgs e)
{
if (this.Created != null)
{
this.Created(this, e);
}
}
//
// Summary:
// Raises the System.IO.FileSystemWatcher.Deleted event.
//
// Parameters:
// e:
// A System.IO.FileSystemEventArgs that contains the event data.
protected void OnDeleted(FileSystemEventArgs e)
{
if (this.Deleted != null)
{
this.Deleted(this, e);
}
}
//
// Summary:
// Raises the System.IO.FileSystemWatcher.Error event.
//
// Parameters:
// e:
// An System.IO.ErrorEventArgs that contains the event data.
protected void OnError(ErrorEventArgs e)
{
if (this.Error != null)
{
this.Error(this, e);
}
}
//
// Summary:
// Raises the System.IO.FileSystemWatcher.Renamed event.
//
// Parameters:
// e:
// A System.IO.RenamedEventArgs that contains the event data.
protected void OnRenamed(RenamedEventArgs e)
{
if (this.Renamed != null)
{
this.Renamed(this, e);
}
}
//
// Summary:
// 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.
//
// Parameters:
// changeType:
// The System.IO.WatcherChangeTypes to watch for.
//
// Returns:
// A System.IO.WaitForChangedResult that contains specific information on the
// change that occurred.
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType)
{
//TODO
throw new NotImplementedException();
}
//
// Summary:
// 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.
//
// Parameters:
// timeout:
// The time (in milliseconds) to wait before timing out.
//
// changeType:
// The System.IO.WatcherChangeTypes to watch for.
//
// Returns:
// A System.IO.WaitForChangedResult that contains specific information on the
// change that occurred.
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout)
{
//TODO
throw new NotImplementedException();
}
#endregion
#region Implementation
private void Initialize()
{
this._events = ArrayList.Synchronized(new ArrayList(32));
this._fileSystemWatcher.Changed += new FileSystemEventHandler(this.FileSystemEventHandler);
this._fileSystemWatcher.Created += new FileSystemEventHandler(this.FileSystemEventHandler);
this._fileSystemWatcher.Deleted += new FileSystemEventHandler(this.FileSystemEventHandler);
this._fileSystemWatcher.Error += new ErrorEventHandler(this.ErrorEventHandler);
this._fileSystemWatcher.Renamed += new RenamedEventHandler(this.RenamedEventHandler);
this._serverTimer = new Timer(this._msConsolidationInterval);
this._serverTimer.Elapsed += new ElapsedEventHandler(this.ElapsedEventHandler);
this._serverTimer.AutoReset = true;
this._serverTimer.Enabled = this._fileSystemWatcher.EnableRaisingEvents;
}
private void Uninitialize()
{
if (this._fileSystemWatcher != null)
{
this._fileSystemWatcher.Dispose();
}
if (this._serverTimer != null)
{
this._serverTimer.Dispose();
}
}
private void FileSystemEventHandler(object sender, FileSystemEventArgs e)
{
this._events.Add(new DelayedEvent(e));
}
private void ErrorEventHandler(object sender, ErrorEventArgs e)
{
this.OnError(e);
}
private void RenamedEventHandler(object sender, RenamedEventArgs e)
{
this._events.Add(new DelayedEvent(e));
}
private void ElapsedEventHandler(Object sender, ElapsedEventArgs e)
{
// We don't fire the events inside the lock. We will queue them here until
// the code exits the locks.
Queue eventsToBeFired = null;
if (System.Threading.Monitor.TryEnter(this._enterThread))
{
// Only one thread at a time is processing the events
try
{
eventsToBeFired = new Queue(32);
// Lock the collection while processing the events
lock (this._events.SyncRoot)
{
DelayedEvent current = null;
for(int i=0; i 0))
{
DelayedEvent de = null;
while (deQueue.Count > 0)
{
de = deQueue.Dequeue() as DelayedEvent;
switch (de.Args.ChangeType)
{
case WatcherChangeTypes.Changed:
this.OnChanged(de.Args);
break;
case WatcherChangeTypes.Created:
this.OnCreated(de.Args);
break;
case WatcherChangeTypes.Deleted:
this.OnDeleted(de.Args);
break;
case WatcherChangeTypes.Renamed:
this.OnRenamed(de.Args as RenamedEventArgs);
break;
}
}
}
}
#endregion
}
}