294 lines
10 KiB
C#
294 lines
10 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace JD.Core
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<FileEvent> _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<FileEvent>();
|
|
|
|
_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;
|
|
}
|
|
}
|
|
}
|