mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
148 lines
4.4 KiB
C#
148 lines
4.4 KiB
C#
|
using System.Text.Json;
|
||
|
using K4os.Compression.LZ4.Internal;
|
||
|
using Microsoft.Extensions.Logging;
|
||
|
using Wabbajack.BuildServer;
|
||
|
using Wabbajack.Common;
|
||
|
using Wabbajack.DTOs;
|
||
|
using Wabbajack.DTOs.JsonConverters;
|
||
|
using Wabbajack.Networking.NexusApi;
|
||
|
using Wabbajack.Networking.NexusApi.DTOs;
|
||
|
using Wabbajack.Paths;
|
||
|
using Wabbajack.Paths.IO;
|
||
|
using Wabbajack.Server.DTOs;
|
||
|
|
||
|
namespace Wabbajack.Server.Services;
|
||
|
|
||
|
public class NexusCacheManager
|
||
|
{
|
||
|
private readonly ILogger<NexusCacheManager> _loggger;
|
||
|
private readonly DTOSerializer _dtos;
|
||
|
private readonly AppSettings _configuration;
|
||
|
private readonly AbsolutePath _cacheFolder;
|
||
|
private readonly SemaphoreSlim _lockObject;
|
||
|
private readonly NexusApi _nexusAPI;
|
||
|
private readonly Timer _timer;
|
||
|
private readonly DiscordWebHook _discord;
|
||
|
|
||
|
public NexusCacheManager(ILogger<NexusCacheManager> logger, DTOSerializer dtos, AppSettings configuration, NexusApi nexusApi, DiscordWebHook discord)
|
||
|
{
|
||
|
_loggger = logger;
|
||
|
_dtos = dtos;
|
||
|
_configuration = configuration;
|
||
|
_cacheFolder = configuration.NexusCacheFolder.ToAbsolutePath();
|
||
|
_lockObject = new SemaphoreSlim(1);
|
||
|
_nexusAPI = nexusApi;
|
||
|
_discord = discord;
|
||
|
|
||
|
_timer = new Timer(_ => UpdateNexusCacheAPI().FireAndForget(), null, TimeSpan.FromSeconds(2),
|
||
|
TimeSpan.FromHours(4));
|
||
|
}
|
||
|
|
||
|
|
||
|
private AbsolutePath CacheFile(string key)
|
||
|
{
|
||
|
return _cacheFolder.Combine(key).WithExtension(Ext.Json);
|
||
|
}
|
||
|
|
||
|
|
||
|
private bool HaveCache(string key)
|
||
|
{
|
||
|
return CacheFile(key).FileExists();
|
||
|
}
|
||
|
|
||
|
public async Task SaveCache<T>(string key, T value, CancellationToken token)
|
||
|
{
|
||
|
var ms = new MemoryStream();
|
||
|
await JsonSerializer.SerializeAsync(ms, value, _dtos.Options, token);
|
||
|
await ms.FlushAsync(token);
|
||
|
var data = ms.ToArray();
|
||
|
await _lockObject.WaitAsync(token);
|
||
|
try
|
||
|
{
|
||
|
await CacheFile(key).WriteAllBytesAsync(data, token: token);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_lockObject.Release();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public async Task<T?> GetCache<T>(string key, CancellationToken token)
|
||
|
{
|
||
|
if (!HaveCache(key)) return default;
|
||
|
|
||
|
var file = CacheFile(key);
|
||
|
await _lockObject.WaitAsync(token);
|
||
|
byte[] data;
|
||
|
try
|
||
|
{
|
||
|
data = await file.ReadAllBytesAsync(token);
|
||
|
}
|
||
|
catch (FileNotFoundException ex)
|
||
|
{
|
||
|
return default;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_lockObject.Release();
|
||
|
}
|
||
|
return await JsonSerializer.DeserializeAsync<T>(new MemoryStream(data), _dtos.Options, token);
|
||
|
}
|
||
|
|
||
|
public async Task UpdateNexusCacheAPI()
|
||
|
{
|
||
|
var gameTasks = GameRegistry.Games.Values
|
||
|
.Where(g => g.NexusName != null)
|
||
|
.SelectAsync(async game =>
|
||
|
{
|
||
|
var mods = await _nexusAPI.GetUpdates(game.Game, CancellationToken.None);
|
||
|
return (game, mods);
|
||
|
});
|
||
|
|
||
|
|
||
|
var purgeList = new List<(string Key, DateTime Date)>();
|
||
|
|
||
|
await foreach (var (game, mods) in gameTasks)
|
||
|
{
|
||
|
foreach (var mod in mods.Item1)
|
||
|
{
|
||
|
var date = Math.Max(mod.LastestModActivity, mod.LatestFileUpdate).AsUnixTime();
|
||
|
purgeList.Add(($"_{game.Game.MetaData().NexusName!.ToLowerInvariant()}_{mod.ModId}_", date));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This is O(m * n) where n and m are 15,000 items, we really should improve this
|
||
|
var files = (from file in _cacheFolder.EnumerateFiles().AsParallel()
|
||
|
from entry in purgeList
|
||
|
where file.FileName.ToString().Contains(entry.Key)
|
||
|
where file.LastModifiedUtc() < entry.Date
|
||
|
select file).ToHashSet();
|
||
|
|
||
|
foreach (var file in files)
|
||
|
{
|
||
|
await PurgeCacheEntry(file);
|
||
|
}
|
||
|
|
||
|
await _discord.Send(Channel.Ham, new DiscordMessage
|
||
|
{
|
||
|
Content = $"Cleared {files.Count} Nexus cache entries due to updates"
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private async Task PurgeCacheEntry(AbsolutePath file)
|
||
|
{
|
||
|
await _lockObject.WaitAsync();
|
||
|
try
|
||
|
{
|
||
|
if (file.FileExists()) file.Delete();
|
||
|
}
|
||
|
catch (FileNotFoundException)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_lockObject.Release();
|
||
|
}
|
||
|
}
|
||
|
}
|