implement IConfigurationHandlerV2
This commit is contained in:
parent
dab429776d
commit
e8bdde70fb
@ -317,12 +317,14 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
if (plugin is ScriptPlugin scriptPlugin)
|
if (plugin is ScriptPlugin scriptPlugin)
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver,
|
||||||
|
_serviceProvider.GetService<IConfigurationHandlerV2<ScriptPluginConfiguration>>());
|
||||||
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
scriptPlugin.Watcher.Changed += async (sender, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver);
|
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver,
|
||||||
|
_serviceProvider.GetService<IConfigurationHandlerV2<ScriptPluginConfiguration>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
206
Application/IO/BaseConfigurationHandlerV2.cs
Normal file
206
Application/IO/BaseConfigurationHandlerV2.cs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.IO;
|
||||||
|
|
||||||
|
public class BaseConfigurationHandlerV2<TConfigurationType> : IConfigurationHandlerV2<TConfigurationType>
|
||||||
|
where TConfigurationType : class
|
||||||
|
{
|
||||||
|
private readonly ILogger<BaseConfigurationHandlerV2<TConfigurationType>> _logger;
|
||||||
|
private readonly ConfigurationWatcher _watcher;
|
||||||
|
private readonly JsonSerializerOptions _serializerOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
Converters =
|
||||||
|
{
|
||||||
|
new JsonStringEnumConverter()
|
||||||
|
},
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _onIo = new(1, 1);
|
||||||
|
private TConfigurationType _configurationInstance;
|
||||||
|
private string _path = string.Empty;
|
||||||
|
private event Action<string> FileUpdated;
|
||||||
|
|
||||||
|
public BaseConfigurationHandlerV2(ILogger<BaseConfigurationHandlerV2<TConfigurationType>> logger, ConfigurationWatcher watcher)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_watcher = watcher;
|
||||||
|
FileUpdated += OnFileUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
~BaseConfigurationHandlerV2()
|
||||||
|
{
|
||||||
|
FileUpdated -= OnFileUpdated;
|
||||||
|
_watcher.Unregister(_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TConfigurationType> Get(string configurationName,
|
||||||
|
TConfigurationType defaultConfiguration = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configurationName))
|
||||||
|
{
|
||||||
|
return defaultConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanName = configurationName.Replace("\\", "").Replace("/", "");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(configurationName))
|
||||||
|
{
|
||||||
|
return defaultConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
_path = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{cleanName}.json");
|
||||||
|
TConfigurationType readConfiguration = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _onIo.WaitAsync();
|
||||||
|
await using var fileStream = File.OpenRead(_path);
|
||||||
|
readConfiguration =
|
||||||
|
await JsonSerializer.DeserializeAsync<TConfigurationType>(fileStream, _serializerOptions);
|
||||||
|
fileStream.Close();
|
||||||
|
_watcher.Register(_path, FileUpdated);
|
||||||
|
|
||||||
|
if (readConfiguration is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Could not parse configuration {Type} at {FileName}", typeof(TConfigurationType).Name,
|
||||||
|
_path);
|
||||||
|
|
||||||
|
return defaultConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
if (defaultConfiguration is not null)
|
||||||
|
{
|
||||||
|
await InternalSet(defaultConfiguration, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not read configuration file at {Path}", _path);
|
||||||
|
return defaultConfiguration;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onIo.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onIo.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _configurationInstance ??= readConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set(TConfigurationType configuration)
|
||||||
|
{
|
||||||
|
await InternalSet(configuration, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Set()
|
||||||
|
{
|
||||||
|
if (_configurationInstance is not null)
|
||||||
|
{
|
||||||
|
await InternalSet(_configurationInstance, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InternalSet(TConfigurationType configuration, bool awaitSemaphore)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (awaitSemaphore)
|
||||||
|
{
|
||||||
|
await _onIo.WaitAsync();
|
||||||
|
}
|
||||||
|
await using var fileStream = File.OpenWrite(_path);
|
||||||
|
await JsonSerializer.SerializeAsync(fileStream, configuration, _serializerOptions);
|
||||||
|
fileStream.Close();
|
||||||
|
_configurationInstance = configuration;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not save configuration {Type} {Path}", configuration.GetType().Name, _path);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (awaitSemaphore && _onIo.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onIo.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnFileUpdated(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _onIo.WaitAsync();
|
||||||
|
await using var fileStream = File.OpenRead(_path);
|
||||||
|
var readConfiguration =
|
||||||
|
await JsonSerializer.DeserializeAsync<TConfigurationType>(fileStream, _serializerOptions);
|
||||||
|
fileStream.Close();
|
||||||
|
|
||||||
|
if (readConfiguration is null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Could not parse updated configuration {Type} at {Path}",
|
||||||
|
typeof(TConfigurationType).Name, filePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CopyUpdatedProperties(readConfiguration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Could not parse updated configuration {Type} at {Path}",
|
||||||
|
typeof(TConfigurationType).Name, filePath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onIo.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onIo.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyUpdatedProperties(TConfigurationType newConfiguration)
|
||||||
|
{
|
||||||
|
if (_configurationInstance is null)
|
||||||
|
{
|
||||||
|
_configurationInstance = newConfiguration;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Updating existing config with new values {Type} at {Path}", typeof(TConfigurationType).Name, _path);
|
||||||
|
|
||||||
|
if (_configurationInstance is IDictionary configDict && newConfiguration is IDictionary newConfigDict)
|
||||||
|
{
|
||||||
|
configDict.Clear();
|
||||||
|
foreach (var key in newConfigDict.Keys)
|
||||||
|
{
|
||||||
|
configDict.Add(key, newConfigDict[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var property in _configurationInstance.GetType().GetProperties()
|
||||||
|
.Where(prop => prop.CanRead && prop.CanWrite))
|
||||||
|
{
|
||||||
|
property.SetValue(_configurationInstance, property.GetValue(newConfiguration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -352,6 +352,12 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
// setup the static resources (config/master api/translations)
|
// setup the static resources (config/master api/translations)
|
||||||
var serviceCollection = new ServiceCollection();
|
var serviceCollection = new ServiceCollection();
|
||||||
|
serviceCollection.AddConfiguration<ApplicationConfiguration>("IW4MAdminSettings")
|
||||||
|
.AddConfiguration<DefaultSettings>()
|
||||||
|
.AddConfiguration<CommandConfiguration>()
|
||||||
|
.AddConfiguration<StatsConfiguration>("StatsPluginSettings");
|
||||||
|
|
||||||
|
// for legacy purposes. update at some point
|
||||||
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||||
await appConfigHandler.BuildAsync();
|
await appConfigHandler.BuildAsync();
|
||||||
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
|
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
|
||||||
@ -456,6 +462,9 @@ namespace IW4MAdmin.Application
|
|||||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||||
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
||||||
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
|
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
|
||||||
|
.AddSingleton(new ConfigurationWatcher())
|
||||||
|
.AddSingleton(typeof(IConfigurationHandlerV2<>), typeof(BaseConfigurationHandlerV2<>))
|
||||||
|
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
|
||||||
.AddSingleton(translationLookup)
|
.AddSingleton(translationLookup)
|
||||||
.AddDatabaseContextOptions(appConfig);
|
.AddDatabaseContextOptions(appConfig);
|
||||||
|
|
||||||
|
10
SharedLibraryCore/Interfaces/IConfigurationHandlerV2.cs
Normal file
10
SharedLibraryCore/Interfaces/IConfigurationHandlerV2.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IConfigurationHandlerV2<TConfigurationType> where TConfigurationType: class
|
||||||
|
{
|
||||||
|
Task<TConfigurationType> Get(string configurationName, TConfigurationType defaultConfiguration = null);
|
||||||
|
Task Set(TConfigurationType configuration);
|
||||||
|
Task Set();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user