//
// 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)
where TData : notnull
{
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);
}