using System.Net.Http; using System.Text.Json; namespace OpenNest.Data; /// /// Duplicate-name checks for a nest's first upload and for renames. Names may already be /// on posted CNC programs and labels, so a nest is never renamed silently: callers offer /// a replacement, or warn, only when another active nest has the exact same name. /// public static class NestNameConflicts { /// /// A freshly allocated name for when an active server nest /// already uses ; null when the name is free, or when the /// check or the allocation fails, so the caller keeps the name it has. /// public static async Task FindReplacementAsync( INestRepository repository, string name, int year, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(repository); if (string.IsNullOrWhiteSpace(name)) return null; try { if (!await IsNameTakenAsync(repository, name, cancellationToken: cancellationToken)) return null; var number = await repository.AllocateNestNumberAsync(year, cancellationToken); return new NestNameAllocation { Year = year, Number = number, Name = NestNames.Format(year, number) }; } catch (Exception ex) when (IsCheckFailure(ex)) { return null; } } /// /// , or null when the server could not answer /// (offline, too slow, or an older server), so the caller can go ahead without warning. /// public static async Task TryIsNameTakenAsync( INestRepository repository, string name, Guid excludeId = default, CancellationToken cancellationToken = default) { try { return await IsNameTakenAsync(repository, name, excludeId, cancellationToken); } catch (Exception ex) when (IsCheckFailure(ex)) { return null; } } /// /// True when an active (not deleted) server nest other than /// is named exactly . /// public static async Task IsNameTakenAsync( INestRepository repository, string name, Guid excludeId = default, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(repository); // The search is a case-insensitive substring match; page through it for the exact name. for (var offset = 0; ;) { var page = await repository.QueryAsync(new NestQuery { Search = name, Sort = NestSortField.Name, Descending = false, Offset = offset, Limit = NestQuery.MaxLimit, }, cancellationToken); if (page.Items.Any(r => !r.Deleted && r.Id != excludeId && string.Equals(r.Name, name, StringComparison.Ordinal))) return true; offset += page.Items.Count; if (page.Items.Count == 0 || offset >= page.Total) return false; } } private static bool IsCheckFailure(Exception ex) => ex is HttpRequestException or IOException or OperationCanceledException or NotSupportedException or ArgumentException or JsonException or InvalidOperationException; }