Skip to content

Instantly share code, notes, and snippets.

@DavidPx
Created August 24, 2022 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DavidPx/1899605a496df84da4637565fd89c137 to your computer and use it in GitHub Desktop.
Save DavidPx/1899605a496df84da4637565fd89c137 to your computer and use it in GitHub Desktop.
Solving FileSystemWatcher Multiple Changed Events with System.Reactive
public class FileEvent
{
public FileEvent(string fullPath, WatcherChangeTypes changeTypes)
{
FullPath = fullPath;
ChangeType = changeTypes;
}
public string FullPath { get; set; }
public WatcherChangeTypes ChangeType { get; set; }
}
using FileWatcher;
await Host.CreateDefaultBuilder(args)
.ConfigureLogging((_, logging) => logging.ClearProviders().AddSimpleConsole(opt => {
opt.IncludeScopes = false;
opt.SingleLine = true;
opt.TimestampFormat = "[hh:mm:ss.fff] ";
}))
.ConfigureServices((context, services) => {
var configRoot = context.Configuration;
services.Configure<AppSettings>(configRoot.GetSection(nameof(AppSettings))); // put AppSettings into the DI container
services.AddHostedService<ReactiveWorker>();
})
.RunConsoleAsync();
public class ReactiveWorker : BackgroundService
{
private readonly ILogger<ReactiveWorker> logger;
private readonly AppSettings appSettings;
public ReactiveWorker(ILogger<ReactiveWorker> logger, IOptions<AppSettings> appSettingsAccessor)
{
this.logger = logger;
appSettings = appSettingsAccessor.Value;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (string.IsNullOrWhiteSpace(appSettings.WatchDirectory))
{
logger.LogError("Configured watch directory is not valid");
return;
}
logger.LogInformation("Watching {watchDirectory} for changes", appSettings.WatchDirectory);
using var watcher = new FileSystemWatcher(appSettings.WatchDirectory, "*.txt");
watcher.EnableRaisingEvents = true;
// We only care about renames and writes. File creations are notifications about 0-byte files.
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
watcher.IncludeSubdirectories = false;
var renameEvents = Observable.FromEventPattern<RenamedEventArgs>(watcher, "Renamed").Select(e => new FileEvent(e.EventArgs.FullPath, e.EventArgs.ChangeType));
var changeEvents = Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed").Select(e => new FileEvent(e.EventArgs.FullPath, e.EventArgs.ChangeType));
changeEvents
.GroupBy(x => x.FullPath) // splits the stream into sub-streams per file
.Subscribe(
group =>
// we are now dealing with a stream of events for just one file
group
.Throttle(TimeSpan.FromSeconds(8)) // on the network large file transfers will file change events ~8s apart!
.Subscribe(HandleFile)
);
// Renames are instant and don't trigger mulitple events
renameEvents.Subscribe(HandleFile);
await Task.Delay(TimeSpan.FromDays(1), stoppingToken);
}
private void HandleFile(FileEvent e)
{
logger.LogInformation("** {file} from {type}", e.FullPath, e.ChangeType);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment