using System.Net.Http; namespace OpenNest.Data.MaterialLedger; /// /// The ledger item codes other open server nests are coded with, or why they could not be /// read. ML's free counts drop only once a hold exists, so these codes are what another /// Auto Nest run must leave out until then. Reading them never blocks: a failure is a result. /// public sealed record LedgerCodesInUse { /// The nest server gets this long to answer, so a stock load or save never waits on it. public static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(5); private LedgerCodesInUse(IReadOnlyList uses, string? unavailable) { Uses = uses; Unavailable = unavailable; Codes = uses.Select(use => use.ItemCode).ToHashSet(StringComparer.Ordinal); } /// One entry per code and nest; empty when . public IReadOnlyList Uses { get; } /// The distinct codes of . public IReadOnlySet Codes { get; } /// Why the codes could not be read; null when they were. public string? Unavailable { get; } public bool IsKnown => Unavailable == null; public static LedgerCodesInUse Known(IEnumerable uses) => new((uses ?? throw new ArgumentNullException(nameof(uses))).Where(use => use != null).ToList(), null); public static LedgerCodesInUse Unknown(string reason) => new(Array.Empty(), string.IsNullOrWhiteSpace(reason) ? "not available" : reason); /// The names of the other nests using , in name order. public IReadOnlyList NestsUsing(string code) => Uses.Where(use => string.Equals(use.ItemCode, code, StringComparison.Ordinal)) .Select(use => use.NestName) .Distinct(StringComparer.Ordinal) .OrderBy(name => name, StringComparer.CurrentCultureIgnoreCase) .ToList(); /// /// Reads the codes from , leaving out the nest /// (the one being edited). A null repository is File /// mode. Only the caller's own cancellation throws. /// public static async Task ReadAsync(INestRepository? repository, Guid excludeNestId, CancellationToken cancellationToken, TimeSpan? timeout = null) { if (repository == null) return Unknown("nests are saved as files"); using var timeoutSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeoutSource.CancelAfter(timeout ?? RequestTimeout); try { return Known(await repository.GetStockCodesInUseAsync(excludeNestId, timeoutSource.Token)); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { return Unknown("the nest server did not answer"); } catch (NotSupportedException) { return Unknown("the nest server needs an update"); } catch (Exception ex) when (ex is HttpRequestException or IOException or System.Text.Json.JsonException or ArgumentException or InvalidOperationException) { return Unknown("the nest server could not be reached"); } } }