Vanguard media stack: ingest, copyparty, Syncthing, normalize sweep, Authentik, Jellyfin + Vanguard Metadata plugin, MariaDB, Gitea, bot runtime
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
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); }
|
||||
}
|
||||
Reference in New Issue
Block a user