diff --git a/src/pdns-dhcp/Kea/KeaDhcpLeaseFileReader.cs b/src/pdns-dhcp/Kea/KeaDhcpLeaseFileReader.cs new file mode 100644 index 0000000..a253949 --- /dev/null +++ b/src/pdns-dhcp/Kea/KeaDhcpLeaseFileReader.cs @@ -0,0 +1,6 @@ +namespace pdns_dhcp.Kea; + +public class KeaDhcpLeaseFileReader +{ + +} diff --git a/src/pdns-dhcp/Kea/KeaLeaseWatcher.cs b/src/pdns-dhcp/Kea/KeaLeaseWatcher.cs index 7107a46..2304d4d 100644 --- a/src/pdns-dhcp/Kea/KeaLeaseWatcher.cs +++ b/src/pdns-dhcp/Kea/KeaLeaseWatcher.cs @@ -1,12 +1,18 @@ +using System.Threading.Channels; + using Microsoft.Extensions.Hosting; using pdns_dhcp.Options; namespace pdns_dhcp.Kea; -public abstract class KeaDhcpLeaseWatcher : BackgroundService +public abstract class KeaDhcpLeaseWatcher : IHostedService { private readonly FileSystemWatcher _fsw; + private readonly string _leaseFile; + private Channel? _eventChannel; + private Task? _executeTask; + private CancellationTokenSource? _stoppingCts; protected KeaDhcpServerOptions Options { get; } @@ -26,31 +32,107 @@ public abstract class KeaDhcpLeaseWatcher : BackgroundService throw new ArgumentException($"{nameof(options.Leases)} must point to a file in an existing path.", nameof(options)); } - _fsw = new(leaseDirectory, leaseFile.ToString()); + _leaseFile = leaseFile.ToString(); + _fsw = new(leaseDirectory, _leaseFile); _fsw.Changed += OnLeaseChanged; - _fsw.Created += OnLeaseChanged; - _fsw.Deleted += OnLeaseChanged; - _fsw.Renamed += OnLeaseRenamed; _fsw.Error += OnLeaseError; } - protected override Task ExecuteAsync(CancellationToken stoppingToken) + public Task StartAsync(CancellationToken cancellationToken) { - return Task.CompletedTask; + _stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _executeTask = Reading(_stoppingCts.Token); + return _executeTask is { IsCompleted: true } completed ? completed : Task.CompletedTask; + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + if (_executeTask is null) + { + return; + } + + _fsw.EnableRaisingEvents = false; + try + { + _stoppingCts!.Cancel(); + } + finally + { + TaskCompletionSource taskCompletionSource = new(); + using (cancellationToken.Register(s => ((TaskCompletionSource)s!).SetCanceled(), taskCompletionSource)) + { + await Task.WhenAny(_executeTask, taskCompletionSource.Task).ConfigureAwait(continueOnCapturedContext: false); + } + } + } + + private async Task Reading(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + using CancellationTokenSource loopCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); + _eventChannel = Channel.CreateUnbounded(); + _fsw.EnableRaisingEvents = true; + ValueTask reader = default; + try + { + await foreach (var @event in _eventChannel.Reader.ReadAllAsync(loopCts.Token)) + { + switch (@event) + { + case { ChangeType: WatcherChangeTypes.Created }: + case RenamedEventArgs { ChangeType: WatcherChangeTypes.Renamed } renamed when renamed.Name == _leaseFile: + reader = FileReader(loopCts.Token); + break; + + case { ChangeType: WatcherChangeTypes.Deleted }: + case RenamedEventArgs { ChangeType: WatcherChangeTypes.Renamed } renamed when renamed.OldName == _leaseFile: + loopCts.Cancel(); + break; + + case { ChangeType: WatcherChangeTypes.Changed }: + break; + } + } + } + catch { } + finally + { + _eventChannel.Writer.TryComplete(); + try + { + await reader.ConfigureAwait(continueOnCapturedContext: false); + } + catch { } + } + } + + ValueTask FileReader(CancellationToken stoppingToken) + { + return ValueTask.CompletedTask; + } } private void OnLeaseChanged(object sender, FileSystemEventArgs e) { - throw new NotImplementedException(); + var writer = _eventChannel?.Writer; + if (writer?.TryWrite(e) != false) + { + return; + } + + var write = writer.WriteAsync(e); + if (write.IsCompleted) + { + return; + } + + write.GetAwaiter().GetResult(); } private void OnLeaseError(object sender, ErrorEventArgs e) { - throw new NotImplementedException(); - } - - private void OnLeaseRenamed(object sender, RenamedEventArgs e) - { - throw new NotImplementedException(); + _eventChannel?.Writer.Complete(e.GetException()); } }