diff --git a/server/src/Hubs/DigitalTubesHub.cs b/server/src/Hubs/DigitalTubesHub.cs index ab82713..1187e90 100644 --- a/server/src/Hubs/DigitalTubesHub.cs +++ b/server/src/Hubs/DigitalTubesHub.cs @@ -8,6 +8,8 @@ using DotNext; using Peripherals.SevenDigitalTubesClient; using System.Collections.Concurrent; +#pragma warning disable 1998 + namespace server.Hubs; [Hub] @@ -16,7 +18,7 @@ public interface IDigitalTubesHub Task StartScan(); Task StopScan(); Task SetFrequency(int frequency); - Task GetStatus(); + Task GetStatus(); } [Receiver] @@ -31,23 +33,27 @@ public class DigitalTubeTaskStatus public int Frequency { get; set; } = 100; public bool IsRunning { get; set; } = false; - public DigitalTubeTaskStatus(DigitalTubeInfo info) + public DigitalTubeTaskStatus(ScanTaskInfo info) { Frequency = info.Frequency; IsRunning = info.IsRunning; } } -public class DigitalTubeInfo +public class ScanTaskInfo { + public string BoardID { get; set; } public string ClientID { get; set; } + public Task? ScanTask { get; set; } public SevenDigitalTubesCtrl TubeClient { get; set; } - public CancellationTokenSource CTS { get; set; } = new CancellationTokenSource(); + public CancellationTokenSource CTS { get; set; } = new(); public int Frequency { get; set; } = 100; public bool IsRunning { get; set; } = false; - public DigitalTubeInfo(string clientID, SevenDigitalTubesCtrl client) + public ScanTaskInfo( + string boardID, string clientID, SevenDigitalTubesCtrl client) { + BoardID = boardID; ClientID = clientID; TubeClient = client; } @@ -58,11 +64,10 @@ public class DigitalTubeInfo public class DigitalTubesHub : Hub, IDigitalTubesHub { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private readonly IHubContext _hubContext; private readonly Database.UserManager _userManager = new(); - private ConcurrentDictionary _infoDict = new(); + private ConcurrentDictionary<(string, string), ScanTaskInfo> _scanTasks = new(); public DigitalTubesHub(IHubContext hubContext) { @@ -95,17 +100,18 @@ public class DigitalTubesHub : Hub, IDigitalTubesHub return boardRet.Value.Value; } - private Task ScanAllTubes(DigitalTubeInfo info) + private Task ScanAllTubes(ScanTaskInfo scanInfo) { + var token = scanInfo.CTS.Token; return Task.Run(async () => { var cntError = 0; - while (!info.CTS.IsCancellationRequested) + while (!token.IsCancellationRequested) { var beginTime = DateTime.Now; - var waitTime = TimeSpan.FromMilliseconds(1000 / info.Frequency); + var waitTime = TimeSpan.FromMilliseconds(1000 / scanInfo.Frequency); - var dataRet = await info.TubeClient.ScanAllTubes(); + var dataRet = await scanInfo.TubeClient.ScanAllTubes(); if (!dataRet.IsSuccessful) { logger.Error($"Failed to scan tubes: {dataRet.Error}"); @@ -113,126 +119,138 @@ public class DigitalTubesHub : Hub, IDigitalTubesHub if (cntError > 3) { logger.Error($"Too many errors, stopping scan"); - info.IsRunning = false; + break; } } - await _hubContext.Clients.Client(info.ClientID).OnReceive(dataRet.Value); + await _hubContext.Clients.Client(scanInfo.ClientID).OnReceive(dataRet.Value); var processTime = DateTime.Now - beginTime; if (processTime < waitTime) { - await Task.Delay(waitTime - processTime); + await Task.Delay(waitTime - processTime, token); } } - }, info.CTS.Token); + scanInfo.IsRunning = false; + }, token) + .ContinueWith((task) => + { + if (task.IsFaulted) + { + logger.Error( + $"Digital tubes scan operation failesj for board {task.Exception}"); + } + else if (task.IsCanceled) + { + logger.Info( + $"Digital tubes scan operation cancelled for board {scanInfo.BoardID}"); + } + else + { + logger.Info( + $"Digital tubes scan completed successfully for board {scanInfo.BoardID}"); + } + }); } - public Task StartScan() + public async Task StartScan() { try { var board = TryGetBoard().OrThrow(() => new Exception("Board not found")); - if (_infoDict.GetOrAdd( - board.ID.ToString(), - (_) => new DigitalTubeInfo( - Context.ConnectionId, - new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 2)) - ) is DigitalTubeInfo info) - { - if (!info.IsRunning) - { - info.IsRunning = true; - if (info.CTS.IsCancellationRequested) - { - info.CTS.Dispose(); - info.CTS = new CancellationTokenSource(); - } - _ = ScanAllTubes(info); - } - } + var key = (board.ID.ToString(), Context.ConnectionId); - return Task.FromResult(true); + if (_scanTasks.TryGetValue(key, out var existing) && existing.IsRunning) + return true; + + var cts = new CancellationTokenSource(); + var scanTaskInfo = new ScanTaskInfo( + board.ID.ToString(), Context.ConnectionId, + new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 0) + ); + scanTaskInfo.ScanTask = ScanAllTubes(scanTaskInfo); + + _scanTasks[key] = scanTaskInfo; + return true; } catch (Exception ex) { logger.Error(ex, "Failed to start scan"); - return Task.FromResult(false); + return false; } } - public Task StopScan() + public async Task StopScan() { try { var board = TryGetBoard().OrThrow(() => new Exception("Board not found")); - if (_infoDict.GetOrAdd( - board.ID.ToString(), - (_) => new DigitalTubeInfo( - Context.ConnectionId, - new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 2)) - ) is DigitalTubeInfo info) - { - info.IsRunning = false; - info.CTS.Cancel(); - } + var key = (board.ID.ToString(), Context.ConnectionId); - return Task.FromResult(true); + if (_scanTasks.TryRemove(key, out var scanInfo)) + { + scanInfo.IsRunning = false; + scanInfo.CTS.Cancel(); + if (scanInfo.ScanTask != null) + await scanInfo.ScanTask; + scanInfo.CTS.Dispose(); + } + return true; } catch (Exception ex) { logger.Error(ex, "Failed to stop scan"); - return Task.FromResult(false); + return false; } } - public Task SetFrequency(int frequency) + public async Task SetFrequency(int frequency) { try { - if (frequency < 1 || frequency > 1000) return Task.FromException( - new ArgumentException("Frequency must be between 1 and 1000")); + if (frequency < 1 || frequency > 1000) + return false; var board = TryGetBoard().OrThrow(() => new Exception("Board not found")); - if (_infoDict.GetOrAdd( - board.ID.ToString(), - (_) => new DigitalTubeInfo( - Context.ConnectionId, - new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 2)) - ) is DigitalTubeInfo info) - { - info.Frequency = frequency; - } + var key = (board.ID.ToString(), Context.ConnectionId); - return Task.FromResult(true); + if (_scanTasks.TryGetValue(key, out var scanInfo) && scanInfo.IsRunning) + { + scanInfo.Frequency = frequency; + return true; + } + else + { + logger.Warn($"SetFrequency called but no running scan for board {board.ID} and client {Context.ConnectionId}"); + return false; + } } catch (Exception ex) { logger.Error(ex, "Failed to set frequency"); - return Task.FromResult(false); + return false; } } - public Task GetStatus() + public async Task GetStatus() { try { var board = TryGetBoard().OrThrow(() => new Exception("Board not found")); - if (_infoDict.GetOrAdd( - board.ID.ToString(), - (_) => new DigitalTubeInfo( - Context.ConnectionId, - new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 2)) - ) is DigitalTubeInfo info) - { - return Task.FromResult(new DigitalTubeTaskStatus(info)); - } + var key = (board.ID.ToString(), Context.ConnectionId); - return Task.FromException(new ArgumentException("Wrong argument")); + if (_scanTasks.TryGetValue(key, out var scanInfo)) + { + return new DigitalTubeTaskStatus(scanInfo); + } + else + { + return null; + } } catch (Exception ex) { logger.Error(ex, "Failed to get status"); - return Task.FromException(new Exception("Failed to get status")); + throw new Exception("Failed to get status", ex); } } diff --git a/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts b/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts index 87d4745..c4966dc 100644 --- a/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts +++ b/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts @@ -20,7 +20,7 @@ export type IDigitalTubesHub = { */ setFrequency(frequency: number): Promise; /** - * @returns Transpiled from System.Threading.Tasks.Task + * @returns Transpiled from System.Threading.Tasks.Task */ getStatus(): Promise; }