From d92f99616987279dcdb2f59a7a64d10c179ab401 Mon Sep 17 00:00:00 2001 From: Masu Baumgartner <68913099+Masu-Baumgartner@users.noreply.github.com> Date: Sun, 10 Nov 2024 22:06:19 +0100 Subject: [PATCH] Started adding module service I will probably change the api paths and a lot of other stuff i wrote today tomorrow :| --- .../ClientPlugins/ClientPluginsController.cs | 37 ++++++++++ Moonlight.ApiServer/Models/ModuleModel.cs | 12 +++ Moonlight.ApiServer/Services/ModuleService.cs | 74 +++++++++++++++++++ Moonlight.ApiServer/Startup.cs | 60 +++++++++++---- .../ClientPlugins/ClientPluginsResponse.cs | 7 ++ 5 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 Moonlight.ApiServer/Http/Controllers/ClientPlugins/ClientPluginsController.cs create mode 100644 Moonlight.ApiServer/Models/ModuleModel.cs create mode 100644 Moonlight.ApiServer/Services/ModuleService.cs create mode 100644 Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs diff --git a/Moonlight.ApiServer/Http/Controllers/ClientPlugins/ClientPluginsController.cs b/Moonlight.ApiServer/Http/Controllers/ClientPlugins/ClientPluginsController.cs new file mode 100644 index 00000000..76223904 --- /dev/null +++ b/Moonlight.ApiServer/Http/Controllers/ClientPlugins/ClientPluginsController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Mvc; +using Moonlight.ApiServer.Services; +using Moonlight.Shared.Http.Responses.ClientPlugins; + +namespace Moonlight.ApiServer.Http.Controllers.ClientPlugins; + +[ApiController] +[Route("api/clientPlugins")] +public class ClientPluginsController : Controller +{ + private readonly ModuleService ModuleService; + + public ClientPluginsController(ModuleService moduleService) + { + ModuleService = moduleService; + } + + [HttpGet] + public async Task Get() + { + var dlls = ModuleService.Modules + .Where(x => x.Modules.ContainsKey("client")) + .SelectMany(x => + x.Modules + .FirstOrDefault(c => c.Key == "client") + .Value + .Select(y => x.Name + "." + y) + ) + .ToArray(); + + return new() + { + CacheKey = "unset", + Dlls = dlls + }; + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/ModuleModel.cs b/Moonlight.ApiServer/Models/ModuleModel.cs new file mode 100644 index 00000000..c4e3fde2 --- /dev/null +++ b/Moonlight.ApiServer/Models/ModuleModel.cs @@ -0,0 +1,12 @@ +namespace Moonlight.ApiServer.Models; + +public class ModuleModel +{ + public string Name { get; set; } + public string Author { get; set; } + public string Version { get; set; } + public string? DonateUrl { get; set; } + public string? UpdateUrl { get; set; } + + public Dictionary> Modules { get; set; } = new(); +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/ModuleService.cs b/Moonlight.ApiServer/Services/ModuleService.cs new file mode 100644 index 00000000..4a95a0fa --- /dev/null +++ b/Moonlight.ApiServer/Services/ModuleService.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using MoonCore.Helpers; +using Moonlight.ApiServer.Models; + +namespace Moonlight.ApiServer.Services; + +public class ModuleService +{ + private readonly ILogger Logger; + private readonly Dictionary ModuleMeta = new(); + private static string ModulePath = PathBuilder.Dir("storage", "modules"); + + public ModuleModel[] Modules => ModuleMeta.Values.ToArray(); + + public ModuleService(ILogger logger) + { + Logger = logger; + } + + public void Load() + { + Logger.LogInformation("Loading modules"); + + Directory.CreateDirectory(ModulePath); + + foreach (var moduleDirectory in Directory.EnumerateDirectories(ModulePath)) + { + var metaPath = PathBuilder.File(moduleDirectory, "meta.json"); + + if (!File.Exists(metaPath)) + { + Logger.LogWarning("No meta.json found in {folder}", moduleDirectory); + continue; + } + + ModuleModel moduleModel; + + try + { + var json = File.ReadAllText(metaPath); + moduleModel = JsonSerializer.Deserialize(json)!; + } + catch (Exception e) + { + Logger.LogError("An error occured while loading meta.json in {folder}: {e}", moduleDirectory, e); + continue; + } + + ModuleMeta.Add(moduleDirectory, moduleModel); + } + + Logger.LogInformation("Loaded {count} modules", ModuleMeta.Count); + } + + public string[] GetModuleDlls(string moduleName, string section) + { + if (ModuleMeta.All(x => x.Value.Name != moduleName)) + throw new ArgumentException($"No module with the name '{moduleName}' found"); + + var moduleKvp = ModuleMeta + .FirstOrDefault(x => x.Value.Name == moduleName); + + var module = moduleKvp.Value; + + if (!module.Modules.ContainsKey(section)) + return []; + + var modulePaths = module.Modules[section].Select( + dllName => PathBuilder.File(ModulePath, moduleKvp.Key, "bin", section, dllName) + ).Where(File.Exists).ToArray(); + + return modulePaths; + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index f9188934..751157a6 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.Loader; using System.Text.Json; using MoonCore.Authentication; using MoonCore.Exceptions; @@ -16,6 +17,7 @@ using Moonlight.ApiServer.Http.Middleware; using Moonlight.ApiServer.Interfaces.Auth; using Moonlight.ApiServer.Interfaces.OAuth2; using Moonlight.ApiServer.Interfaces.Startup; +using Moonlight.ApiServer.Services; using Moonlight.Shared.Http.Responses.OAuth2; namespace Moonlight.ApiServer; @@ -25,7 +27,7 @@ public static class Startup public static async Task Main(string[] args) => await Run(args, []); - public static async Task Run(string[] args, Assembly[]? pluginAssemblies = null) + public static async Task Run(string[] args, Assembly[]? additionalAssemblies = null) { // Cry about it #pragma warning disable ASP0000 @@ -47,12 +49,10 @@ public static class Startup // Storage i guess Directory.CreateDirectory(PathBuilder.Dir("storage")); -// TODO: Load plugin/module assemblies - -// Configure startup logger + // Configure startup logger var startupLoggerFactory = new LoggerFactory(); -// TODO: Add direct extension method + // TODO: Add direct extension method var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => { configuration.Console.Enable = true; @@ -64,7 +64,35 @@ public static class Startup var startupLogger = startupLoggerFactory.CreateLogger("Startup"); -// Configure startup interfaces + // Load plugin/modules + var moduleService = new ModuleService(startupLoggerFactory.CreateLogger()); + moduleService.Load(); + + // Load api server module assemblies + var apiServerDlls = moduleService.Modules.SelectMany( + x => moduleService.GetModuleDlls(x.Name, "apiServer") + ); + + var apiServerModuleContext = new AssemblyLoadContext(null); + + foreach (var apiServerDll in apiServerDlls) + { + try + { + apiServerModuleContext.LoadFromStream(File.OpenRead( + apiServerDll + )); + } + catch (Exception e) + { + startupLogger.LogCritical("Unable to load dll {name} into context: {e}", apiServerDll, e); + throw; + } + } + + var moduleAssemblies = apiServerModuleContext.Assemblies.ToArray(); + + // Configure startup interfaces var startupServiceCollection = new ServiceCollection(); startupServiceCollection.AddConfiguration(options => @@ -87,10 +115,10 @@ public static class Startup // Configure assemblies to scan configuration.AddAssembly(typeof(Startup).Assembly); - if(pluginAssemblies != null) - configuration.AddAssemblies(pluginAssemblies); + if(additionalAssemblies != null) + configuration.AddAssemblies(additionalAssemblies); - //TODO: Load plugins from file + configuration.AddAssemblies(moduleAssemblies); }); @@ -128,7 +156,13 @@ public static class Startup } } - builder.Services.AddControllers(); + var controllerBuilder = builder.Services.AddControllers(); + + // Add current assemblies to the application part + foreach (var moduleAssembly in moduleAssemblies) + controllerBuilder.AddApplicationPart(moduleAssembly); + + builder.Services.AddSingleton(moduleService); builder.Services.AddSingleton(config); builder.Services.AutoAddServices(typeof(Startup).Assembly); builder.Services.AddHttpClient(); @@ -144,10 +178,10 @@ public static class Startup configuration.AddAssembly(typeof(Startup).Assembly); - if(pluginAssemblies != null) - configuration.AddAssemblies(pluginAssemblies); + if(additionalAssemblies != null) + configuration.AddAssemblies(additionalAssemblies); - //TODO: Load plugins from file + configuration.AddAssemblies(moduleAssemblies); }); var app = builder.Build(); diff --git a/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs b/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs new file mode 100644 index 00000000..767df61c --- /dev/null +++ b/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs @@ -0,0 +1,7 @@ +namespace Moonlight.Shared.Http.Responses.ClientPlugins; + +public class ClientPluginsResponse +{ + public string[] Dlls { get; set; } + public string CacheKey { get; set; } +} \ No newline at end of file