2
\$\begingroup\$

For a class last fall I was working on a "cloud backup" system. Originally in Pascal, I decided to rewrite it in C# and expand the feature set. Below is the FileSystemWatcher "manager" class which captures events from one to many FileSystemWatchers. It doesn't trigger any events of its own, yet, but I'm curious if the "debounce" technique makes sense.

using System;
using System.Text;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System.Collections.Concurrent;

namespace FileWatcher
{
    class EventData
    {
        public String eventFile;
        public DateTime eventTime;
        public WatcherChangeTypes eventType;
        public FileSystemEventArgs referenceEventArgs;
    }

    class FileWatchManager
    {
        private Dictionary<String, FileSystemWatcher> watchList;
        private ConcurrentDictionary<String, EventData> fileEvents;
        private Object eventSync;
        private bool watchStatus;
        private System.Timers.Timer debounceTimer;

        public FileWatchManager()
        {
            this.watchList = new Dictionary<string, FileSystemWatcher>();
            this.watchStatus = false;

            this.fileEvents = new ConcurrentDictionary<String, EventData>();
            eventSync = new Object();

            debounceTimer = new System.Timers.Timer(100);
            debounceTimer.Elapsed += DebounceTimerComplete;
        }

        public void AddDirectory(string directory, bool subdirectories = false)
        {
            if(! System.IO.Directory.Exists(directory))
                return;

            string directoryHash = CryptoHelper.GetStringSha256(directory);
            FileSystemWatcher newWatcher = new FileSystemWatcher();
            newWatcher.Path = directory;
            newWatcher.Created += this.FileCreateHandler;
            newWatcher.Changed += this.FileChangedHandler;
            newWatcher.Deleted += this.FileDeletedHandler;
            newWatcher.Renamed += this.FileRenamedHandler;
            //newWatcher.Renamed
            newWatcher.IncludeSubdirectories = subdirectories;

            // Possibly start the watching
            newWatcher.EnableRaisingEvents = this.watchStatus;

            this.watchList.Add(directoryHash, newWatcher);
        }

        public void RemoveDirectory(string directory)
        {
            string directoryHash = CryptoHelper.GetStringSha256(directory);

            if(! this.watchList.ContainsKey(directoryHash))
                return;

            this.watchList[directoryHash].EnableRaisingEvents = false;
            this.watchList.Remove(directoryHash);
        }

        public void Start()
        {
            this.watchStatus = true;
            foreach(KeyValuePair<String, FileSystemWatcher> singleWatcher in this.watchList)
            {
                singleWatcher.Value.EnableRaisingEvents = true;
            }

            debounceTimer.Enabled = true;
        }

        public void Stop()
        {
            this.watchStatus = false;
            foreach(KeyValuePair<String, FileSystemWatcher> singleWatcher in this.watchList)
            {
                singleWatcher.Value.EnableRaisingEvents = false;
            }
        }

        public void PauseDirectory(string directory)
        {
            this.setRaiseEvent(directory, false);
        }

        public void ResumeDirectory(string directory)
        {
            this.setRaiseEvent(directory, true);
        }

        private void FileCreateHandler(object source, FileSystemEventArgs e)
        {
            string fileHash = CryptoHelper.GetStringMd5(e.FullPath);
            lock(eventSync)
            {
                this.fileEvents.TryAdd(fileHash, new EventData {
                    eventFile = e.FullPath,
                    eventTime = DateTime.Now,
                    eventType = WatcherChangeTypes.Created,
                    referenceEventArgs = e
                });

                this.debounceTimer.Enabled = true;
            }
        }

        private void FileChangedHandler(object source, FileSystemEventArgs e)
        {
            string fileHash = CryptoHelper.GetStringMd5(e.FullPath);
            lock(eventSync)
            {
                if(fileEvents.ContainsKey(fileHash))
                {
                    // Update the time of the event to now
                    fileEvents[fileHash].eventTime = DateTime.Now;
                } else {
                    fileEvents.TryAdd(fileHash, new EventData{
                        eventFile = e.FullPath,
                        eventTime = DateTime.Now,
                        eventType = WatcherChangeTypes.Changed,
                        referenceEventArgs = e
                    });
                }

                debounceTimer.Enabled = true;
            }
        }

        private void FileDeletedHandler(object source, FileSystemEventArgs e)
        {
            string fileHash = CryptoHelper.GetStringMd5(e.FullPath);
            lock(eventSync)
            {
                if(fileEvents.ContainsKey(fileHash))
                {
                    fileEvents[fileHash].eventTime = DateTime.Now;
                    fileEvents[fileHash].eventType = WatcherChangeTypes.Deleted;
                } else {
                    fileEvents.TryAdd(fileHash, new EventData {
                        eventFile = e.FullPath,
                        eventTime = DateTime.Now,
                        eventType = WatcherChangeTypes.Deleted,
                        referenceEventArgs = e
                    });
                }

                debounceTimer.Enabled = true;
            }
        }

        private void FileRenamedHandler(Object source, RenamedEventArgs e)
        {
            string oldFileHash = CryptoHelper.GetStringMd5(e.OldFullPath);
            string newFileHash = CryptoHelper.GetStringMd5(e.FullPath);
            lock(eventSync)
            {               
                EventData oldEvent; 
                if(fileEvents.TryRemove(oldFileHash, out oldEvent))
                {
                    oldEvent.eventTime = DateTime.Now;
                    oldEvent.eventFile = e.FullPath;
                    oldEvent.eventType |= WatcherChangeTypes.Renamed;
                    // keep reference data the same so old file can be kept track of

                    fileEvents.TryAdd(newFileHash, oldEvent);
                }

            }
        }

        private void setRaiseEvent(string directory, Boolean status)
        {
            string directoryHash = CryptoHelper.GetStringSha256(directory);
            if(! this.watchList.ContainsKey(directoryHash))
                return;

            this.watchList[directoryHash].EnableRaisingEvents = status;
        }

        private void DebounceTimerComplete(Object source, System.Timers.ElapsedEventArgs e)
        {
            if(Monitor.TryEnter(this.eventSync))
            {
                if(this.fileEvents.IsEmpty)
                {
                    this.debounceTimer.Enabled = false;
                    Monitor.Exit(eventSync);
                    return;
                }

                EventData myEvent;
                foreach(var fileEvent in this.fileEvents)
                {
                    if(DateTime.Now.Subtract(fileEvent.Value.eventTime).TotalMilliseconds > debounceTimer.Interval)
                    {        
                        fileEvents.TryRemove(fileEvent.Key, out myEvent);

                        Console.WriteLine(fileEvent.Key);
                        Console.WriteLine(myEvent.eventFile + " event @ " + myEvent.eventTime);

                        if(myEvent.eventType.HasFlag(WatcherChangeTypes.Renamed))
                        {
                            Console.WriteLine("File Was Renamed!!!!!");
                        }
                    }
                }

                Monitor.Exit(this.eventSync);
            }
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.