Files
LightlessClient/LightlessSync/Services/PenumbraTempCollectionJanitor.cs

188 lines
5.0 KiB
C#

using System.Linq;
using LightlessSync.Interop.Ipc;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
namespace LightlessSync.Services;
public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriberBase
{
private readonly IpcManager _ipc;
private readonly LightlessConfigService _config;
private int _ran;
private static readonly TimeSpan OrphanCleanupDelay = TimeSpan.FromDays(1);
public PenumbraTempCollectionJanitor(
ILogger<PenumbraTempCollectionJanitor> logger,
LightlessMediator mediator,
IpcManager ipc,
LightlessConfigService config) : base(logger, mediator)
{
_ipc = ipc;
_config = config;
Mediator.Subscribe<PenumbraInitializedMessage>(this, _ => CleanupOrphansOnBoot());
}
public void Register(Guid id)
{
if (id == Guid.Empty) return;
var changed = false;
var config = _config.Current;
if (config.OrphanableTempCollections.Add(id))
{
changed = true;
}
var now = DateTime.UtcNow;
var existing = config.OrphanableTempCollectionEntries.FirstOrDefault(entry => entry.Id == id);
if (existing is null)
{
config.OrphanableTempCollectionEntries.Add(new OrphanableTempCollectionEntry
{
Id = id,
RegisteredAtUtc = now
});
changed = true;
}
else if (existing.RegisteredAtUtc == DateTime.MinValue)
{
existing.RegisteredAtUtc = now;
changed = true;
}
if (changed)
{
_config.Save();
}
}
public void Unregister(Guid id)
{
if (id == Guid.Empty) return;
var config = _config.Current;
var changed = config.OrphanableTempCollections.Remove(id);
changed |= RemoveEntry(config.OrphanableTempCollectionEntries, id) > 0;
if (changed)
{
_config.Save();
}
}
private void CleanupOrphansOnBoot()
{
if (Interlocked.Exchange(ref _ran, 1) == 1)
return;
if (!_ipc.Penumbra.APIAvailable)
return;
var config = _config.Current;
var ids = config.OrphanableTempCollections;
var entries = config.OrphanableTempCollectionEntries;
if (ids.Count == 0 && entries.Count == 0)
return;
var now = DateTime.UtcNow;
var changed = EnsureEntries(ids, entries, now);
var cutoff = now - OrphanCleanupDelay;
var expired = entries
.Where(entry => entry.Id != Guid.Empty && entry.RegisteredAtUtc != DateTime.MinValue && entry.RegisteredAtUtc <= cutoff)
.Select(entry => entry.Id)
.Distinct()
.ToList();
if (expired.Count == 0)
{
if (changed)
{
_config.Save();
}
return;
}
var appId = Guid.NewGuid();
Logger.LogInformation("Cleaning up {count} orphaned Lightless temp collections older than {delay}", expired.Count, OrphanCleanupDelay);
foreach (var id in expired)
{
try
{
_ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id)
.GetAwaiter().GetResult();
}
catch (Exception ex)
{
Logger.LogDebug(ex, "Failed removing orphaned temp collection {id}", id);
}
}
foreach (var id in expired)
{
ids.Remove(id);
}
foreach (var id in expired)
{
RemoveEntry(entries, id);
}
_config.Save();
}
private static int RemoveEntry(List<OrphanableTempCollectionEntry> entries, Guid id)
{
var removed = 0;
for (var i = entries.Count - 1; i >= 0; i--)
{
if (entries[i].Id != id)
{
continue;
}
entries.RemoveAt(i);
removed++;
}
return removed;
}
private static bool EnsureEntries(HashSet<Guid> ids, List<OrphanableTempCollectionEntry> entries, DateTime now)
{
var changed = false;
foreach (var id in ids)
{
if (id == Guid.Empty)
{
continue;
}
if (entries.Any(entry => entry.Id == id))
{
continue;
}
entries.Add(new OrphanableTempCollectionEntry
{
Id = id,
RegisteredAtUtc = now
});
changed = true;
}
foreach (var entry in entries)
{
if (entry.Id == Guid.Empty || entry.RegisteredAtUtc != DateTime.MinValue)
{
continue;
}
entry.RegisteredAtUtc = now;
changed = true;
}
return changed;
}
}