From 310cca169ab98609abb0c171c909cd18f2478873 Mon Sep 17 00:00:00 2001 From: OlliMartin Date: Wed, 29 Jan 2025 17:26:51 +0100 Subject: [PATCH 1/3] Add support for extension assemblies; NO SECURITY IMPLEMENTED YET --- .../MockedFlowExecutorApiFixture.cs | 4 +- Oma.WndwCtrl.Abstractions/Errors/FlowError.cs | 2 +- Oma.WndwCtrl.Api/CtrlApiProgram.cs | 6 ++- Oma.WndwCtrl.Api/CtrlApiService.cs | 5 ++- .../ConfigurationService.cs | 4 ++ .../component-configuration-windows.json | 3 +- .../IServiceCollectionExtensions.cs | 40 +++++++++++++++++++ .../Extensions/JsonExtensions.cs | 16 +++++++- .../Model/Settings/AssemblyInfo2.cs | 8 ++++ .../Model/Settings/ExtensionSettings.cs | 14 +++++++ .../BackgroundServiceWrapper.cs | 10 +++++ Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs | 33 ++++++++++++--- .../Commands/MediaCommandExecutor.cs | 21 ++++++++++ .../Model/Commands/MediaCommand.cs | 12 ++++++ .../Oma.WndwCtrl.Ext.Windows.Media.csproj | 13 ++++++ .../MetricsApiService.config.json | 7 ---- Oma.WndwCtrl.MetricsApi/MetricsApiService.cs | 5 ++- .../MgmtApiService.config.development.json | 5 --- .../MgmtApiService.config.json | 34 ++++++++++++++-- Oma.WndwCtrl.MgmtApi/MgmtApiService.cs | 4 +- Oma.WndwCtrl.MgmtApi/Program.cs | 2 +- Oma.WndwCtrl.sln | 12 ++++++ 22 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs create mode 100644 Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs create mode 100644 Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs create mode 100644 Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs create mode 100644 Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj diff --git a/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs b/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs index 6d26b77..8ae64f9 100644 --- a/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs +++ b/.integrationTests/Oma.WndwCtrl.Api.IntegrationTests/TestFramework/MockedFlowExecutorApiFixture.cs @@ -19,7 +19,9 @@ public sealed class MockedFlowExecutorApiFixture : WebApplicationFactory.ModifyJsonSerializerOptions(SystemTextJsonSerializerConfig.Options); + WebApplicationWrapper.ModifyJsonSerializerOptions( + SystemTextJsonSerializerConfig.Options + ); } public ValueTask InitializeAsync() => ValueTask.CompletedTask; diff --git a/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs b/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs index 23fb18b..840c2e4 100644 --- a/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs +++ b/Oma.WndwCtrl.Abstractions/Errors/FlowError.cs @@ -35,7 +35,7 @@ public FlowError(string message, bool isExceptional) : this(message, isException [System.Diagnostics.Contracts.Pure] public static FlowError NoCommandExecutorFound(ICommand command) => new( - $"No transformation executor found that handles transformation type {command.GetType().FullName}.", + $"No command executor found that handles transformation type {command.GetType().FullName}.", isExceptional: false ); diff --git a/Oma.WndwCtrl.Api/CtrlApiProgram.cs b/Oma.WndwCtrl.Api/CtrlApiProgram.cs index 4fdce18..f3291d4 100644 --- a/Oma.WndwCtrl.Api/CtrlApiProgram.cs +++ b/Oma.WndwCtrl.Api/CtrlApiProgram.cs @@ -12,9 +12,13 @@ public async static Task Main(string[] args) MessageBus = new NoOpMessageBus(), }; + IConfiguration configuration = new ConfigurationBuilder() + .Build(); + CtrlApiService apiService = new( await ComponentConfigurationAccessor.FromFileAsync(), - messageBusAccessor + messageBusAccessor, + configuration ); await apiService.StartAsync(CancellationToken.None, args); diff --git a/Oma.WndwCtrl.Api/CtrlApiService.cs b/Oma.WndwCtrl.Api/CtrlApiService.cs index 9568290..6c489a7 100644 --- a/Oma.WndwCtrl.Api/CtrlApiService.cs +++ b/Oma.WndwCtrl.Api/CtrlApiService.cs @@ -17,9 +17,10 @@ namespace Oma.WndwCtrl.Api; public class CtrlApiService( ComponentConfigurationAccessor configurationAccessor, - MessageBusAccessor messageBusAccessor + MessageBusAccessor messageBusAccessor, + IConfiguration rootConfiguration ) - : WebApplicationWrapper(messageBusAccessor) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { private readonly MessageBusAccessor _messageBusAccessor = messageBusAccessor; diff --git a/Oma.WndwCtrl.Configuration/ConfigurationService.cs b/Oma.WndwCtrl.Configuration/ConfigurationService.cs index 42c18c1..4e4794e 100644 --- a/Oma.WndwCtrl.Configuration/ConfigurationService.cs +++ b/Oma.WndwCtrl.Configuration/ConfigurationService.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.Configuration.Model; using Oma.WndwCtrl.CoreAsp; @@ -10,6 +11,9 @@ ComponentConfigurationAccessor componentConfigurationAccessor ) : BackgroundServiceWrapper(configuration) { + protected override IServiceCollection ConfigureServices(IServiceCollection services) => + base.ConfigureServices(services); + protected async override Task PreHostRunAsync(CancellationToken cancelToken = default) { await base.PreHostRunAsync(cancelToken); diff --git a/Oma.WndwCtrl.Configuration/component-configuration-windows.json b/Oma.WndwCtrl.Configuration/component-configuration-windows.json index 4e8c2e4..c7170f9 100644 --- a/Oma.WndwCtrl.Configuration/component-configuration-windows.json +++ b/Oma.WndwCtrl.Configuration/component-configuration-windows.json @@ -11,7 +11,7 @@ }, "oma-service-status": { "type": "sensor", - "active": false, + "active": true, "queryCommand": { "type": "cli", "fileName": "sc.exe", @@ -65,6 +65,7 @@ }, "ping-google": { "type": "sensor", + "active": false, "queryCommand": { "type": "cli", "fileName": "ping", diff --git a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs index c7b7159..69d4ac0 100644 --- a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.Abstractions; @@ -13,6 +14,7 @@ using Oma.WndwCtrl.Core.Logger; using Oma.WndwCtrl.Core.Metrics; using Oma.WndwCtrl.Core.Model; +using Oma.WndwCtrl.Core.Model.Settings; namespace Oma.WndwCtrl.Core.Extensions; @@ -24,6 +26,8 @@ public static IServiceCollection AddCommandExecutors( IConfiguration configuration ) { + services.AddOptions(); + IConfigurationSection coreConfig = configuration.GetSection("Core"); // TODO: Will cause problems when called multiple times. @@ -35,6 +39,13 @@ IConfiguration configuration coreConfig.GetSection(CliParserLoggerOptions.SectionName) ); + ExtensionSettings extensions = new(); + coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); + + List extensionAssemblies = extensions.GetAssemblies(); + JsonExtensions.AddAssembliesToLoad(extensionAssemblies.ToList()); + AddExtensionExecutors(services, extensionAssemblies); + services .AddSingleton() .AddSingleton() @@ -47,4 +58,33 @@ IConfiguration configuration return services; } + + private static IServiceCollection AddExtensionExecutors( + IServiceCollection services, + List extensionAssemblies + ) + { + foreach (Assembly assembly in extensionAssemblies) + { + AddAllFromAssembly(services, assembly); + AddAllFromAssembly(services, assembly); + } + + return services; + } + + public static IServiceCollection AddAllFromAssembly( + IServiceCollection services, + Assembly assembly, + ServiceLifetime serviceLifetime = ServiceLifetime.Scoped + ) + { + Type baseType = typeof(T); + + foreach (Type implType in assembly.GetTypes() + .Where(t => t is { IsAbstract: false, } && t.IsAssignableTo(baseType))) + services.Add(new ServiceDescriptor(baseType, implType, serviceLifetime)); + + return services; + } } \ No newline at end of file diff --git a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs index a4d6e55..b1fe28d 100644 --- a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs @@ -7,12 +7,24 @@ namespace Oma.WndwCtrl.Core.Extensions; public static class JsonExtensions { - private static readonly Assembly AssembliesToSearch = typeof(BaseTransformation).Assembly; + private static IEnumerable _assembliesToInspect = []; + + internal static void AddAssembliesToLoad(List assembliesToLoad) + { + if (assembliesToLoad.Count == 0) + { + return; + } + + _assembliesToInspect = _assembliesToInspect.Union(assembliesToLoad).Distinct(); + } public static Action GetPolymorphismModifierFor( Func typeToDiscriminatorTransform ) { + List assembliesToSearch = new(_assembliesToInspect) { typeof(BaseTransformation).Assembly, }; + return jsonTypeInfo => { Type baseType = typeof(T); @@ -29,7 +41,7 @@ Func typeToDiscriminatorTransform UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, }; - IEnumerable types = AssembliesToSearch.GetTypes() + IEnumerable types = assembliesToSearch.SelectMany(a => a.GetTypes()) .Where(t => t is { IsClass: true, IsAbstract: false, } && t.IsAssignableTo(baseType)); foreach (Type t in types) diff --git a/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs new file mode 100644 index 0000000..eed216a --- /dev/null +++ b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs @@ -0,0 +1,8 @@ +namespace Oma.WndwCtrl.Core.Model.Settings; + +public record AssemblyInfo2 +{ + public string AssemblyName { get; set; } + + public string AssemblyVersion { get; set; } +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs new file mode 100644 index 0000000..147115c --- /dev/null +++ b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs @@ -0,0 +1,14 @@ +using System.Reflection; + +namespace Oma.WndwCtrl.Core.Model.Settings; + +public class ExtensionSettings : List +{ + public const string SectionName = "Extensions"; + + public List GetAssemblies() + { + // TODO: Obvious security concerns.. + return this.Select(assemblyInfo => Assembly.LoadFrom($"{assemblyInfo.AssemblyName}")).ToList(); + } +} \ No newline at end of file diff --git a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs index f5b759b..d53212c 100644 --- a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs @@ -16,11 +16,20 @@ public class BackgroundServiceWrapper(IConfiguration config protected readonly string RunningInOs = configuration.GetValue("ACaaD:OS") ?? "windows"; + private IConfiguration _configuration; + protected static IServiceProvider ServiceProvider => _serviceProvider ?? throw new InvalidOperationException( "The WebApplicationWrapper has not been initialized properly." ); + protected IConfiguration Configuration + { + get => _configuration ?? + throw new InvalidOperationException($"{nameof(Configuration)} is not populated."); + private set => _configuration = value; + } + [PublicAPI] protected IHost? Host { get; private set; } @@ -37,6 +46,7 @@ public async Task StartAsync(CancellationToken cancelToken = default, params str Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); #endif + Configuration = configuration; HostBuilder hostBuilder = new(); hostBuilder.ConfigureHostConfiguration(builder => builder.AddConfiguration(configuration)); diff --git a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs index d07883a..e2ed2f6 100644 --- a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.FileProviders; using Oma.WndwCtrl.Abstractions; using Oma.WndwCtrl.Core.Extensions; +using Oma.WndwCtrl.Core.Model.Settings; using Oma.WndwCtrl.CoreAsp.Api.Filters; using Oma.WndwCtrl.CoreAsp.Conventions; using Oma.WndwCtrl.Messaging.Bus; @@ -14,7 +15,10 @@ namespace Oma.WndwCtrl.CoreAsp; -public class WebApplicationWrapper(MessageBusAccessor? messageBusAccessor) : IApiService +public class WebApplicationWrapper( + MessageBusAccessor? messageBusAccessor, + IConfiguration? rootConfiguration +) : IApiService where TAssemblyDescriptor : class, IApiService { [SuppressMessage( @@ -24,6 +28,8 @@ public class WebApplicationWrapper(MessageBusAccessor? mess )] private static IServiceProvider? _serviceProvider; + private static readonly string _serviceName = typeof(TAssemblyDescriptor).Name; + private IConfiguration? _configuration; private IWebHostEnvironment? _environment; @@ -74,8 +80,20 @@ public async Task StartAsync(CancellationToken cancelToken = default, params str Environment = builder.Environment; Configuration = builder.Configuration; + IConfiguration coreConfig = Configuration.GetSection("Core"); + ExtensionSettings extensions = new(); + coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); + ConfigurationConfiguration(builder.Configuration); + builder.WebHost.ConfigureKestrel( + (context, options) => + { + IConfiguration configToUse = context.Configuration.GetSection(_serviceName).GetSection("Kestrel"); + options.Configure(configToUse); + } + ); + IMvcCoreBuilder mvcBuilder = builder.Services .AddMvcCore( opts => @@ -154,7 +172,9 @@ public Task ForceStopAsync(CancellationToken cancelToken) => Application is not ? Application.StopAsync(cancelToken) : Task.CompletedTask; - public static void ModifyJsonSerializerOptions(JsonSerializerOptions jsonSerializerOptions) + public static void ModifyJsonSerializerOptions( + JsonSerializerOptions jsonSerializerOptions + ) { jsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver() .WithAddedModifier( @@ -179,7 +199,10 @@ protected virtual IConfigurationBuilder ConfigurationConfiguration( IConfigurationBuilder configurationBuilder ) { - string serviceName = typeof(TAssemblyDescriptor).Name; + if (rootConfiguration is not null) + { + configurationBuilder.AddConfiguration(rootConfiguration); + } IFileProvider standardFileProvider = configurationBuilder.GetFileProvider(); @@ -190,12 +213,12 @@ IConfigurationBuilder configurationBuilder configurationBuilder.SetFileProvider(compositeFileProvider); - configurationBuilder.AddJsonFile($"{serviceName}.config.json", optional: true, reloadOnChange: false); + configurationBuilder.AddJsonFile($"{_serviceName}.config.json", optional: true, reloadOnChange: false); if (Environment.IsDevelopment()) { configurationBuilder.AddJsonFile( - $"{serviceName}.config.development.json", + $"{_serviceName}.config.development.json", optional: true, reloadOnChange: false ); diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs new file mode 100644 index 0000000..ad7b946 --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs @@ -0,0 +1,21 @@ +using LanguageExt; +using Oma.WndwCtrl.Abstractions; +using Oma.WndwCtrl.Abstractions.Errors; +using Oma.WndwCtrl.Abstractions.Model; +using Oma.WndwCtrl.Ext.Windows.Media.Model.Commands; +using static LanguageExt.Prelude; + +namespace Oma.WndwCtrl.Ext.Windows.Media.Executors.Commands; + +public class MediaCommandExecutor : ICommandExecutor +{ + public async Task> ExecuteAsync( + MediaCommand command, + CancellationToken cancelToken = default + ) => Right( + new CommandOutcome() + { + Success = true, + } + ); +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs new file mode 100644 index 0000000..df9ea9d --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; +using Oma.WndwCtrl.Core.Model.Commands; + +namespace Oma.WndwCtrl.Ext.Windows.Media.Model.Commands; + +[PublicAPI] +public class MediaCommand : BaseCommand +{ + public override string Category => "Media"; + + public string Action { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj new file mode 100644 index 0000000..e21e89f --- /dev/null +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json b/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json index 196353d..b927333 100644 --- a/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json +++ b/Oma.WndwCtrl.MetricsApi/MetricsApiService.config.json @@ -3,12 +3,5 @@ "LogLevel": { "Default": "Warning" } - }, - "Kestrel": { - "Endpoints": { - "MetricsApi": { - "Url": "http://192.168.178.22:19090" - } - } } } \ No newline at end of file diff --git a/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs b/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs index 200d6d6..6b57cb8 100644 --- a/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs +++ b/Oma.WndwCtrl.MetricsApi/MetricsApiService.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.CoreAsp; using Oma.WndwCtrl.Messaging.Bus; @@ -8,8 +9,8 @@ namespace Oma.WndwCtrl.MetricsApi; -public class MetricsApiService(MessageBusAccessor messageBusAccessor) - : WebApplicationWrapper(messageBusAccessor) +public class MetricsApiService(MessageBusAccessor messageBusAccessor, IConfiguration rootConfiguration) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { protected override IServiceCollection ConfigureServices(IServiceCollection services) { diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json index eed5140..884a904 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.development.json @@ -6,11 +6,6 @@ "Oma.WndwCtrl.Scheduling": "Information" } }, - "SchedulingService": { - "Enabled": true, - "Active": true, - "CheckInterval": "00:00:00.001" - }, "Core": { "CliParser": { "Silent": true diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json index 035168a..28cb695 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json @@ -15,14 +15,40 @@ "Enabled": false }, "Core": { + "Extensions": [ + { + "AssemblyName": "Z:\\git\\wndw.ctl\\Oma.WndwCtrl.Extensions\\Windows\\Oma.WndwCtrl.Ext.Windows.Media\\bin\\Debug\\net9.0\\Oma.WndwCtrl.Ext.Windows.Media.dll", + "AssemblyVersion": "1.0.0.0" + } + ], "CliParser": { "Silent": true } }, - "Kestrel": { - "Endpoints": { - "ManagementApi": { - "Url": "http://localhost:8443" + "MgmtApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:8443" + } + } + } + }, + "CtrlApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5000" + } + } + } + }, + "MetricsApiService": { + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:19090" + } } } } diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs b/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs index 7dec9aa..e49c5fa 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.cs @@ -11,8 +11,8 @@ namespace Oma.WndwCtrl.MgmtApi; -public class MgmtApiService(MessageBusAccessor? messageBusAccessor) - : WebApplicationWrapper(messageBusAccessor) +public class MgmtApiService(MessageBusAccessor? messageBusAccessor, IConfiguration? rootConfiguration) + : WebApplicationWrapper(messageBusAccessor, rootConfiguration) { protected override IServiceCollection ConfigureServices(IServiceCollection services) { diff --git a/Oma.WndwCtrl.MgmtApi/Program.cs b/Oma.WndwCtrl.MgmtApi/Program.cs index 011c1da..db41b56 100644 --- a/Oma.WndwCtrl.MgmtApi/Program.cs +++ b/Oma.WndwCtrl.MgmtApi/Program.cs @@ -1,5 +1,5 @@ using Oma.WndwCtrl.MgmtApi; -MgmtApiService apiService = new(null); +MgmtApiService apiService = new(messageBusAccessor: null, rootConfiguration: null); await apiService.StartAsync(); await apiService.WaitForShutdownAsync(); \ No newline at end of file diff --git a/Oma.WndwCtrl.sln b/Oma.WndwCtrl.sln index 8cc5f5a..084c554 100644 --- a/Oma.WndwCtrl.sln +++ b/Oma.WndwCtrl.sln @@ -66,6 +66,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installation", "Installatio Documentation\Installation\Linux.md = Documentation\Installation\Linux.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Oma.WndwCtrl.Extensions", "Oma.WndwCtrl.Extensions", "{9AE4447A-201F-4DCF-8715-803610028379}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{4A56D8FD-7965-4FE2-B989-B72A3CB14FB6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oma.WndwCtrl.Ext.Windows.Media", "Oma.WndwCtrl.Extensions\Windows\Oma.WndwCtrl.Ext.Windows.Media\Oma.WndwCtrl.Ext.Windows.Media.csproj", "{418214AE-8597-4FAB-A511-C4194AD96E4F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +146,10 @@ Global {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU {59CE4E31-306C-48EC-AD4D-EBB8F9000FFD}.Release|Any CPU.Build.0 = Release|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {418214AE-8597-4FAB-A511-C4194AD96E4F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {E7217C2E-5C78-4106-977D-3091CFA63F43} = {307F90ED-24AD-4725-960B-0A7AAFFE8C6D} @@ -152,5 +162,7 @@ Global {94B42F55-3A25-4022-B02B-28F4F945CB00} = {FC063003-91F3-415C-AB68-D1C08337FAC2} {C0FC9E76-5113-4E14-8061-4A9EE8CA1507} = {FC063003-91F3-415C-AB68-D1C08337FAC2} {41D1E3C4-0AC3-4245-BB9C-68F8D13481ED} = {94B42F55-3A25-4022-B02B-28F4F945CB00} + {4A56D8FD-7965-4FE2-B989-B72A3CB14FB6} = {9AE4447A-201F-4DCF-8715-803610028379} + {418214AE-8597-4FAB-A511-C4194AD96E4F} = {4A56D8FD-7965-4FE2-B989-B72A3CB14FB6} EndGlobalSection EndGlobal From 8c1c373070a479cd11d76e8834e28ffa01484176 Mon Sep 17 00:00:00 2001 From: OlliMartin Date: Wed, 29 Jan 2025 17:53:39 +0100 Subject: [PATCH 2/3] Fix issues --- .../ConfigurationService.cs | 4 -- .../IServiceCollectionExtensions.cs | 8 +-- .../Extensions/JsonExtensions.cs | 2 +- .../Model/Settings/AssemblyInfo2.cs | 8 +-- .../Model/Settings/ExtensionSettings.cs | 2 +- .../BackgroundServiceWrapper.cs | 2 +- Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs | 2 +- .../Commands/MediaCommandExecutor.cs | 50 ++++++++++++++++--- .../Model/Commands/MediaCommand.cs | 13 ++++- .../Oma.WndwCtrl.Ext.Windows.Media.csproj | 1 + .../MgmtApiService.config.json | 3 +- Oma.WndwCtrl.sln.DotSettings | 1 + 12 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Oma.WndwCtrl.Configuration/ConfigurationService.cs b/Oma.WndwCtrl.Configuration/ConfigurationService.cs index 4e4794e..42c18c1 100644 --- a/Oma.WndwCtrl.Configuration/ConfigurationService.cs +++ b/Oma.WndwCtrl.Configuration/ConfigurationService.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.Configuration.Model; using Oma.WndwCtrl.CoreAsp; @@ -11,9 +10,6 @@ ComponentConfigurationAccessor componentConfigurationAccessor ) : BackgroundServiceWrapper(configuration) { - protected override IServiceCollection ConfigureServices(IServiceCollection services) => - base.ConfigureServices(services); - protected async override Task PreHostRunAsync(CancellationToken cancelToken = default) { await base.PreHostRunAsync(cancelToken); diff --git a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs index 69d4ac0..cf276fb 100644 --- a/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/IServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; +using JetBrains.Annotations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Oma.WndwCtrl.Abstractions; @@ -39,7 +40,7 @@ IConfiguration configuration coreConfig.GetSection(CliParserLoggerOptions.SectionName) ); - ExtensionSettings extensions = new(); + ExtensionSettings extensions = []; coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); List extensionAssemblies = extensions.GetAssemblies(); @@ -59,7 +60,7 @@ IConfiguration configuration return services; } - private static IServiceCollection AddExtensionExecutors( + private static void AddExtensionExecutors( IServiceCollection services, List extensionAssemblies ) @@ -69,10 +70,9 @@ List extensionAssemblies AddAllFromAssembly(services, assembly); AddAllFromAssembly(services, assembly); } - - return services; } + [PublicAPI] public static IServiceCollection AddAllFromAssembly( IServiceCollection services, Assembly assembly, diff --git a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs index b1fe28d..b804e36 100644 --- a/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs +++ b/Oma.WndwCtrl.Core/Extensions/JsonExtensions.cs @@ -23,7 +23,7 @@ public static Action GetPolymorphismModifierFor( Func typeToDiscriminatorTransform ) { - List assembliesToSearch = new(_assembliesToInspect) { typeof(BaseTransformation).Assembly, }; + List assembliesToSearch = [.._assembliesToInspect, typeof(BaseTransformation).Assembly,]; return jsonTypeInfo => { diff --git a/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs index eed216a..bb51297 100644 --- a/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs +++ b/Oma.WndwCtrl.Core/Model/Settings/AssemblyInfo2.cs @@ -1,8 +1,10 @@ +using JetBrains.Annotations; + namespace Oma.WndwCtrl.Core.Model.Settings; +[UsedImplicitly] +[PublicAPI] public record AssemblyInfo2 { - public string AssemblyName { get; set; } - - public string AssemblyVersion { get; set; } + public string AssemblyName { get; set; } = string.Empty; } \ No newline at end of file diff --git a/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs index 147115c..2d0cd6a 100644 --- a/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs +++ b/Oma.WndwCtrl.Core/Model/Settings/ExtensionSettings.cs @@ -8,7 +8,7 @@ public class ExtensionSettings : List public List GetAssemblies() { - // TODO: Obvious security concerns.. + // TODO: [Required for MVP] Obvious security concerns.. return this.Select(assemblyInfo => Assembly.LoadFrom($"{assemblyInfo.AssemblyName}")).ToList(); } } \ No newline at end of file diff --git a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs index d53212c..273d88c 100644 --- a/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/BackgroundServiceWrapper.cs @@ -16,7 +16,7 @@ public class BackgroundServiceWrapper(IConfiguration config protected readonly string RunningInOs = configuration.GetValue("ACaaD:OS") ?? "windows"; - private IConfiguration _configuration; + private IConfiguration? _configuration; protected static IServiceProvider ServiceProvider => _serviceProvider ?? throw new InvalidOperationException( diff --git a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs index e2ed2f6..ca324fb 100644 --- a/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs +++ b/Oma.WndwCtrl.CoreAsp/WebApplicationWrapper.cs @@ -81,7 +81,7 @@ public async Task StartAsync(CancellationToken cancelToken = default, params str Configuration = builder.Configuration; IConfiguration coreConfig = Configuration.GetSection("Core"); - ExtensionSettings extensions = new(); + ExtensionSettings extensions = []; coreConfig.GetSection(ExtensionSettings.SectionName).Bind(extensions); ConfigurationConfiguration(builder.Configuration); diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs index ad7b946..cf6f4be 100644 --- a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs @@ -1,3 +1,6 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using JetBrains.Annotations; using LanguageExt; using Oma.WndwCtrl.Abstractions; using Oma.WndwCtrl.Abstractions.Errors; @@ -7,15 +10,48 @@ namespace Oma.WndwCtrl.Ext.Windows.Media.Executors.Commands; -public class MediaCommandExecutor : ICommandExecutor +public partial class MediaCommandExecutor : ICommandExecutor { - public async Task> ExecuteAsync( + private const int KEYEVENTF_EXTENTEDKEY = 1; + + private const int VK_MEDIA_NEXT_TRACK = 0xB0; + private const int VK_MEDIA_PREV_TRACK = 0xB1; + private const int VK_MEDIA_STOP = 0xB2; + private const int VK_MEDIA_PLAY_PAUSE = 0xB3; + + [SuppressMessage( + "Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "Must be disposed by caller." + )] + [MustDisposeResource] + [SuppressMessage("ReSharper", "NotDisposedResource", Justification = "Method flagged as must-dispose.")] + public Task> ExecuteAsync( MediaCommand command, CancellationToken cancelToken = default - ) => Right( - new CommandOutcome() + ) + { + byte keyToSend = command.Action switch { - Success = true, - } - ); + MediaAction.Play or MediaAction.Pause => VK_MEDIA_PLAY_PAUSE, + MediaAction.Next => VK_MEDIA_NEXT_TRACK, + MediaAction.Previous => VK_MEDIA_PREV_TRACK, + MediaAction.Stop => VK_MEDIA_STOP, + var err => throw new InvalidOperationException($"Unhandled media action {err.ToString()}."), + }; + + keybd_event(keyToSend, scanCode: 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); + + return Task.FromResult>( + Right( + new CommandOutcome + { + Success = true, + } + ) + ); + } + + [LibraryImport("user32.dll")] + private static partial void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo); } \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs index df9ea9d..ad816cf 100644 --- a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Model/Commands/MediaCommand.cs @@ -1,12 +1,23 @@ +using System.Text.Json.Serialization; using JetBrains.Annotations; using Oma.WndwCtrl.Core.Model.Commands; namespace Oma.WndwCtrl.Ext.Windows.Media.Model.Commands; +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum MediaAction +{ + Play, + Pause, + Stop, + Next, + Previous, +} + [PublicAPI] public class MediaCommand : BaseCommand { public override string Category => "Media"; - public string Action { get; init; } = string.Empty; + public MediaAction Action { get; init; } } \ No newline at end of file diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj index e21e89f..aa306cc 100644 --- a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Oma.WndwCtrl.Ext.Windows.Media.csproj @@ -1,6 +1,7 @@  + true net9.0 enable enable diff --git a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json index 28cb695..5be049e 100644 --- a/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json +++ b/Oma.WndwCtrl.MgmtApi/MgmtApiService.config.json @@ -17,8 +17,7 @@ "Core": { "Extensions": [ { - "AssemblyName": "Z:\\git\\wndw.ctl\\Oma.WndwCtrl.Extensions\\Windows\\Oma.WndwCtrl.Ext.Windows.Media\\bin\\Debug\\net9.0\\Oma.WndwCtrl.Ext.Windows.Media.dll", - "AssemblyVersion": "1.0.0.0" + "AssemblyName": "Z:\\git\\wndw.ctl\\Oma.WndwCtrl.Extensions\\Windows\\Oma.WndwCtrl.Ext.Windows.Media\\bin\\Debug\\net9.0\\Oma.WndwCtrl.Ext.Windows.Media.dll" } ], "CliParser": { diff --git a/Oma.WndwCtrl.sln.DotSettings b/Oma.WndwCtrl.sln.DotSettings index 3aeed5b..d8bd1dc 100644 --- a/Oma.WndwCtrl.sln.DotSettings +++ b/Oma.WndwCtrl.sln.DotSettings @@ -117,5 +117,6 @@ True True True + True True True \ No newline at end of file From 7cfed0c416b0c6a04566bf15d43657fca39c06de Mon Sep 17 00:00:00 2001 From: OlliMartin Date: Wed, 29 Jan 2025 18:00:02 +0100 Subject: [PATCH 3/3] Fix code analysis finding --- .../Executors/Commands/MediaCommandExecutor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs index cf6f4be..c93b2e5 100644 --- a/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs +++ b/Oma.WndwCtrl.Extensions/Windows/Oma.WndwCtrl.Ext.Windows.Media/Executors/Commands/MediaCommandExecutor.cs @@ -10,6 +10,7 @@ namespace Oma.WndwCtrl.Ext.Windows.Media.Executors.Commands; +[UsedImplicitly] public partial class MediaCommandExecutor : ICommandExecutor { private const int KEYEVENTF_EXTENTEDKEY = 1;