public class FileChangedEvent { public FileChangedEvent(string fullPath, string fileName, WatcherChangeTypes changeType) { FullPath = fullPath; FileName = fileName; ChangeType = changeType; } // Full file path and name public string FullPath { get; private set; } // File name only public string FileName { get; private set; } // The type of change public WatcherChangeTypes ChangeType { get; private set; } } public interface IFileChangeMonitor { void Dispose(); string DirectoryPath { get; } void Start(); public bool IsActive { get; } void Stop(); IDisposable SubscribeOnChanged(string fileName, Action<FileChangedEvent> onChanged, double throttle_ms = 100); IDisposable SubscribeOnError(Action<ErrorEventArgs> onError); } public class FileChangeMonitor : IDisposable, IFileChangeMonitor { // https://weblogs.asp.net/ashben/31773 private readonly FileSystemWatcher _fileSystemWatcher; private readonly DirectoryInfo _directory; // directory being monitored private const string DefaultFileFilter = @"*.json"; // TODO: add NotifyFilter parameter public FileChangeMonitor(DirectoryInfo dir, string defaultFileFilter = DefaultFileFilter) { //const int MAX_NUM_FILES_TO_MONITOR = 10; //const int AVG_FILE_NAME_LENGTH = 30; //const int FOUR_KB = 1024 * 4; Debug.Assert(dir != null); Debug.Assert(dir.Exists == true); _directory = new DirectoryInfo(dir.FullName); //int bufferSize = ((MAX_NUM_FILES_TO_MONITOR * // (16 + AVG_FILE_NAME_LENGTH * 2) % FOUR_KB) + 1) * FOUR_KB; _fileSystemWatcher = new FileSystemWatcher(_directory.FullName) { //InternalBufferSize = bufferSize, default is enough NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite, Filter = defaultFileFilter, IncludeSubdirectories = false }; } public string DirectoryPath => _directory?.FullName; public IDisposable SubscribeOnChanged(string fileName, Action<FileChangedEvent> onChanged, double throttle_ms = 200) { Debug.Assert(_directory?.Exists == true); Trace.WriteLine($"*** Subscribing for changes to {Path.Combine(DirectoryPath, fileName)}"); return GetFileChangedPublisher(fileName, throttle_ms) .Subscribe(onChanged); } public void Start() { Debug.Assert(_directory?.Exists == true); _fileSystemWatcher.EnableRaisingEvents = true; } public bool IsActive => _fileSystemWatcher.EnableRaisingEvents; public void Stop() { Debug.Assert(_directory?.Exists == true); _fileSystemWatcher.EnableRaisingEvents = false; } public void Dispose() { if (IsActive) Stop(); GC.SuppressFinalize(this); _fileSystemWatcher.Dispose(); } // Returns an IObservable, which is effectively a publisher which // can be subscribed to and disposed of when finished with private IObservable<FileChangedEvent> GetFileChangedPublisher( string targetFileName, double throttle_ms = 200) { Debug.Assert(_directory?.Exists == true); IObservable<EventPattern<FileSystemEventArgs>> fswCreated = Observable.FromEventPattern<FileSystemEventArgs>(_fileSystemWatcher, "Changed"); return fswCreated. Where((EventPattern<FileSystemEventArgs> ev) => ev.EventArgs.Name == targetFileName). // Rx Extension Sample() will only allow the last event within a time // interval to pass through. This effectively throttles events Sample(TimeSpan.FromMilliseconds(throttle_ms)). Select(ev => new FileChangedEvent(ev.EventArgs.FullPath, ev.EventArgs.Name, ev.EventArgs.ChangeType)); } private IObservable<ErrorEventArgs> GetErrorPublisher() { Debug.Assert(_directory?.Exists == true); return Observable .FromEventPattern<ErrorEventHandler, ErrorEventArgs>( ev => _fileSystemWatcher.Error += ev, ev => _fileSystemWatcher.Error -= ev) .Select(x => x.EventArgs); } public IDisposable SubscribeOnError(Action<ErrorEventArgs> onError) { Debug.Assert(_directory?.Exists == true); return GetErrorPublisher().Subscribe(onError); } }
The thing is it is an example of how to integrate React with a file system watcher or an existing class that publishes events using standard C# events. Here is an example of how to use the service
DirectoryInfo dir = new DirectoryInfo("\\Some\Directory"); FileChangeMonitor datafileMonitor = new FileChangeMonitor(dir); datafileMonitor.Start(); ... // Subscribed to just 1 file but we could subscribe to multiple in the same directory IDisposable subscription = datafileMonitor.SubscribeOnChanged( "FileToMonitor.json", fce => DoSomethingWith(fce)); ... subscription.Dispose(); // Finish with our subscription to a specific file's changes ... datafileMonitor.Stop(); // Optionally we could just call just Dispose() datafileMonitor.Dispose();
We could change the class so that the type of changes monitored could be passed in through the class constructor (the NotifyFilter enumeration). Also, an option to change the internal buffer size could be required.
No comments:
Post a Comment