barcode-host/Server/HostedService/TimedHostedService.cs
2023-10-19 22:28:40 -07:00

301 lines
12 KiB
C#

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<Notification> _Notifications;
private readonly ILogger<TimedHostedService> _Logger;
private readonly IHttpClientFactory _HttpClientFactory;
private readonly ILinuxGroupManager _LinuxGroupManager;
private readonly Dictionary<string, InputReader> _Readers;
private readonly IHubContext<NotificationHub> _HubContext;
private readonly Dictionary<EventCode, char> _CharToEventCodes;
private readonly List<(string MethodName, Timer Timer)> _Timers;
public TimedHostedService(ILogger<TimedHostedService> logger, AppSettings appSettings, ILinuxGroupManager linuxGroupManager, ILastScanService lastScanService, ISerialService serialService, IHttpClientFactory httpClientFactory, IHubContext<NotificationHub> 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<string> 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<LinuxDevice>? 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<InputReader>? 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<string> 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>? 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<string> 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)}"); }
}
}