250501 커밋

This commit is contained in:
2025-05-01 07:23:28 +09:00
parent 98bb2e3c5c
commit 23176551b7
353 changed files with 9972 additions and 6652 deletions

View File

@@ -2,25 +2,22 @@
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace ServerCore;
public sealed class ConfigManager : Singleton<ConfigManager>
public sealed class ConfigManager
{
private readonly List<(string path, string type)> m_file_sources = new();
private readonly List<JObject> m_jobject_sources = new();
private readonly Dictionary<uint, (string path, string type)> m_key_to_source = new();
private readonly Dictionary<string, JObject> m_path_to_jobject = new();
private readonly Dictionary<string, FileSystemWatcher> m_watchers = new();
private bool m_is_detect_duplicates = true;
public ConfigManager() { }
@@ -30,39 +27,38 @@ public sealed class ConfigManager : Singleton<ConfigManager>
m_is_detect_duplicates = enable;
}
public ConfigManager addJson(string path)
public ConfigManager addJson(uint key, string path)
{
loadJson(path);
watchFile(path, "json");
m_key_to_source[key] = (path, "json");
return this;
}
public ConfigManager addYaml(string path)
public ConfigManager addYaml(uint key, string path)
{
loadYaml(path);
watchFile(path, "yaml");
m_key_to_source[key] = (path, "yaml");
return this;
}
private void loadJson(string path)
{
if (false == File.Exists(path))
{
if (!File.Exists(path))
throw new FileNotFoundException($"Not found Json File !!! : path:{path}");
}
var json_text = File.ReadAllText(path);
var jObject = JObject.Parse(json_text);
m_file_sources.Add((path, "json"));
m_jobject_sources.Add(jObject);
m_path_to_jobject[path] = jObject;
}
private void loadYaml(string path)
{
if (false == File.Exists(path))
{
if (!File.Exists(path))
throw new FileNotFoundException($"Not found Yaml File !!! : path:{path}");
}
var yamlText = File.ReadAllText(path);
var deserializer = new DeserializerBuilder()
@@ -76,20 +72,18 @@ public sealed class ConfigManager : Singleton<ConfigManager>
var json = serializer.Serialize(yamlObject);
var jObject = JObject.Parse(json);
m_file_sources.Add((path, "yaml"));
m_jobject_sources.Add(jObject);
m_path_to_jobject[path] = jObject;
}
private void watchFile(string path, string type)
{
if (m_watchers.ContainsKey(path))
{
return;
}
var directory_name = Path.GetDirectoryName(path);
NullReferenceCheckHelper.throwIfNull(directory_name, () => $"directory_name is null !!! : path:{path}, format:{type}");
var watcher = new FileSystemWatcher(directory_name)
var watcher = new FileSystemWatcher(directory_name!)
{
Filter = Path.GetFileName(path),
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size
@@ -102,40 +96,54 @@ public sealed class ConfigManager : Singleton<ConfigManager>
m_watchers[path] = watcher;
}
private void reloadAll()
{
Log.getLogger().error("Detected changes in config file !!!, Reloading...");
m_jobject_sources.Clear();
foreach (var (path, type) in m_file_sources)
m_path_to_jobject.Clear();
foreach (var key in m_key_to_source.Keys)
{
if (type == "json")
if (false == reloadConfig(key))
{
loadJson(path);
}
else if (type == "yaml")
{
loadYaml(path);
Log.getLogger().error($"Failed to reload config for key={key} during full reload");
}
}
}
public bool reloadConfig(uint key)
{
if (!m_key_to_source.TryGetValue(key, out var entry))
return false;
try
{
if (entry.type == "json")
loadJson(entry.path);
else if (entry.type == "yaml")
loadYaml(entry.path);
Log.getLogger().info($"Reloaded config for key={key}, path={entry.path}");
return true;
}
catch (Exception ex)
{
Log.getLogger().error($"Failed to reload config: key={key}, path={entry.path}, error={ex.Message}");
return false;
}
}
public T bind<T>(string sectionName)
{
if (m_jobject_sources.Count == 0)
{
if (m_path_to_jobject.Count == 0)
throw new InvalidOperationException("No config sources have been loaded !!!");
}
var merged = new JObject();
foreach (var source in m_jobject_sources)
foreach (var source in m_path_to_jobject.Values)
{
if (true == m_is_detect_duplicates)
{
if (m_is_detect_duplicates)
detectDuplicateKeys(merged, source);
}
merged.Merge(source, new JsonMergeSettings
{
@@ -144,22 +152,114 @@ public sealed class ConfigManager : Singleton<ConfigManager>
});
}
JToken? target_token = null;
if (false == merged.TryGetValue(sectionName, out var found_target_token))
return extractSection<T>(merged, sectionName, "merged");
}
public T bindFromPath<T>(string path, string sectionName)
{
if (!m_path_to_jobject.TryGetValue(path, out var jObject))
throw new ArgumentException($"Config file not loaded or not found: path={path}");
return extractSection<T>(jObject, sectionName, $"path={path}");
}
public T bindFromKey<T>(uint key, string sectionName)
{
if (!m_key_to_source.TryGetValue(key, out var entry))
throw new ArgumentException($"Key not found: key={key}");
if (!m_path_to_jobject.TryGetValue(entry.path, out var jObject))
throw new Exception($"JObject not found for path associated with key={key}");
return extractSection<T>(jObject, sectionName, $"key={key}, path={entry.path}");
}
public bool tryBindFromKey<T>(uint key, string sectionName, out T? result)
{
result = default;
try
{
target_token = merged;
result = bindFromKey<T>(key, sectionName);
return true;
}
else
catch
{
target_token = found_target_token;
return false;
}
}
public bool tryBindFromPath<T>(string path, string sectionName, out T? result)
{
result = default;
try
{
result = bindFromPath<T>(path, sectionName);
return true;
}
catch
{
return false;
}
}
public JObject? getRawJsonByKey(uint key)
{
if (!m_key_to_source.TryGetValue(key, out var entry))
return null;
return m_path_to_jobject.TryGetValue(entry.path, out var jObject)
? jObject
: null;
}
public JObject? getRawJsonByPath(string path)
{
return m_path_to_jobject.TryGetValue(path, out var jObject)
? jObject
: null;
}
public bool removeConfig(uint key)
{
if (!m_key_to_source.TryGetValue(key, out var entry))
return false;
m_key_to_source.Remove(key);
m_path_to_jobject.Remove(entry.path);
if (m_watchers.TryGetValue(entry.path, out var watcher))
{
watcher.EnableRaisingEvents = false;
watcher.Dispose();
m_watchers.Remove(entry.path);
}
if (target_token == null)
return true;
}
public IEnumerable<uint> getAllKeys()
{
return m_key_to_source.Keys;
}
public IEnumerable<string> getAllPaths()
{
return m_path_to_jobject.Keys;
}
private T extractSection<T>(JObject jObject, string sectionName, string context)
{
if (!jObject.TryGetValue(sectionName, out var token))
{
throw new Exception($"Section could not be found !!! : sectionName:{sectionName}");
token = jObject; // fallback
}
return target_token.ToObject<T>()!;
if (token == null)
throw new Exception($"Section not found: sectionName={sectionName}, context={context}");
return token.ToObject<T>()!;
}
private void detectDuplicateKeys(JObject existing, JObject incoming)
@@ -172,4 +272,4 @@ public sealed class ConfigManager : Singleton<ConfigManager>
}
}
}
}
}

