using System; using System.Collections.Concurrent; using System.IO; using System.Threading; using System.Threading.Tasks; namespace JD.Core { /// /// A wrapper around the System.IO.FileSystemWatcher class. /// /// As the System.IO.FileSystemWatcher class may be unreliable under heavy charge, the wrapper transmits the events to a local queue and /// another thread will execute events on a regular and configurable way. You can disable this feature to make it behave like the /// original System.IO.FileSystemWatcher. /// Please notice that, even with this feature, there is no guarantee that this implementation will catch all events, and that this implementation /// shall not be used on network drives. /// public class FileWatcher : IDisposable { private string _path; private string _filter; private string _error = ""; private FileSystemWatcher _watcher; private bool _immediateEventCall; private int _pollingInterval = 10; private int _bufferSize = 8192; private readonly ConcurrentQueue _fileEventQueue; private FileSystemEventHandler _onCreated; private FileSystemEventHandler _onChanged; private FileSystemEventHandler _onDeleted; private RenamedEventHandler _onRenamed; private readonly FileSystemEventHandler _onCreatedDeferred; private readonly FileSystemEventHandler _onChangedDeferred; private readonly FileSystemEventHandler _onDeletedDeferred; private readonly RenamedEventHandler _onRenamedDeferred; private Task _task; private CancellationTokenSource _cancellationTokenSource; public FileWatcher(string path, string filter = "*.*") { _path = path; _filter = filter; _fileEventQueue = new ConcurrentQueue(); _onCreatedDeferred = (sender, e) => _fileEventQueue.Enqueue(new FileEvent { Type = FileEventType.Created, Args = e }); _onChangedDeferred = (sender, e) => _fileEventQueue.Enqueue(new FileEvent { Type = FileEventType.Changed, Args = e }); _onDeletedDeferred = (sender, e) => _fileEventQueue.Enqueue(new FileEvent { Type = FileEventType.Created, Args = e }); _onRenamedDeferred = (sender, e) => _fileEventQueue.Enqueue(new FileEvent { Type = FileEventType.Renamed, Args = e }); } public void Dispose() { Stop(); _watcher.Dispose(); _watcher = null; DisposeTask(); } private void DisposeTask() { if (_task != null) { if (!_task.IsCompleted) _task.Wait(); _task.Dispose(); _task = null; } } public string Path { get { return _path; } set { if (_watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the Path property. "); _path = value; } } public string Filter { get { return _filter; } set { if (_watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the Filter property. "); _filter = value; } } public int BufferSize { get { return _bufferSize; } set { if (_watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the BufferSize property. "); _bufferSize = value; } } public bool ImmediateEventCall { get { return _immediateEventCall; } set { if (_watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the ImmediateEventCall property. "); _immediateEventCall = value; } } public int PollingInterval { get { return _pollingInterval; } set { _pollingInterval = value; } } public FileSystemEventHandler FileChanged { get { return _onChanged; } set { if (_watcher != null && _watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the FileChanged property. "); _onChanged = value; } } public FileSystemEventHandler FileCreated { get { return _onCreated; } set { if (_watcher != null && _watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the FileChanged property. "); _onCreated = value; } } public FileSystemEventHandler FileDeleted { get { return _onDeleted; } set { if (_watcher != null && _watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the FileDeleted property. "); _onDeleted = value; } } public RenamedEventHandler FileRenamed { get { return _onRenamed; } set { if (_watcher != null && _watcher.EnableRaisingEvents) throw new InvalidOperationException("Stop the FileWatcher before changing the FileRenamed property. "); _onRenamed = value; } } [Obsolete("Unsafe. Use FileChanged / FileCreated / FileDeleted / FileRenamed instead. ")] public void SetEventHandler(FileSystemEventHandler onChanged = null, FileSystemEventHandler onCreated = null, FileSystemEventHandler onDeleted = null, RenamedEventHandler onRenamed = null) { _onCreated = onCreated; _onChanged = onChanged; _onDeleted = onDeleted; _onRenamed = onRenamed; } public bool Start() { if (_watcher != null) return true; _error = ""; try { Directory.CreateDirectory(_path); _watcher = new FileSystemWatcher { Path = _path, NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, Filter = _filter, InternalBufferSize = _bufferSize }; // Add event handlers. if (_onChanged != null) _watcher.Changed += ImmediateEventCall ? _onChanged : _onChangedDeferred; if (_onCreated != null) _watcher.Created += ImmediateEventCall ? _onCreated : _onCreatedDeferred; if (_onDeleted != null) _watcher.Deleted += ImmediateEventCall ? _onDeleted : _onDeletedDeferred; if (_onRenamed != null) _watcher.Renamed += ImmediateEventCall ? _onRenamed : _onRenamedDeferred; if (!ImmediateEventCall) { DisposeTask(); _task = TaskManager.Instance.CreateTask(FileEventThread); _cancellationTokenSource = new CancellationTokenSource(); _task.Start(); } // Begin watching. _watcher.EnableRaisingEvents = true; return true; } catch (Exception ex) { _error = $"Error creating watcher on {_path}\n\nException:\n{ex.Message}\n"; //MessageBox.Show(message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); return false; } } public void FileEventThread() { while (!_cancellationTokenSource.IsCancellationRequested) { FileEvent fileEvent; while (_fileEventQueue.TryDequeue(out fileEvent)) { switch (fileEvent.Type) { case FileEventType.Changed: _onChanged?.Invoke(this, (FileSystemEventArgs)fileEvent.Args); break; case FileEventType.Created: _onCreated?.Invoke(this, (FileSystemEventArgs)fileEvent.Args); break; case FileEventType.Deleted: _onDeleted?.Invoke(this, (FileSystemEventArgs)fileEvent.Args); break; case FileEventType.Renamed: _onRenamed?.Invoke(this, (RenamedEventArgs)fileEvent.Args); break; } } if (PollingInterval != 0) Thread.Sleep(PollingInterval); } } public void Stop() { _watcher.EnableRaisingEvents = false; // Remove event handlers. if (_onChanged != null) _watcher.Changed -= ImmediateEventCall ? _onChanged : _onChangedDeferred; if (_onCreated != null) _watcher.Created -= ImmediateEventCall ? _onCreated : _onCreatedDeferred; if (_onDeleted != null) _watcher.Deleted -= ImmediateEventCall ? _onDeleted : _onDeletedDeferred; if (_onRenamed != null) _watcher.Renamed -= ImmediateEventCall ? _onRenamed : _onRenamedDeferred; _cancellationTokenSource.Cancel(); } public enum FileEventType { Changed, Created, Deleted, Renamed } public struct FileEvent { public FileEventType Type; public EventArgs Args; } } }