almost done

This commit is contained in:
zznty
2022-10-03 21:49:31 +07:00
parent f001aa4a7e
commit 5e508600f9
19 changed files with 435 additions and 145 deletions

View File

@@ -1,10 +1,10 @@
using System.Reflection;
using System.Collections;
using System.Reflection;
using System.Text.Json;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
using Humanizer;
using Torch.Views;
using TorchRemote.Models.Responses;
using TorchRemote.Models.Shared.Settings;
using TorchRemote.Plugin.Abstractions.Controllers;
@@ -17,27 +17,19 @@ public class SettingsController : WebApiController, ISettingsController
{
private const string RootPath = "/settings";
[Route(HttpVerbs.Get, RootPath)]
public IEnumerable<string> GetAllSettings()
{
return Statics.SettingManager.Settings.Keys;
}
[Route(HttpVerbs.Get, $"{RootPath}/{{fullName}}")]
public SettingInfoResponse Get(string fullName)
{
if (!Statics.SettingManager.Settings.TryGetValue(fullName, out var setting))
throw HttpException.NotFound($"Setting with fullName {fullName} not found", fullName);
return new(StringExtensions.Humanize(setting.Name), setting.Schema, setting
.Type.GetProperties(
BindingFlags.Public | BindingFlags.Instance)
.Where(b => setting.IncludeDisplayOnly &&
b.HasAttribute<DisplayAttribute>())
.Select(b =>
{
var attr = b.GetCustomAttribute<DisplayAttribute>();
return new SettingPropertyInfo(
attr?.Name ?? StringExtensions.Humanize(b.Name),
Statics.SerializerOptions
.PropertyNamingPolicy!.ConvertName(b.Name),
attr?.Description,
attr?.Order is 0 or null ? null : attr.Order);
}).ToArray());
return new(StringExtensions.Humanize(setting.Name), setting.Schema);
}
[Route(HttpVerbs.Get, $"{RootPath}/{{fullName}}/values")]
@@ -59,9 +51,12 @@ public class SettingsController : WebApiController, ISettingsController
return type switch
{
_ when type == typeof(int) || type == typeof(int?) => (PropertyBase)new IntegerProperty(name, (int?)value),
_ when type == typeof(int) || type == typeof(int?) => (PropertyBase)new IntegerProperty(
name, (int?)value),
_ when type == typeof(bool) || type == typeof(bool?) => new BooleanProperty(name, (bool?)value),
_ when type == typeof(string) => new StringProperty(name, (string?)value),
_ when type.IsPrimitive => new NumberProperty(name, value is null ? null : (double?)Convert.ChangeType(value, typeof(double))),
_ when type.IsPrimitive => new NumberProperty(
name, value is null ? null : (double?)Convert.ChangeType(value, typeof(double))),
_ when type == typeof(DateTime) || type == typeof(DateTime?) => new DateTimeProperty(
name, (DateTime?)value),
_ when type == typeof(TimeSpan) || type == typeof(TimeSpan?) => new DurationProperty(
@@ -71,6 +66,8 @@ public class SettingsController : WebApiController, ISettingsController
_ when type == typeof(Guid) || type == typeof(Guid?) => new UuidProperty(
name, (Guid?)value),
_ when type == typeof(Uri) => new UriProperty(name, (Uri?)value),
_ when typeof(ICollection).IsAssignableFrom(type) =>
new ArrayProperty(name, JsonSerializer.SerializeToElement(value, type, Statics.SerializerOptions)),
_ when type.IsClass => new ObjectProperty(
name, JsonSerializer.SerializeToElement(value, type, Statics.SerializerOptions)),
_ => throw HttpException.NotFound("Property type not found", name),
@@ -147,6 +144,9 @@ public class SettingsController : WebApiController, ISettingsController
case ObjectProperty objectProperty when type.IsClass:
propInfo.SetValue(instance, objectProperty.Value.Deserialize(type, Statics.SerializerOptions));
break;
case ArrayProperty arrayProperty when typeof(ICollection).IsAssignableFrom(type):
propInfo.SetValue(instance, arrayProperty.Value.Deserialize(type, Statics.SerializerOptions));
break;
case StringProperty stringProperty when type == typeof(string):
propInfo.SetValue(instance, stringProperty.Value);
break;
@@ -168,4 +168,4 @@ public class SettingsController : WebApiController, ISettingsController
return true;
}).Count();
}
}
}

View File

@@ -51,17 +51,17 @@ public class ApiServerManager : Manager
Statics.ChatModule = chatModule;
_server = new WebServer(o => o
.WithUrlPrefix(_config.Listener.UrlPrefix)
.WithMicrosoftHttpListener())
.WithLocalSessionManager()
.WithModule(apiModule
.WithController<ServerController>()
.WithController<SettingsController>()
.WithController<WorldsController>()
.WithController<ChatController>())
.WithModule(new LogsModule("/api/live/logs", true))
.WithModule(chatModule)
.WithBearerToken("/api", new SymmetricSecurityKey(Convert.FromBase64String(_config.SecurityKey)), new BasicAuthorizationServerProvider());
.WithUrlPrefix(_config.Listener.UrlPrefix)
.WithMicrosoftHttpListener())
.WithLocalSessionManager()
.WithModule(apiModule
.WithController<ServerController>()
.WithController<SettingsController>()
.WithController<WorldsController>()
.WithController<ChatController>())
.WithModule(new LogsModule("/api/live/logs", true))
.WithModule(chatModule)
.WithBearerToken("/api", new SymmetricSecurityKey(Convert.FromBase64String(_config.SecurityKey)), new BasicAuthorizationServerProvider());
}
public override void Attach()
@@ -92,4 +92,4 @@ public class ApiServerManager : Manager
return Convert.ToBase64String(aes.Key);
}
}
}

