using System.Text.Json; namespace Jellyfin.Plugin.Vanguard.Services; /// /// Persistent history of Discord scheduled events (Discord only returns upcoming/active ones, so we keep /// everything we have ever seen) plus an in-memory member cache. /// public sealed class EventStore { private readonly object _lock = new(); private readonly string _path; private Dictionary _events = new(); private readonly Dictionary _members = new(); private readonly Dictionary _identities = new(StringComparer.OrdinalIgnoreCase); public EventStore(string dataFolder) { Directory.CreateDirectory(dataFolder); _path = Path.Combine(dataFolder, "events.json"); Load(); } private void Load() { try { if (File.Exists(_path)) { var list = JsonSerializer.Deserialize>(File.ReadAllText(_path)) ?? []; _events = list.ToDictionary(e => e.Id); } } catch (Exception ex) when (ex is JsonException or IOException) { _events = new(); } } public void Merge(IEnumerable fresh) { lock (_lock) { foreach (var e in fresh) _events[e.Id] = e; var tmp = _path + ".tmp"; File.WriteAllText(tmp, JsonSerializer.Serialize(_events.Values.OrderBy(e => e.Start).ToList(), new JsonSerializerOptions { WriteIndented = true })); File.Move(tmp, _path, overwrite: true); } } /// Event whose [start - slack, end + slack] window contains ; closest start wins. public DiscordEvent? FindEventAt(DateTimeOffset at, TimeSpan slack, TimeSpan defaultDuration) { lock (_lock) { return _events.Values .Where(e => at >= e.Start - slack && at <= (e.End ?? e.Start + defaultDuration) + slack) .OrderBy(e => Math.Abs((e.Start - at).Ticks)) .FirstOrDefault(); } } public int Count { get { lock (_lock) return _events.Count; } } public DiscordMember? GetCachedMember(string discordId, TimeSpan maxAge) { lock (_lock) return _members.TryGetValue(discordId, out var m) && DateTimeOffset.UtcNow - m.At < maxAge ? m.Member : null; } public void CacheMember(DiscordMember m) { lock (_lock) _members[m.UserId] = (m, DateTimeOffset.UtcNow); } public bool TryGetCachedIdentity(string folder, TimeSpan maxAge, out string? discordId) { lock (_lock) { if (_identities.TryGetValue(folder, out var v) && DateTimeOffset.UtcNow - v.At < maxAge) { discordId = v.DiscordId; return true; } discordId = null; return false; } } public void CacheIdentity(string folder, string? discordId) { lock (_lock) _identities[folder] = (discordId, DateTimeOffset.UtcNow); } }