Files

78 lines
3.0 KiB
C#

using System.Text.Json;
namespace Jellyfin.Plugin.Vanguard.Services;
/// <summary>
/// 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.
/// </summary>
public sealed class EventStore
{
private readonly object _lock = new();
private readonly string _path;
private Dictionary<string, DiscordEvent> _events = new();
private readonly Dictionary<string, (DiscordMember Member, DateTimeOffset At)> _members = new();
private readonly Dictionary<string, (string? DiscordId, DateTimeOffset At)> _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<List<DiscordEvent>>(File.ReadAllText(_path)) ?? [];
_events = list.ToDictionary(e => e.Id);
}
}
catch (Exception ex) when (ex is JsonException or IOException) { _events = new(); }
}
public void Merge(IEnumerable<DiscordEvent> 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);
}
}
/// <summary>Event whose [start - slack, end + slack] window contains <paramref name="at"/>; closest start wins.</summary>
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); }
}