View File

@@ -1,12 +1,16 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Text.Json;
using Json.Schema;
using Json.Schema.Generation;
using NLog;
using Torch;
using Torch.API;
using Torch.Managers;
using Torch.Server;
using Torch.Server.Managers;
using Torch.Server.ViewModels;
using TorchRemote.Plugin.Refiners;
using TorchRemote.Plugin.Utils;
namespace TorchRemote.Plugin.Managers;
@@ -17,6 +21,8 @@ public class SettingManager : Manager
[Dependency]
private readonly InstanceManager _instanceManager = null!;
[Dependency]
private readonly PluginManager _pluginManager = null!;
public SettingManager(ITorchBase torchInstance) : base(torchInstance)
{
@@ -26,25 +32,66 @@ public class SettingManager : Manager
{
base.Attach();
_instanceManager.InstanceLoaded += InstanceManagerOnInstanceLoaded;
RegisterSetting("Torch Config", Torch.Config, typeof(TorchConfig));
foreach (var plugin in _pluginManager.Plugins.Values)
{
var type = plugin.GetType();
object persistentInstance;
const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
bool IsSuitable(MemberInfo m, Type t) =>
t.IsGenericType && typeof(Persistent<>).IsAssignableFrom(t.GetGenericTypeDefinition()) &&
(m.Name.Contains(
"config", StringComparison.InvariantCultureIgnoreCase) ||
m.Name.Contains(
"cfg", StringComparison.InvariantCultureIgnoreCase));
var fields = type.GetFields(flags).Where(b => IsSuitable(b, b.FieldType)).ToArray();
var props = type.GetProperties(flags).Where(b => IsSuitable(b, b.PropertyType)).ToArray();
if (fields.FirstOrDefault() is { } field)
{
persistentInstance = field.GetValue(plugin);
}
else if (props.FirstOrDefault() is { } prop)
{
persistentInstance = prop.GetValue(plugin);
}
else
{
continue;
}
if (persistentInstance is null)
continue;
var persistentType = persistentInstance.GetType();
var getter = persistentType.GetProperty("Data")!;
RegisterSetting(plugin.Name, getter.GetValue(persistentInstance), persistentType.GenericTypeArguments[0]);
}
}
private void InstanceManagerOnInstanceLoaded(ConfigDedicatedViewModel config)
{
RegisterSetting(config.SessionSettings, typeof(SessionSettingsViewModel));
RegisterSetting("Session Settings", config.SessionSettings, typeof(SessionSettingsViewModel));
}
public void RegisterSetting(object value, Type type, bool includeOnlyDisplay = true)
public void RegisterSetting(string name, object value, Type type)
{
var builder = new JsonSchemaBuilder().FromType(type, new()
{
PropertyNamingMethod = input => Statics.SerializerOptions.PropertyNamingPolicy!.ConvertName(input)
PropertyNamingMethod = input => Statics.SerializerOptions.PropertyNamingPolicy!.ConvertName(input),
Refiners = { new DisplayAttributeRefiner() }
});
Settings[type.FullName!] = new(type.Name, type, builder.Build(), value, includeOnlyDisplay);
Log.Info("Registered {0} type", type.FullName);
Settings[type.FullName!] = new(name, type, builder.Build(), value);
Log.Info("Registered {0} type with name {1}", type.FullName, name);
}
public IDictionary<string, Setting> Settings { get; } = new ConcurrentDictionary<string, Setting>();
}
public record Setting(string Name, Type Type, JsonSchema Schema, object Value, bool IncludeDisplayOnly);
public record Setting(string Name, Type Type, JsonSchema Schema, object Value);

View File

@@ -0,0 +1,28 @@
using Json.Schema.Generation;
using Json.Schema.Generation.Intents;
using Torch.Views;
namespace TorchRemote.Plugin.Refiners;
public class DisplayAttributeRefiner : ISchemaRefiner
{
public bool ShouldRun(SchemaGenerationContextBase context)
{
return context.GetAttributes().OfType<DisplayAttribute>().Any();
}
public void Run(SchemaGenerationContextBase context)
{
foreach (var displayAttribute in context.GetAttributes().OfType<DisplayAttribute>())
{
if (!string.IsNullOrEmpty(displayAttribute.Name))
context.Intents.Add(new TitleIntent(displayAttribute.Name));
if (!string.IsNullOrEmpty(displayAttribute.Description))
context.Intents.Add(new DescriptionIntent(displayAttribute.Description));
if (displayAttribute.ReadOnly)
context.Intents.Add(new ReadOnlyIntent(displayAttribute.ReadOnly));
}
}
}

View File

@@ -75,7 +75,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="EmbedIO" Version="3.5.0" />
<PackageReference Include="EmbedIO" Version="3.4.3" />
<PackageReference Include="EmbedIO.BearerToken" Version="3.4.2" />
<PackageReference Include="Json.More.Net" Version="1.7.1" />
<PackageReference Include="JsonPointer.Net" Version="2.2.2" />

View File

@@ -1 +0,0 @@
global using JsonData = TorchRemote.Plugin.Utils.JsonDataAttribute;

View File

@@ -2,5 +2,5 @@
<PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>TorchRemote.Plugin</Name>
<Guid>284017F3-9682-4841-A544-EB04DB8CB9BA</Guid>
<Version>v1.0.1</Version>
<Version>v1.0.2</Version>
</PluginManifest>