78 lines
3.0 KiB
C#
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); }
|
|
}
|