View File

@@ -0,0 +1,66 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using ServerCore;
public class ConfigManagerConfigurationProvider : ConfigurationProvider
{
private readonly ConfigManager m_config_manager;
public ConfigManagerConfigurationProvider(ConfigManager configManager)
{
m_config_manager = configManager;
Load();
}
public override void Load()
{
var data = new Dictionary<string, string>();
foreach (var jObj in m_config_manager.getAllPaths())
{
var jtoken = m_config_manager.getRawJsonByPath(jObj);
if (jtoken == null) continue;
flattenJson(jtoken, data, parentPath: null);
}
Data = data!;
}
private void flattenJson(JToken token, IDictionary<string, string> data, string? parentPath)
{
NullReferenceCheckHelper.throwIfNull(token, () => $"token is null !!!");
switch (token.Type)
{
case JTokenType.Object:
foreach (var prop in (JObject)token)
{
var path = parentPath == null ? prop.Key : $"{parentPath}:{prop.Key}";
NullReferenceCheckHelper.throwIfNull(prop.Value, () => $"prop.Value is null !!!");
flattenJson(prop.Value, data, path);
}
break;
case JTokenType.Array:
var index = 0;
foreach (var item in (JArray)token)
{
var path = $"{parentPath}:{index++}";
flattenJson(item, data, path);
}
break;
default:
if (token is JValue value && parentPath != null)
{
data[parentPath] = value.ToString();
}
break;
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Configuration;
namespace ServerCore;
public class ConfigManagerConfigurationSource : IConfigurationSource
{
private readonly ConfigManager m_config_manager;
public ConfigManagerConfigurationSource(ConfigManager configManager)
{
m_config_manager = configManager;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new ConfigManagerConfigurationProvider(m_config_manager);
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.Extensions.Configuration;
using ServerCore;
/*=============================================================================================
ConfigurationBuilder 연동 지원 Helper 클래스
var configManager = new ConfigManager()
.addJson(1, "appsettings.json")
.addYaml(2, "settings.yaml");
var configuration = new ConfigurationBuilder()
.addConfigManager(configManager)
.Build();
var mySetting = configuration["MyApp:Setting"];
author : kangms
=============================================================================================*/
public static class ConfigManagerHelper
{
public static IConfigurationBuilder addConfigManager(this IConfigurationBuilder builder, ConfigManager manager)
{
return builder.Add(new ConfigManagerConfigurationSource(manager));
}
}