mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 09:21:05 -04:00
File > New restarted its counter every day while names only carry the year, so the first nest of each day was always N26-223, and each PC kept its own counter. In Database mode the nest server now hands out the year's next number (POST /api/nest-names/next, nest_sequences table, schema v6), staying above every stored generated name so offline or pre-existing names are never reissued. The per-PC counter remains the fallback when the server is unreachable or older, resets per year, and is raised past each server allocation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.2 KiB
C#
70 lines
2.2 KiB
C#
namespace OpenNest.Data;
|
|
|
|
/// <summary>
|
|
/// Generated nest names: "N" + two-digit year + "-" + the year's sequence number in an
|
|
/// unambiguous base-30 alphabet, padded to three digits with its zero digit ('2').
|
|
/// The sequence runs for the whole year; three digits cover 27,000 nests before a fourth appears.
|
|
/// </summary>
|
|
public static class NestNames
|
|
{
|
|
private const string Base30Digits = "2345679ACDEFGHJKLMNPQRSTUVWXYZ";
|
|
|
|
/// <summary>Earliest and latest years a sequence may be allocated for.</summary>
|
|
public const int MinYear = 2000, MaxYear = 2099;
|
|
|
|
public static string Format(int year, int number)
|
|
{
|
|
ArgumentOutOfRangeException.ThrowIfNegative(number);
|
|
var seq = ToBase30(number).PadLeft(3, Base30Digits[0]);
|
|
return $"N{year % 100:D2}-{seq}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// The sequence number of a generated name for <paramref name="year"/>, or false for
|
|
/// any other name (renamed nests, other years, non-canonical padding).
|
|
/// </summary>
|
|
public static bool TryParse(string? name, int year, out int number)
|
|
{
|
|
number = 0;
|
|
var prefix = $"N{year % 100:D2}-";
|
|
if (name is null || name.Length < prefix.Length + 3 || !name.StartsWith(prefix, StringComparison.Ordinal))
|
|
return false;
|
|
|
|
long value = 0;
|
|
foreach (var c in name.AsSpan(prefix.Length))
|
|
{
|
|
var digit = Base30Digits.IndexOf(c);
|
|
if (digit < 0)
|
|
return false;
|
|
value = value * Base30Digits.Length + digit;
|
|
if (value > int.MaxValue)
|
|
return false;
|
|
}
|
|
|
|
number = (int)value;
|
|
return Format(year, number) == name;
|
|
}
|
|
|
|
private static string ToBase30(int value)
|
|
{
|
|
if (value == 0)
|
|
return Base30Digits[0].ToString();
|
|
|
|
var result = "";
|
|
while (value > 0)
|
|
{
|
|
result = Base30Digits[value % Base30Digits.Length] + result;
|
|
value /= Base30Digits.Length;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>Wire shape of the nest server's next-name allocation.</summary>
|
|
public sealed class NestNameAllocation
|
|
{
|
|
public int Year { get; set; }
|
|
public int Number { get; set; }
|
|
public string Name { get; set; } = "";
|
|
}
|