Files
h-school/src/HSchool.Server/Game/UserStore.cs
T

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);
}