forked from Eauldane/SnowcloakClient
138 lines
5.1 KiB
C#
138 lines
5.1 KiB
C#
using MareSynchronos.MareConfiguration.Configurations;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
|
|
namespace MareSynchronos.MareConfiguration;
|
|
|
|
public class ConfigurationSaveService : IHostedService
|
|
{
|
|
private readonly HashSet<object> _configsToSave = [];
|
|
private readonly ILogger<ConfigurationSaveService> _logger;
|
|
private readonly SemaphoreSlim _configSaveSemaphore = new(1, 1);
|
|
private readonly CancellationTokenSource _configSaveCheckCts = new();
|
|
public const string BackupFolder = "config_backup";
|
|
private readonly MethodInfo _saveMethod;
|
|
|
|
public ConfigurationSaveService(ILogger<ConfigurationSaveService> logger, IEnumerable<IConfigService<IMareConfiguration>> configs)
|
|
{
|
|
foreach (var config in configs)
|
|
{
|
|
config.ConfigSave += OnConfigurationSave;
|
|
}
|
|
_logger = logger;
|
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
|
_saveMethod = GetType().GetMethod(nameof(SaveConfig), BindingFlags.Instance | BindingFlags.NonPublic)!;
|
|
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
|
}
|
|
|
|
private void OnConfigurationSave(object? sender, EventArgs e)
|
|
{
|
|
_configSaveSemaphore.Wait();
|
|
_configsToSave.Add(sender!);
|
|
_configSaveSemaphore.Release();
|
|
}
|
|
|
|
private async Task PeriodicSaveCheck(CancellationToken ct)
|
|
{
|
|
while (!ct.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await SaveConfigs().ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error during SaveConfigs");
|
|
}
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(5), ct).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private async Task SaveConfigs()
|
|
{
|
|
if (_configsToSave.Count == 0) return;
|
|
|
|
await _configSaveSemaphore.WaitAsync().ConfigureAwait(false);
|
|
var configList = _configsToSave.ToList();
|
|
_configsToSave.Clear();
|
|
_configSaveSemaphore.Release();
|
|
|
|
foreach (var config in configList)
|
|
{
|
|
var expectedType = config.GetType().BaseType!.GetGenericArguments()[0];
|
|
var save = _saveMethod.MakeGenericMethod(expectedType);
|
|
await ((Task)save.Invoke(this, [config])!).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private async Task SaveConfig<T>(IConfigService<T> config) where T : IMareConfiguration
|
|
{
|
|
_logger.LogTrace("Saving {configName}", config.ConfigurationName);
|
|
var configDir = config.ConfigurationPath.Replace(config.ConfigurationName, string.Empty);
|
|
|
|
try
|
|
{
|
|
var configBackupFolder = Path.Join(configDir, BackupFolder);
|
|
if (!Directory.Exists(configBackupFolder))
|
|
Directory.CreateDirectory(configBackupFolder);
|
|
|
|
var configNameSplit = config.ConfigurationName.Split(".");
|
|
var existingConfigs = Directory.EnumerateFiles(
|
|
configBackupFolder,
|
|
configNameSplit[0] + "*")
|
|
.Select(c => new FileInfo(c))
|
|
.OrderByDescending(c => c.LastWriteTime).ToList();
|
|
if (existingConfigs.Skip(10).Any())
|
|
{
|
|
foreach (var oldBak in existingConfigs.Skip(10).ToList())
|
|
{
|
|
oldBak.Delete();
|
|
}
|
|
}
|
|
|
|
string backupPath = Path.Combine(configBackupFolder, configNameSplit[0] + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + "." + configNameSplit[1]);
|
|
_logger.LogTrace("Backing up current config to {backupPath}", backupPath);
|
|
File.Copy(config.ConfigurationPath, backupPath, overwrite: true);
|
|
FileInfo fi = new(backupPath);
|
|
fi.LastWriteTimeUtc = DateTime.UtcNow;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// ignore if file cannot be backupped
|
|
_logger.LogWarning(ex, "Could not create backup for {config}", config.ConfigurationPath);
|
|
}
|
|
|
|
var temp = config.ConfigurationPath + ".tmp";
|
|
try
|
|
{
|
|
await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(config.Current, typeof(T), new JsonSerializerOptions()
|
|
{
|
|
WriteIndented = true
|
|
})).ConfigureAwait(false);
|
|
File.Move(temp, config.ConfigurationPath, true);
|
|
config.UpdateLastWriteTime();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Error during config save of {config}", config.ConfigurationName);
|
|
}
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_ = Task.Run(() => PeriodicSaveCheck(_configSaveCheckCts.Token));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public async Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
await _configSaveCheckCts.CancelAsync().ConfigureAwait(false);
|
|
_configSaveCheckCts.Dispose();
|
|
|
|
await SaveConfigs().ConfigureAwait(false);
|
|
}
|
|
}
|