108 lines
2.9 KiB
C#
108 lines
2.9 KiB
C#
using System.Text.Json;
|
|
|
|
namespace HSchool.Server.Game;
|
|
|
|
/// <summary>One registered player name, stored exactly as typed on first login.</summary>
|
|
internal sealed record UserRecord(string Name);
|
|
|
|
/// <summary>Persistent user list beside school saves.</summary>
|
|
internal sealed class UserStore
|
|
{
|
|
private const string UsersFileName = "users.json";
|
|
|
|
private static readonly JsonSerializerOptions Json = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
PropertyNameCaseInsensitive = true,
|
|
WriteIndented = true,
|
|
};
|
|
|
|
private readonly object _gate = new();
|
|
private readonly ILogger<UserStore> _logger;
|
|
private readonly string _path;
|
|
private List<UserRecord> _users = [];
|
|
|
|
public UserStore(SchoolStore schools, ILogger<UserStore> logger)
|
|
{
|
|
_logger = logger;
|
|
_path = Path.Combine(schools.DirectoryPath, UsersFileName);
|
|
Load();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the canonical spelling for <paramref name="normalized"/> or registers
|
|
/// <paramref name="displayName"/> on first use.
|
|
/// </summary>
|
|
public string ResolveOrRegister(string normalized, string displayName)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
var existing = FindCanonicalLocked(normalized);
|
|
if (existing is not null)
|
|
{
|
|
return existing;
|
|
}
|
|
|
|
_users.Add(new UserRecord(displayName));
|
|
SaveLocked();
|
|
return displayName;
|
|
}
|
|
}
|
|
|
|
public bool TryFindCanonical(string normalized, out string canonical)
|
|
{
|
|
lock (_gate)
|
|
{
|
|
canonical = FindCanonicalLocked(normalized) ?? "";
|
|
return canonical.Length > 0;
|
|
}
|
|
}
|
|
|
|
private string? FindCanonicalLocked(string normalized)
|
|
{
|
|
foreach (var user in _users)
|
|
{
|
|
if (string.Equals(user.Name, normalized, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return user.Name;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void Load()
|
|
{
|
|
if (!File.Exists(_path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var document = JsonSerializer.Deserialize<UserDocument>(File.ReadAllText(_path), Json);
|
|
_users = document?.Users?.ToList() ?? [];
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Could not read {Path}; starting with an empty user list.", _path);
|
|
_users = [];
|
|
}
|
|
}
|
|
|
|
private void SaveLocked()
|
|
{
|
|
WriteAtomic(_path, new UserDocument(_users));
|
|
}
|
|
|
|
private static void WriteAtomic(string path, UserDocument document)
|
|
{
|
|
var json = JsonSerializer.Serialize(document, Json);
|
|
var temp = path + ".tmp";
|
|
File.WriteAllText(temp, json);
|
|
File.Move(temp, path, overwrite: true);
|
|
}
|
|
|
|
private sealed record UserDocument(IReadOnlyList<UserRecord> Users);
|
|
}
|