fork from 1.3
This commit is contained in:
13
FreeSR.Shared/Command/CommandAttribute.cs
Normal file
13
FreeSR.Shared/Command/CommandAttribute.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class CommandAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
FreeSR.Shared/Command/CommandCategory.cs
Normal file
46
FreeSR.Shared/Command/CommandCategory.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using System.Reflection;
|
||||
|
||||
public class CommandCategory : ICommandCategory
|
||||
{
|
||||
private readonly Dictionary<string, CommandHandler> handlers = new Dictionary<string, CommandHandler>();
|
||||
private readonly Dictionary<string, ICommandCategory> categories = new Dictionary<string, ICommandCategory>();
|
||||
|
||||
public void Build()
|
||||
{
|
||||
Type type = GetType();
|
||||
foreach (MethodInfo info in type.GetMethods())
|
||||
{
|
||||
CommandAttribute attribute = info.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
var handler = new CommandHandler(type, info);
|
||||
handlers.Add(attribute.Name, handler);
|
||||
}
|
||||
|
||||
foreach (Type info in type.GetNestedTypes())
|
||||
{
|
||||
CommandAttribute attribute = info.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
CommandCategory category = (CommandCategory)Activator.CreateInstance(info);
|
||||
category.Build();
|
||||
categories.Add(attribute.Name, category);
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult Invoke(ICommandContext context, string[] parameters, uint depth)
|
||||
{
|
||||
if (handlers.TryGetValue(parameters[depth], out CommandHandler handler))
|
||||
return handler.Invoke(this, context, parameters, depth + 1);
|
||||
if (categories.TryGetValue(parameters[depth], out ICommandCategory category))
|
||||
return category.Invoke(context, parameters, depth + 1);
|
||||
|
||||
return CommandResult.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
FreeSR.Shared/Command/CommandException.cs
Normal file
10
FreeSR.Shared/Command/CommandException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
public class CommandException : Exception
|
||||
{
|
||||
public CommandException(string message) : base(message)
|
||||
{
|
||||
// CommandException.
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FreeSR.Shared/Command/CommandHandler.cs
Normal file
68
FreeSR.Shared/Command/CommandHandler.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using FreeSR.Shared.Command.Convert;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
public class CommandHandler
|
||||
{
|
||||
private Delegate _handlerDelegate;
|
||||
private readonly List<ICommandParameterConverter> _additionalParameterConverters = new List<ICommandParameterConverter>();
|
||||
|
||||
public CommandHandler(Type type, MethodInfo method)
|
||||
{
|
||||
InitializeDelegate(type, method);
|
||||
InitializeParameters(method);
|
||||
}
|
||||
|
||||
private void InitializeDelegate(Type type, MethodInfo method)
|
||||
{
|
||||
ParameterInfo[] info = method.GetParameters();
|
||||
if (info.Length < 1 || !typeof(ICommandContext).IsAssignableFrom(info[0].ParameterType))
|
||||
throw new CommandException("");
|
||||
|
||||
ParameterExpression instance = Expression.Parameter(type);
|
||||
|
||||
var parameters = new List<ParameterExpression> { instance };
|
||||
parameters.AddRange(info.Select(p => Expression.Parameter(p.ParameterType)));
|
||||
|
||||
MethodCallExpression call = Expression.Call(instance, method, parameters.Skip(1));
|
||||
LambdaExpression lambda = Expression.Lambda(call, parameters);
|
||||
_handlerDelegate = lambda.Compile();
|
||||
}
|
||||
|
||||
private void InitializeParameters(MethodInfo method)
|
||||
{
|
||||
foreach (Type parameterType in method.GetParameters()
|
||||
.Skip(1)
|
||||
.Select(p => p.ParameterType))
|
||||
{
|
||||
ICommandParameterConverter converter = CommandManager.Instance.GetConverter(parameterType);
|
||||
if (converter == null)
|
||||
throw new CommandException("");
|
||||
|
||||
_additionalParameterConverters.Add(converter);
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult Invoke(ICommandCategory category, ICommandContext context, string[] parameters, uint depth)
|
||||
{
|
||||
var additionalParameterCount = parameters.Length - depth;
|
||||
if (additionalParameterCount < _additionalParameterConverters.Count)
|
||||
return CommandResult.Parameter;
|
||||
|
||||
var parameterObjects = new List<object> { category, context };
|
||||
for (int i = 0; i < _additionalParameterConverters.Count; i++)
|
||||
{
|
||||
if (!_additionalParameterConverters[i].TryConvert(parameters[depth + i], out object result))
|
||||
return CommandResult.Parameter;
|
||||
|
||||
parameterObjects.Add(result);
|
||||
}
|
||||
|
||||
_handlerDelegate.DynamicInvoke(parameterObjects.ToArray());
|
||||
return CommandResult.Ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
FreeSR.Shared/Command/CommandManager.cs
Normal file
93
FreeSR.Shared/Command/CommandManager.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using System.Reflection;
|
||||
using FreeSR.Shared.Command.Context;
|
||||
using FreeSR.Shared.Command.Convert;
|
||||
using NLog;
|
||||
|
||||
public sealed class CommandManager : Singleton<CommandManager>
|
||||
{
|
||||
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly Dictionary<Type, ICommandParameterConverter> converters = new Dictionary<Type, ICommandParameterConverter>();
|
||||
private readonly Dictionary<string, CommandCategory> categories = new Dictionary<string, CommandCategory>();
|
||||
|
||||
private CommandManager()
|
||||
{
|
||||
// CommandManager.
|
||||
}
|
||||
|
||||
public void Initialize(params Type[] categories)
|
||||
{
|
||||
InitializeConverters();
|
||||
InitializeCategories(categories);
|
||||
}
|
||||
|
||||
private void InitializeConverters()
|
||||
{
|
||||
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => typeof(ICommandParameterConverter).IsAssignableFrom(t)))
|
||||
{
|
||||
CommandParameterConverterAttribute attribute = type.GetCustomAttribute<CommandParameterConverterAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
ICommandParameterConverter converter = (ICommandParameterConverter)Activator.CreateInstance(type);
|
||||
converters.Add(attribute.Type, converter);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeCategories(params Type[] types)
|
||||
{
|
||||
foreach (Type type in types)
|
||||
{
|
||||
CommandAttribute attribute = type.GetCustomAttribute<CommandAttribute>();
|
||||
if (attribute == null)
|
||||
continue;
|
||||
|
||||
CommandCategory category = (CommandCategory)Activator.CreateInstance(type);
|
||||
category.Build();
|
||||
|
||||
categories.Add(attribute.Name, category);
|
||||
}
|
||||
}
|
||||
|
||||
public ICommandParameterConverter GetConverter(Type type)
|
||||
{
|
||||
return converters.TryGetValue(type, out ICommandParameterConverter converter) ? converter : null;
|
||||
}
|
||||
|
||||
public void Invoke(string command)
|
||||
{
|
||||
var context = new ConsoleCommandContext();
|
||||
Invoke(context, command);
|
||||
}
|
||||
|
||||
public void Invoke(ICommandContext context, string command)
|
||||
{
|
||||
CommandResult result = InvokeResult(context, command);
|
||||
if (result != CommandResult.Ok)
|
||||
context.SendError(GetError());
|
||||
|
||||
string GetError()
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
CommandResult.Invalid => "No valid command was found.",
|
||||
CommandResult.Parameter => "Invalid parameters were provided for the command.",
|
||||
CommandResult.Permission => "You don't have permission to invoke the command.",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private CommandResult InvokeResult(ICommandContext context, string command)
|
||||
{
|
||||
string[] parameters = command.Split(' ');
|
||||
if (categories.TryGetValue(parameters[0], out CommandCategory category))
|
||||
return category.Invoke(context, parameters, 1);
|
||||
|
||||
return CommandResult.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
FreeSR.Shared/Command/CommandResult.cs
Normal file
10
FreeSR.Shared/Command/CommandResult.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
public enum CommandResult
|
||||
{
|
||||
Ok,
|
||||
Invalid,
|
||||
Permission,
|
||||
Parameter
|
||||
}
|
||||
}
|
||||
19
FreeSR.Shared/Command/Context/ConsoleCommandContext.cs
Normal file
19
FreeSR.Shared/Command/Context/ConsoleCommandContext.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace FreeSR.Shared.Command.Context
|
||||
{
|
||||
using NLog;
|
||||
|
||||
public class ConsoleCommandContext : ICommandContext
|
||||
{
|
||||
private static readonly Logger s_log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
s_log.Info(message);
|
||||
}
|
||||
|
||||
public void SendError(string message)
|
||||
{
|
||||
s_log.Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
FreeSR.Shared/Command/Context/ICommandContext.cs
Normal file
8
FreeSR.Shared/Command/Context/ICommandContext.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FreeSR.Shared.Command.Context
|
||||
{
|
||||
public interface ICommandContext
|
||||
{
|
||||
void SendMessage(string message);
|
||||
void SendError(string message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class CommandParameterConverterAttribute : Attribute
|
||||
{
|
||||
public Type Type { get; }
|
||||
|
||||
public CommandParameterConverterAttribute(Type type)
|
||||
{
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
public interface ICommandParameterConverter
|
||||
{
|
||||
bool TryConvert(string parameter, out object result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[CommandParameterConverter(typeof(string))]
|
||||
public class StringCommandParameterConverter : ICommandParameterConverter
|
||||
{
|
||||
public bool TryConvert(string parameter, out object result)
|
||||
{
|
||||
result = parameter;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace FreeSR.Shared.Command.Convert
|
||||
{
|
||||
[CommandParameterConverter(typeof(uint))]
|
||||
public class UIntCommandParameterConverter : ICommandParameterConverter
|
||||
{
|
||||
public bool TryConvert(string parameter, out object result)
|
||||
{
|
||||
result = null;
|
||||
if (!uint.TryParse(parameter, out uint parseResult))
|
||||
return false;
|
||||
|
||||
result = parseResult;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
FreeSR.Shared/Command/ICommandCategory.cs
Normal file
9
FreeSR.Shared/Command/ICommandCategory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FreeSR.Shared.Command
|
||||
{
|
||||
using FreeSR.Shared.Command.Context;
|
||||
|
||||
public interface ICommandCategory
|
||||
{
|
||||
CommandResult Invoke(ICommandContext context, string[] parameters, uint depth);
|
||||
}
|
||||
}
|
||||
25
FreeSR.Shared/Configuration/ConfigurationManager.cs
Normal file
25
FreeSR.Shared/Configuration/ConfigurationManager.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace FreeSR.Shared.Configuration
|
||||
{
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
public sealed class ConfigurationManager<T> : Singleton<ConfigurationManager<T>> where T : class
|
||||
{
|
||||
public IConfiguration Config { get; private set; }
|
||||
public T Model { get; private set; }
|
||||
|
||||
public void Initialize(string path)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.AddJsonFile(path, false, true)
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
Config = builder.Build();
|
||||
Model = Config.Get<T>();
|
||||
|
||||
ChangeToken.OnChange(
|
||||
Config.GetReloadToken,
|
||||
() => Model = Config.Get<T>());
|
||||
}
|
||||
}
|
||||
}
|
||||
8
FreeSR.Shared/Configuration/NetworkConfiguration.cs
Normal file
8
FreeSR.Shared/Configuration/NetworkConfiguration.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FreeSR.Shared.Configuration
|
||||
{
|
||||
public class NetworkConfiguration
|
||||
{
|
||||
public string Host { get; set; }
|
||||
public ushort Port { get; set; }
|
||||
}
|
||||
}
|
||||
10
FreeSR.Shared/Exceptions/ServerInitializationException.cs
Normal file
10
FreeSR.Shared/Exceptions/ServerInitializationException.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared.Exceptions
|
||||
{
|
||||
public class ServerInitializationException : Exception
|
||||
{
|
||||
public ServerInitializationException(string message) : base(message)
|
||||
{
|
||||
// ServerInitializationException.
|
||||
}
|
||||
}
|
||||
}
|
||||
24
FreeSR.Shared/FreeSR.Shared.csproj
Normal file
24
FreeSR.Shared/FreeSR.Shared.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="nlog.config">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
FreeSR.Shared/Singleton.cs
Normal file
10
FreeSR.Shared/Singleton.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace FreeSR.Shared
|
||||
{
|
||||
public abstract class Singleton<T> where T : class
|
||||
{
|
||||
private static readonly Lazy<T> instance = new Lazy<T>(() =>
|
||||
Activator.CreateInstance(typeof(T), true) as T);
|
||||
|
||||
public static T Instance => instance.Value;
|
||||
}
|
||||
}
|
||||
29
FreeSR.Shared/nlog.config
Normal file
29
FreeSR.Shared/nlog.config
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
|
||||
<targets>
|
||||
<target name="coloredConsole" xsi:type="ColoredConsole" useDefaultRowHighlightingRules="false"
|
||||
layout="${date:format=HH\:mm\:ss} [${level:uppercase=true}] ${logger:shortName=true}: ${message}" >
|
||||
<highlight-row condition="level == LogLevel.Debug" foregroundColor="DarkGray" />
|
||||
<highlight-row condition="level == LogLevel.Info" foregroundColor="Blue" />
|
||||
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" />
|
||||
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" />
|
||||
<highlight-row condition="level == LogLevel.Fatal" foregroundColor="Red" backgroundColor="White" />
|
||||
</target>
|
||||
|
||||
<target name="infoFile" xsi:type="File"
|
||||
layout="${longdate} ${pad:padding=5:inner=${level:uppercase=true}} ${logger} ${message}"
|
||||
fileName="${basedir}/logs/info.log" keepFileOpen="false" encoding="iso-8859-2" />
|
||||
<target name="errorFile" xsi:type="File"
|
||||
layout="${longdate} ${pad:padding=5:inner=${level:uppercase=true}} ${logger} ${message}"
|
||||
fileName="${basedir}/logs/error.log" keepFileOpen="false" encoding="iso-8859-2" />
|
||||
</targets>
|
||||
|
||||
<rules>
|
||||
<logger name="*" minlevel="Debug" writeTo="coloredConsole" />
|
||||
|
||||
<logger name="*" minlevel="Debug" maxlevel="Info" writeTo="infoFile" />
|
||||
<logger name="*" minlevel="Warn" maxlevel="Fatal" writeTo="errorFile" />
|
||||
</rules>
|
||||
</nlog>
|
||||
Reference in New Issue
Block a user