// // ServerManager.cs // // Copyright (c) František Boháček. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NosSmooth.Comms.Data; using NosSmooth.Comms.Data.Messages; using NosSmooth.Core.Extensions; using Remora.Results; namespace NosSmooth.Comms.Core; /// /// Manages a server, awaits connections, handles messages. /// public class ServerManager { private readonly IServer _server; private readonly MessageHandler _messageHandler; private readonly IOptions _options; private readonly ILogger _logger; private readonly ILogger _handlerLogger; private readonly List _connectionHandlers; private Task? _task; private CancellationTokenSource? _ctSource; /// /// Initializes a new instance of the class. /// /// The server to manage. /// The message handler. /// The options. /// The logger. /// The logger for message handler. public ServerManager ( IServer server, MessageHandler messageHandler, IOptions options, ILogger logger, ILogger handlerLogger ) { _server = server; _connectionHandlers = new List(); _messageHandler = messageHandler; _options = options; _logger = logger; _handlerLogger = handlerLogger; } /// /// Run the manager and await the task. /// /// The token used for stopping the handler and disconnecting the connection. /// A result that may or may not have succeeded. public Task RunManagerAsync(CancellationToken stopToken) { StartManager(stopToken); return _task!; } /// /// Broadcast the given message to all clients. /// /// The message to broadcast. /// The cancellation token used for cancelling the operation. /// The type of the message. /// A result that may or may not have succeeded. public async Task BroadcastAsync(TMessage message, CancellationToken ct = default) { var errors = new List(); foreach (var handler in _connectionHandlers) { var result = await handler.SendMessageAsync(message, ct); if (!result.IsSuccess) { errors.Add(Result.FromError(result)); } } return errors.Count switch { 0 => Result.FromSuccess(), 1 => (Result)errors[0], _ => new AggregateError(errors) }; } /// /// Run the handler without awaiting the task. /// /// The token used for stopping the handler and disconnecting the connection. public void StartManager(CancellationToken stopToken = default) { if (_task is not null) { return; } _ctSource = CancellationTokenSource.CreateLinkedTokenSource(stopToken); _task = ManagerTask(); } /// /// Request stop the server. /// public void RequestStop() { _ctSource?.Cancel(); } private async Task ManagerTask() { if (_ctSource is null) { throw new InvalidOperationException("The ct source is not initialized."); } await _server.ListenAsync(_ctSource!.Token); while (!_ctSource.IsCancellationRequested) { var connectionResult = await _server.WaitForConnectionAsync(_ctSource.Token); if (!connectionResult.IsDefined(out var connection)) { _logger.LogResultError(connectionResult); continue; } var handler = new ConnectionHandler(null, connection, _messageHandler, _options, _handlerLogger); _connectionHandlers.Add(handler); handler.StartHandler(_ctSource.Token); } List errors = new List(); foreach (var handler in _connectionHandlers) { var handlerResult = await handler.RunHandlerAsync(_ctSource.Token); if (!handlerResult.IsSuccess) { errors.Add(handlerResult); } } _server.Close(); return errors.Count switch { 0 => Result.FromSuccess(), 1 => (Result)errors[0], _ => new AggregateError(errors) }; } }