mirror of
https://github.com/roflmuffin/CounterStrikeSharp.git
synced 2025-12-05 23:58:24 -08:00
feat(config): add toml loading support (#804)
This commit is contained in:
@@ -1,38 +1,60 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using CounterStrikeSharp.API.Core;
|
using CounterStrikeSharp.API.Core;
|
||||||
using CounterStrikeSharp.API.Core.Attributes;
|
using CounterStrikeSharp.API.Core.Attributes;
|
||||||
|
using CounterStrikeSharp.API.Core.Attributes.Registration;
|
||||||
namespace WithConfig;
|
using CounterStrikeSharp.API.Modules.Admin;
|
||||||
|
using CounterStrikeSharp.API.Modules.Commands;
|
||||||
public class SampleConfig : BasePluginConfig
|
using CounterStrikeSharp.API.Modules.Config;
|
||||||
{
|
using CounterStrikeSharp.API.Modules.Extensions;
|
||||||
[JsonPropertyName("ChatPrefix")] public string ChatPrefix { get; set; } = "My Cool Plugin";
|
|
||||||
|
namespace WithConfig;
|
||||||
[JsonPropertyName("ChatInterval")] public float ChatInterval { get; set; } = 60;
|
|
||||||
}
|
public class SampleConfig : BasePluginConfig
|
||||||
|
{
|
||||||
[MinimumApiVersion(80)]
|
[JsonPropertyName("ChatPrefix")] public string ChatPrefix { get; set; } = "My Cool Plugin";
|
||||||
public class WithConfigPlugin : BasePlugin, IPluginConfig<SampleConfig>
|
|
||||||
{
|
[JsonPropertyName("ChatInterval")] public float ChatInterval { get; set; } = 60;
|
||||||
public override string ModuleName => "Example: With Config";
|
}
|
||||||
public override string ModuleVersion => "1.0.0";
|
|
||||||
|
[MinimumApiVersion(80)]
|
||||||
public SampleConfig Config { get; set; }
|
public class WithConfigPlugin : BasePlugin, IPluginConfig<SampleConfig>
|
||||||
|
{
|
||||||
public void OnConfigParsed(SampleConfig config)
|
public override string ModuleName => "Example: With Config";
|
||||||
{
|
public override string ModuleVersion => "1.0.0";
|
||||||
// Do manual verification of the config and override any invalid values
|
|
||||||
if (config.ChatInterval > 60)
|
public SampleConfig Config { get; set; }
|
||||||
{
|
|
||||||
config.ChatInterval = 60;
|
public void OnConfigParsed(SampleConfig config)
|
||||||
}
|
{
|
||||||
|
// Do manual verification of the config and override any invalid values
|
||||||
if (config.ChatPrefix.Length > 25)
|
if (config.ChatInterval > 60)
|
||||||
{
|
{
|
||||||
throw new Exception($"Invalid value has been set to config value 'ChatPrefix': {config.ChatPrefix}");
|
config.ChatInterval = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once we've validated the config, we can set it to the instance
|
if (config.ChatPrefix.Length > 25)
|
||||||
Config = config;
|
{
|
||||||
}
|
throw new Exception($"Invalid value has been set to config value 'ChatPrefix': {config.ChatPrefix}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Once we've validated the config, we can set it to the instance
|
||||||
|
Config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("css_reload_config", "Reloads the plugin config")]
|
||||||
|
public void OnReloadConfig(CCSPlayerController? player, CommandInfo commandInfo)
|
||||||
|
{
|
||||||
|
commandInfo.ReplyToCommand("Chat Interval before reload: " + Config.ChatInterval);
|
||||||
|
Config.Reload();
|
||||||
|
commandInfo.ReplyToCommand("Chat Interval after reload: " + Config.ChatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConsoleCommand("css_reset_config", "Resets the plugin config")]
|
||||||
|
public void OnResetConfig(CCSPlayerController? player, CommandInfo commandInfo)
|
||||||
|
{
|
||||||
|
commandInfo.ReplyToCommand("Chat Interval before reset: " + Config.ChatInterval);
|
||||||
|
Config.ChatInterval = 60;
|
||||||
|
Config.Update();
|
||||||
|
commandInfo.ReplyToCommand("Chat Interval after reset: " + Config.ChatInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0"/>
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0"/>
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
||||||
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0"/>
|
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0"/>
|
||||||
|
<PackageReference Include="Tomlyn" Version="0.19.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Core\Schema\"/>
|
<Folder Include="Core\Schema\"/>
|
||||||
@@ -59,9 +60,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
|
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
|
||||||
<Exec
|
<Exec
|
||||||
Command="git describe --long --always --exclude=* --abbrev=7"
|
Command="git describe --long --always --exclude=* --abbrev=7"
|
||||||
ConsoleToMSBuild="True"
|
ConsoleToMSBuild="True"
|
||||||
IgnoreExitCode="False"
|
IgnoreExitCode="False"
|
||||||
>
|
>
|
||||||
<Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput"/>
|
<Output PropertyName="SourceRevisionId" TaskParameter="ConsoleOutput"/>
|
||||||
</Exec>
|
</Exec>
|
||||||
|
|||||||
@@ -14,18 +14,21 @@
|
|||||||
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
* along with CounterStrikeSharp. If not, see <https://www.gnu.org/licenses/>. *
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
using CounterStrikeSharp.API.Core;
|
|
||||||
using CounterStrikeSharp.API.Core.Logging;
|
using CounterStrikeSharp.API.Core.Logging;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Tomlyn;
|
||||||
|
|
||||||
namespace CounterStrikeSharp.API.Modules.Config
|
namespace CounterStrikeSharp.API.Modules.Config
|
||||||
{
|
{
|
||||||
|
enum ConfigType
|
||||||
|
{
|
||||||
|
Json,
|
||||||
|
Toml
|
||||||
|
}
|
||||||
|
|
||||||
public static class ConfigManager
|
public static class ConfigManager
|
||||||
{
|
{
|
||||||
private static readonly DirectoryInfo? _rootDir;
|
private static readonly DirectoryInfo? _rootDir;
|
||||||
@@ -33,47 +36,59 @@ namespace CounterStrikeSharp.API.Modules.Config
|
|||||||
private static readonly string _pluginConfigsFolderPath;
|
private static readonly string _pluginConfigsFolderPath;
|
||||||
private static ILogger _logger = CoreLogging.Factory.CreateLogger("ConfigManager");
|
private static ILogger _logger = CoreLogging.Factory.CreateLogger("ConfigManager");
|
||||||
|
|
||||||
|
internal static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
ReadCommentHandling = JsonCommentHandling.Skip
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static TomlModelOptions TomlModelOptions { get; } = new()
|
||||||
|
{
|
||||||
|
ConvertPropertyName = name => name
|
||||||
|
};
|
||||||
|
|
||||||
static ConfigManager()
|
static ConfigManager()
|
||||||
{
|
{
|
||||||
_rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
|
_rootDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.Parent;
|
||||||
_pluginConfigsFolderPath = Path.Combine(_rootDir.FullName, "configs", "plugins");
|
_pluginConfigsFolderPath = Path.Combine(_rootDir.FullName, "configs", "plugins");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Load<T>(string pluginName) where T : IBasePluginConfig, new()
|
public static T Load<T>(string pluginName) where T : class, IBasePluginConfig, new()
|
||||||
{
|
{
|
||||||
string directoryPath = Path.Combine(_pluginConfigsFolderPath, pluginName);
|
string directoryPath = Path.Combine(_pluginConfigsFolderPath, pluginName);
|
||||||
string configPath = Path.Combine(directoryPath, $"{pluginName}.json");
|
string configPath = Path.Combine(directoryPath, $"{pluginName}");
|
||||||
string exampleConfigPath = Path.Combine(directoryPath, $"{pluginName}.example.json");
|
string exampleConfigPath = Path.Combine(directoryPath, $"{pluginName}.example");
|
||||||
|
|
||||||
T config = (T)Activator.CreateInstance(typeof(T))!;
|
string[] configFilePaths =
|
||||||
|
[
|
||||||
|
$"{configPath}.toml",
|
||||||
|
$"{configPath}.json",
|
||||||
|
];
|
||||||
|
|
||||||
if (!File.Exists(configPath) && !File.Exists(exampleConfigPath))
|
foreach (var path in configFilePaths)
|
||||||
{
|
{
|
||||||
try
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(directoryPath))
|
return Deserialize<T>(path);
|
||||||
{
|
}
|
||||||
Directory.CreateDirectory(directoryPath);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
string[] exampleFilePaths =
|
||||||
builder.Append(
|
[
|
||||||
$"// This configuration was automatically generated by CounterStrikeSharp for plugin '{pluginName}', at {DateTimeOffset.Now:yyyy/MM/dd hh:mm:ss}\n");
|
$"{exampleConfigPath}.toml",
|
||||||
builder.Append(JsonSerializer.Serialize<T>(config,
|
$"{exampleConfigPath}.json"
|
||||||
new JsonSerializerOptions { WriteIndented = true }));
|
];
|
||||||
File.WriteAllText(configPath, builder.ToString());
|
|
||||||
return config;
|
foreach (var path in exampleFilePaths)
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to generate configuration file for {PluginName}", pluginName);
|
|
||||||
}
|
|
||||||
} else if (File.Exists(exampleConfigPath) && !File.Exists(configPath))
|
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(path)) continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Copying example configuration file for {PluginName}", pluginName);
|
_logger.LogInformation("Copying example configuration file for {PluginName}", pluginName);
|
||||||
File.Copy(exampleConfigPath, configPath);
|
var destPath = Path.Combine(directoryPath, Path.GetFileName(path).Replace(".example", ""));
|
||||||
|
File.Copy(path, destPath);
|
||||||
|
return Deserialize<T>(destPath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -83,14 +98,58 @@ namespace CounterStrikeSharp.API.Modules.Config
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
config = JsonSerializer.Deserialize<T>(File.ReadAllText(configPath), new JsonSerializerOptions() { ReadCommentHandling = JsonCommentHandling.Skip })!;
|
if (!Directory.Exists(directoryPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = new T();
|
||||||
|
var output = Serialize(config, ConfigType.Json, pluginName);
|
||||||
|
File.WriteAllText(Path.Combine(directoryPath, $"{pluginName}.json"), output);
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to parse configuration file for {PluginName}", pluginName);
|
_logger.LogError(ex, "Failed to generate configuration file for {PluginName}", pluginName);
|
||||||
|
return new T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T Deserialize<T>(string path) where T : class, IBasePluginConfig, new()
|
||||||
|
{
|
||||||
|
switch (Path.GetExtension(path))
|
||||||
|
{
|
||||||
|
case ".toml":
|
||||||
|
return Toml.ToModel<T>(File.ReadAllText(path), options: TomlModelOptions);
|
||||||
|
case ".json":
|
||||||
|
return JsonSerializer.Deserialize<T>(File.ReadAllText(path), JsonSerializerOptions)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
throw new NotSupportedException("Unsupported configuration file format");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Serialize<T>(T config, ConfigType configType, string pluginName) where T : class, IBasePluginConfig, new()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
string comment =
|
||||||
|
$"This configuration was automatically generated by CounterStrikeSharp for plugin '{pluginName}', at {DateTimeOffset.Now:yyyy/MM/dd hh:mm:ss}\n";
|
||||||
|
|
||||||
|
switch (configType)
|
||||||
|
{
|
||||||
|
case ConfigType.Json:
|
||||||
|
builder.Append($"// {comment}");
|
||||||
|
builder.Append(JsonSerializer.Serialize<T>(config, JsonSerializerOptions));
|
||||||
|
break;
|
||||||
|
case ConfigType.Toml:
|
||||||
|
builder.Append($"# {comment}");
|
||||||
|
builder.Append(Toml.FromModel(config, options: TomlModelOptions));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException("Unsupported configuration file format");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
using CounterStrikeSharp.API.Modules.Config;
|
||||||
|
using Tomlyn;
|
||||||
|
|
||||||
namespace CounterStrikeSharp.API.Modules.Extensions;
|
namespace CounterStrikeSharp.API.Modules.Extensions;
|
||||||
|
|
||||||
public static class PluginConfigExtensions
|
public static class PluginConfigExtensions
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
|
public static JsonSerializerOptions JsonSerializerOptions => ConfigManager.JsonSerializerOptions;
|
||||||
{
|
|
||||||
WriteIndented = true,
|
|
||||||
ReadCommentHandling = JsonCommentHandling.Skip
|
|
||||||
};
|
|
||||||
|
|
||||||
public static JsonSerializerOptions JsonSerializerOptions => _jsonSerializerOptions;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the configuration file path
|
/// Gets the configuration file path
|
||||||
@@ -21,7 +18,24 @@ public static class PluginConfigExtensions
|
|||||||
public static string GetConfigPath<T>(this T _) where T : BasePluginConfig, new()
|
public static string GetConfigPath<T>(this T _) where T : BasePluginConfig, new()
|
||||||
{
|
{
|
||||||
string assemblyName = typeof(T).Assembly.GetName().Name ?? string.Empty;
|
string assemblyName = typeof(T).Assembly.GetName().Name ?? string.Empty;
|
||||||
return Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp", "configs", "plugins", assemblyName, $"{assemblyName}.json");
|
|
||||||
|
string[] configFilePaths =
|
||||||
|
[
|
||||||
|
Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp", "configs", "plugins", assemblyName,
|
||||||
|
$"{assemblyName}.json"),
|
||||||
|
Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp", "configs", "plugins", assemblyName,
|
||||||
|
$"{assemblyName}.toml"),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (var path in configFilePaths)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFilePaths[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -37,7 +51,22 @@ public static class PluginConfigExtensions
|
|||||||
{
|
{
|
||||||
using var stream = new FileStream(configPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
using var stream = new FileStream(configPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
using var writer = new StreamWriter(stream);
|
using var writer = new StreamWriter(stream);
|
||||||
writer.Write(JsonSerializer.Serialize(config, JsonSerializerOptions));
|
|
||||||
|
switch (Path.GetExtension(configPath))
|
||||||
|
{
|
||||||
|
case ".json":
|
||||||
|
{
|
||||||
|
writer.Write(JsonSerializer.Serialize(config, ConfigManager.JsonSerializerOptions));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ".toml":
|
||||||
|
writer.Write(Toml.FromModel(config, ConfigManager.TomlModelOptions));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Configuration file type '{Path.GetExtension(configPath)}' is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -63,8 +92,22 @@ public static class PluginConfigExtensions
|
|||||||
|
|
||||||
var configContent = File.ReadAllText(configPath);
|
var configContent = File.ReadAllText(configPath);
|
||||||
|
|
||||||
var newConfig = JsonSerializer.Deserialize<T>(configContent, JsonSerializerOptions)
|
T? newConfig = null;
|
||||||
?? throw new JsonException($"Deserialization failed for configuration file '{configPath}'.");
|
switch (Path.GetExtension(configPath))
|
||||||
|
{
|
||||||
|
case ".json":
|
||||||
|
newConfig = JsonSerializer.Deserialize<T>(configContent, ConfigManager.JsonSerializerOptions)
|
||||||
|
?? throw new JsonException($"Deserialization failed for configuration file '{configPath}'.");
|
||||||
|
break;
|
||||||
|
case ".toml":
|
||||||
|
newConfig = Toml.ToModel<T>(configContent, options: ConfigManager.TomlModelOptions);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newConfig is null)
|
||||||
|
{
|
||||||
|
throw new SerializationException($"Deserialization failed for configuration file '{configPath}'.");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user