using Barcode.Host.Server.Models; using Barcode.Host.Shared.DataModels; using Barcode.Host.Shared.KeyboardMouse; using Barcode.Host.Shared.Models.Stateless; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog.Context; namespace Barcode.Host.Server.HostedService; public class TimedHostedService : IHostedService, IAggregateInputReader, IDisposable { public event InputReader.RaiseKeyPress? OnKeyPress; private readonly int _ExecutionCount; private readonly AppSettings _AppSettings; private readonly ISerialService _SerialService; private readonly ILastScanService _LastScanService; private readonly ILogger _Logger; private readonly ILinuxGroupManager _LinuxGroupManager; private readonly Dictionary _Readers; private readonly Dictionary _CharToEventCodes; private readonly List<(string MethodName, Timer Timer)> _Timers; public TimedHostedService(ILogger logger, AppSettings appSettings, ILinuxGroupManager linuxGroupManager, ILastScanService lastScanService, ISerialService serialService) { _Readers = new(); _Logger = logger; _ExecutionCount = 0; _CharToEventCodes = new(); _AppSettings = appSettings; _SerialService = serialService; _LastScanService = lastScanService; _LinuxGroupManager = linuxGroupManager; Timer writeTimer = new(Write, null, Timeout.Infinite, Timeout.Infinite); Timer scanForNewInputsTimer = new(ScanForNewInputs, null, Timeout.Infinite, Timeout.Infinite); _Timers = new List<(string, Timer)>() { (nameof(Write), writeTimer), (nameof(ScanForNewInputs), scanForNewInputsTimer) }; } public Task StartAsync(CancellationToken stoppingToken) { string? methodName = IMethodName.GetActualAsyncMethodName(); using (LogContext.PushProperty("MethodName", methodName)) { _Logger.LogInformation($"Timed Hosted Service: {_AppSettings.GitCommitSeven}:{Environment.ProcessId} running."); _SerialService.Open(); if (!_LinuxGroupManager.IsInInputGroup().WaitAsync(stoppingToken).Result) { if (string.IsNullOrEmpty(_AppSettings.RootPassword)) throw new Exception($"Please check appsettings file(s) for <{nameof(_AppSettings.RootPassword)}>!"); _ = _LinuxGroupManager.AddUserToInputGroup(_AppSettings.RootPassword); _ = _LinuxGroupManager.RebootSystem(_AppSettings.RootPassword); } List<(EventCode, char)> collection = _LastScanService.IncludeEventCodes(); foreach ((EventCode eventCode, char @char) in collection) _CharToEventCodes.Add(eventCode, @char); int dueTime = 0; foreach ((string _, Timer timer) in _Timers) { dueTime += 300; _ = timer.Change(dueTime, Timeout.Infinite); } } return Task.CompletedTask; } public Task StopAsync(CancellationToken stoppingToken) { string? methodName = IMethodName.GetActualAsyncMethodName(); using (LogContext.PushProperty("MethodName", methodName)) { _Logger.LogInformation($"Timed Hosted Service: {_AppSettings.GitCommitSeven}:{Environment.ProcessId} is stopping."); for (short i = 0; i < short.MaxValue; i++) { Thread.Sleep(500); if (_ExecutionCount == 0) break; } } return Task.CompletedTask; } public void Dispose() { foreach ((string _, Timer timer) in _Timers) timer.Dispose(); foreach (InputReader inputReader in _Readers.Values) { inputReader.OnKeyPress -= ReaderOnOnKeyPress; inputReader.Dispose(); } _Readers.Clear(); _SerialService.Close(); GC.SuppressFinalize(this); } private void ReaderOnOnKeyPress(KeyPressEvent e) { OnKeyPress?.Invoke(e); if (e.TimeSpan.TotalMilliseconds > _AppSettings.ClearLastScanServiceAfter) _LastScanService.Clear(); if (e.KeyState == KeyState.KeyUp && _CharToEventCodes.TryGetValue(e.EventCode, out char @char)) _LastScanService.Add(e.EventCode, @char); } private Timer? GetTimer(string methodName) { (string MethodName, Timer Timer)[] results = _Timers.Where(l => l.MethodName == methodName).ToArray(); return !results.Any() ? null : results.First().Timer; } private void ScanForNewInputs() { string fileName; IEnumerable? devices = null; string[] files = Directory.GetFiles("/dev/input/", "event*"); foreach (string file in files) { if (_Readers is null || _Readers.ContainsKey(file)) continue; devices ??= DeviceReader.Get(_AppSettings.LinuxDevicePath); fileName = Path.GetFileName(file); InputReader reader = new(file, _Logger); if (devices.Any(l => !string.IsNullOrEmpty(l.Name) && l.Name.EndsWith(_AppSettings.DeviceNameEndsWith) && l.Handlers.Any(m => m == fileName))) reader.OnKeyPress += ReaderOnOnKeyPress; _Readers?.Add(file, reader); } IEnumerable? deadReaders = _Readers?.Values.Where(r => r.Faulted); if (deadReaders is not null) { foreach (InputReader? inputReader in deadReaders) { _ = _Readers?.Remove(inputReader.Path); inputReader.OnKeyPress -= ReaderOnOnKeyPress; inputReader.Dispose(); } } } private void ScanForNewInputs(object? sender) { try { ScanForNewInputs(); } catch (Exception ex) { _Logger.LogError(ex, nameof(ScanForNewInputs)); } try { Timer? timer = GetTimer(nameof(ScanForNewInputs)); if (timer is not null) { TimeSpan timeSpan = new(DateTime.Now.AddSeconds(30).Ticks - DateTime.Now.Ticks); _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); } } catch (Exception ex) { _Logger.LogError(ex, $"{nameof(ScanForNewInputs)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } } private void Write() { int count = _LastScanService.GetCount(); if (count > 0) { Result result = _LastScanService.GetScan(); if (!string.IsNullOrEmpty(result.Results)) _SerialService.SerialPortWrite(count, result.Results); } } private void Write(object? sender) { try { Write(); } catch (Exception ex) { _Logger.LogError(ex, nameof(Write)); } try { Timer? timer = GetTimer(nameof(Write)); if (timer is not null) { TimeSpan timeSpan = new(DateTime.Now.AddMilliseconds(_AppSettings.WriteToSerialEvery).Ticks - DateTime.Now.Ticks); _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); } } catch (Exception ex) { _Logger.LogError(ex, $"{nameof(Write)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } } }