// // Contractor.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 System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NosSmooth.Core.Contracts.Responders; using Remora.Results; namespace NosSmooth.Core.Contracts; /// /// A class holding s, /// updates the contracts. /// public class Contractor : IEnumerable { /// /// Maximum time a contract may be registered for. /// public static readonly TimeSpan Timeout = new TimeSpan(0, 5, 0); private readonly List _contracts; private readonly SemaphoreSlim _semaphore; /// /// Initializes a new instance of the class. /// public Contractor() { _semaphore = new SemaphoreSlim(1, 1); _contracts = new List(); } /// /// Register the given contract to receive feedback for it. /// /// The contract to register. public void Register(IContract contract) { try { _semaphore.Wait(); _contracts.Add(new ContractInfo(contract, DateTime.Now)); } finally { _semaphore.Release(); } } /// /// Unregister the given contract, no more info will be received. /// /// The contract. public void Unregister(IContract contract) { try { _semaphore.Wait(); _contracts.RemoveAll(ci => ci.contract == contract); } finally { _semaphore.Release(); } } /// /// Update all of the contracts with the given data. /// /// /// Called from /// or similar. Used for updating the state. /// The contracts look for actions that trigger updates /// and in case it matches the , /// the state is switched. /// /// The data that were received. /// The cancellation token used for cancelling the operation. /// The type of the data. /// The result that may or may not have succeeded. public async Task Update(TData data, CancellationToken ct = default) { var errors = new List(); var toRemove = new List(); try { await _semaphore.WaitAsync(ct); foreach (var info in _contracts) { if (DateTime.Now - info.addedAt > Timeout) { errors.Add ( (Result)new GenericError ( $"A contract {info.contract} has been registered for too long and was unregistered automatically." ) ); continue; } var result = await info.contract.Update(data); if (!result.IsDefined(out var response)) { errors.Add(result); } if (response == ContractUpdateResponse.InterestedAndUnregister) { toRemove.Add(info); } } foreach (var contract in toRemove) { _contracts.Remove(contract); } return errors.Count switch { 0 => Result.FromSuccess(), 1 => (Result)errors[0], _ => new AggregateError(errors) }; } finally { _semaphore.Release(); } } /// public IEnumerator GetEnumerator() => _contracts.Select(x => x.contract).GetEnumerator(); /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private record struct ContractInfo(IContract contract, DateTime addedAt); }