using Barcode.Host.Server.Hubs; using Barcode.Host.Server.Models; using Barcode.Host.Shared.DataModels; using Barcode.Host.Shared.KeyboardMouse; using Barcode.Host.Shared.Models; using Barcode.Host.Shared.Models.Stateless; using Microsoft.AspNetCore.SignalR; using System.Globalization; namespace Barcode.Host.Server.HostedService; public class TimedHostedService : IHostedService, IAggregateInputReader, IDisposable { public event InputReader.RaiseKeyPress? OnKeyPress; private readonly int _ExecutionCount; protected readonly Calendar _Calendar; private readonly AppSettings _AppSettings; private readonly IFileService _FileService; private readonly IPostService _PostService; private readonly ISerialService _SerialService; private readonly ILastScanService _LastScanService; private readonly List _Notifications; private readonly ILogger _Logger; private readonly IHttpClientFactory _HttpClientFactory; private readonly ILinuxGroupManager _LinuxGroupManager; private readonly Dictionary _Readers; private readonly IHubContext _HubContext; private readonly Dictionary _CharToEventCodes; private readonly List<(string MethodName, Timer Timer)> _Timers; public TimedHostedService(ILogger logger, AppSettings appSettings, ILinuxGroupManager linuxGroupManager, ILastScanService lastScanService, ISerialService serialService, IHttpClientFactory httpClientFactory, IHubContext hubContext, IFileService fileService, IPostService postService) { _Timers = new(); _Logger = logger; _Readers = new(); _ExecutionCount = 0; _Notifications = new(); _HubContext = hubContext; _CharToEventCodes = new(); _AppSettings = appSettings; _FileService = fileService; _PostService = postService; _SerialService = serialService; _LastScanService = lastScanService; _HttpClientFactory = httpClientFactory; _LinuxGroupManager = linuxGroupManager; _Calendar = new CultureInfo("en-US").Calendar; Timer writeTimer = new(Write, null, Timeout.Infinite, Timeout.Infinite); Timer postToTimer = new(PostTo, null, Timeout.Infinite, Timeout.Infinite); Timer shareToTimer = new(ShareTo, null, Timeout.Infinite, Timeout.Infinite); Timer scanForNewInputsTimer = new(ScanForNewInputs, null, Timeout.Infinite, Timeout.Infinite); if (!string.IsNullOrEmpty(_AppSettings.SerialPortName)) _Timers.Add((nameof(Write), writeTimer)); if (!string.IsNullOrEmpty(_AppSettings.PostTo)) _Timers.Add((nameof(PostTo), postToTimer)); if (!string.IsNullOrEmpty(_AppSettings.FileShare)) _Timers.Add((nameof(ShareTo), shareToTimer)); #if Linux _Timers.Add((nameof(ScanForNewInputs), scanForNewInputsTimer)); #endif } public Task StartAsync(CancellationToken stoppingToken) { _Logger.LogInformation("Timed Hosted Service: {BuildSourceVersion}:{ProcessId} running.", _AppSettings.BuildSourceVersion, Environment.ProcessId); if (!string.IsNullOrEmpty(_AppSettings.SerialPortName)) _SerialService.Open(); #if Linux 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); } #endif 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) { _Logger.LogInformation("Timed Hosted Service: {BuildSourceVersion}:{ProcessId} is stopping.", _AppSettings.BuildSourceVersion, Environment.ProcessId); 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); int count = _LastScanService.GetCount(); if (count > _AppSettings.NotifyMinimum) { Result result = _LastScanService.GetScan(); if (!string.IsNullOrEmpty(result.Results)) { Notification notification = new(e, result.Results, _AppSettings.ToolClass, null); _Notifications.Add(notification); _ = _HubContext.Clients.All.SendAsync(nameof(NotificationHub.NotifyAll), notification); } } } } 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() { if (!string.IsNullOrEmpty(_AppSettings.SerialPortName)) { 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)}"); } } private void PostTo() { if (!string.IsNullOrEmpty(_AppSettings.PostTo)) { Task? httpResponseMessage; lock (_Notifications) { if (!_Notifications.Any()) httpResponseMessage = null; else { HttpClient httpClient = _HttpClientFactory.CreateClient(nameof(TimedHostedService)); httpResponseMessage = _PostService.PostAsync(_AppSettings.PostTo, httpClient, _Notifications.Last()); _Notifications.Clear(); } } if (httpResponseMessage is not null) { httpResponseMessage.Wait(); if (httpResponseMessage.Result.StatusCode != System.Net.HttpStatusCode.OK) throw new Exception(httpResponseMessage.Result.StatusCode.ToString()); Task content = httpResponseMessage.Result.Content.ReadAsStringAsync(); content.Wait(); Notification notification = new(null, null, _AppSettings.ToolClass, content.Result); _ = _HubContext.Clients.All.SendAsync(nameof(NotificationHub.NotifyAll), notification); } } } private void PostTo(object? sender) { try { PostTo(); } catch (Exception ex) { _Logger.LogError(ex, nameof(PostTo)); } try { Timer? timer = GetTimer(nameof(PostTo)); if (timer is not null) { TimeSpan timeSpan = new(DateTime.Now.AddMilliseconds(_AppSettings.PostToEvery).Ticks - DateTime.Now.Ticks); _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); } } catch (Exception ex) { _Logger.LogError(ex, $"{nameof(PostTo)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } } private void ShareTo() { if (!string.IsNullOrEmpty(_AppSettings.FileShare)) { lock (_Notifications) { if (_Notifications.Any()) { _FileService.Write(_AppSettings.EquipmentName, _AppSettings.FileShare, _Calendar, _Notifications.Last()); _Notifications.Clear(); } } } } private void ShareTo(object? sender) { try { ShareTo(); } catch (Exception ex) { _Logger.LogError(ex, nameof(ShareTo)); } try { Timer? timer = GetTimer(nameof(ShareTo)); if (timer is not null) { TimeSpan timeSpan = new(DateTime.Now.AddMilliseconds(_AppSettings.ShareToEvery).Ticks - DateTime.Now.Ticks); _ = timer.Change((int)timeSpan.TotalMilliseconds, Timeout.Infinite); } } catch (Exception ex) { _Logger.LogError(ex, $"{nameof(ShareTo)}-{nameof(Timer)}.{nameof(Timer.Change)}"); } } }