Implemented basic plugin store and improved plugin system

This commit is contained in:
Marcel Baumgartner
2023-07-23 21:30:57 +02:00
parent 21bea974a9
commit 0658e55a78
14 changed files with 288 additions and 15 deletions

View File

@@ -0,0 +1,6 @@
namespace Moonlight.App.Models.Misc;
public class OfficialMoonlightPlugin
{
public string Name { get; set; }
}

View File

@@ -15,6 +15,13 @@ public static class Permissions
Name = "Admin Statistics",
Description = "View statistical information about the moonlight instance"
};
public static Permission AdminSysPlugins = new()
{
Index = 2,
Name = "Admin system plugins",
Description = "View and install plugins"
};
public static Permission AdminDomains = new()
{

View File

@@ -1,5 +1,4 @@
using Moonlight.App.Plugin.UI;
using Moonlight.App.Plugin.UI.Servers;
using Moonlight.App.Plugin.UI.Servers;
using Moonlight.App.Plugin.UI.Webspaces;
namespace Moonlight.App.Plugin;
@@ -12,4 +11,5 @@ public abstract class MoonlightPlugin
public Func<ServerPageContext, Task>? OnBuildServerPage { get; set; }
public Func<WebspacePageContext, Task>? OnBuildWebspacePage { get; set; }
public Func<IServiceCollection, Task>? OnBuildServices { get; set; }
}

View File

@@ -8,4 +8,5 @@ public class ServerPageContext
public List<ServerSetting> Settings { get; set; } = new();
public Server Server { get; set; }
public User User { get; set; }
public string[] ImageTags { get; set; }
}

View File

@@ -16,6 +16,7 @@ public class StorageService
Directory.CreateDirectory(PathBuilder.Dir("storage", "resources"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "backups"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
{

View File

@@ -46,7 +46,7 @@ public class MoonlightService
try
{
var client = new GitHubClient(new ProductHeaderValue("Moonlight"));
var client = new GitHubClient(new ProductHeaderValue("Moonlight-Panel"));
var pullRequests = await client.PullRequest.GetAllForRepository("Moonlight-Panel", "Moonlight", new PullRequestRequest
{

View File

@@ -1,25 +1,57 @@
using System.Reflection;
using Moonlight.App.Database.Entities;
using System.Runtime.Loader;
using Moonlight.App.Helpers;
using Moonlight.App.Plugin;
using Moonlight.App.Plugin.UI;
using Moonlight.App.Plugin.UI.Servers;
using Moonlight.App.Plugin.UI.Webspaces;
namespace Moonlight.App.Services;
namespace Moonlight.App.Services.Plugins;
public class PluginService
{
public List<MoonlightPlugin> Plugins { get; set; }
public List<MoonlightPlugin> Plugins { get; private set; }
public Dictionary<MoonlightPlugin, string> PluginFiles { get; private set; }
private AssemblyLoadContext LoadContext;
public PluginService()
{
LoadPlugins();
LoadContext = new(null, true);
ReloadPlugins().Wait();
}
private void LoadPlugins()
private Task UnloadPlugins()
{
Plugins = new();
PluginFiles = new();
if(LoadContext.Assemblies.Any())
LoadContext.Unload();
return Task.CompletedTask;
}
public async Task ReloadPlugins()
{
await UnloadPlugins();
// Try to update all plugins ending with .dll.cache
foreach (var pluginFile in Directory.EnumerateFiles(
PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins"))
.Where(x => x.EndsWith(".dll.cache")))
{
try
{
var realPath = pluginFile.Replace(".cache", "");
File.Copy(pluginFile, realPath, true);
File.Delete(pluginFile);
Logger.Info($"Updated plugin {realPath} on startup");
}
catch (Exception)
{
// ignored
}
}
var pluginType = typeof(MoonlightPlugin);
@@ -27,7 +59,7 @@ public class PluginService
PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins"))
.Where(x => x.EndsWith(".dll")))
{
var assembly = Assembly.LoadFile(pluginFile);
var assembly = LoadContext.LoadFromAssemblyPath(pluginFile);
foreach (var type in assembly.GetTypes())
{
@@ -38,6 +70,7 @@ public class PluginService
Logger.Info($"Loaded plugin '{plugin.Name}' ({plugin.Version}) by {plugin.Author}");
Plugins.Add(plugin);
PluginFiles.Add(plugin, pluginFile);
}
}
}
@@ -66,4 +99,13 @@ public class PluginService
return context;
}
public async Task BuildServices(IServiceCollection serviceCollection)
{
foreach (var plugin in Plugins)
{
if (plugin.OnBuildServices != null)
await plugin.OnBuildServices.Invoke(serviceCollection);
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Text;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Octokit;
namespace Moonlight.App.Services.Plugins;
public class PluginStoreService
{
private readonly GitHubClient Client;
private readonly PluginService PluginService;
public PluginStoreService(PluginService pluginService)
{
PluginService = pluginService;
Client = new(new ProductHeaderValue("Moonlight-Panel"));
}
public async Task<OfficialMoonlightPlugin[]> GetPlugins()
{
var items = await Client.Repository.Content.GetAllContents("Moonlight-Panel", "OfficialPlugins");
if (items == null)
{
Logger.Fatal("Unable to read plugin repo contents");
return Array.Empty<OfficialMoonlightPlugin>();
}
return items
.Where(x => x.Type == ContentType.Dir)
.Select(x => new OfficialMoonlightPlugin()
{
Name = x.Name
})
.ToArray();
}
public async Task<string> GetPluginReadme(OfficialMoonlightPlugin plugin)
{
var rawReadme = await Client.Repository.Content
.GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/README.md");
if (rawReadme == null)
return "Error";
return Encoding.UTF8.GetString(rawReadme);
}
public async Task InstallPlugin(OfficialMoonlightPlugin plugin, bool updating = false)
{
var rawPlugin = await Client.Repository.Content
.GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/{plugin.Name}.dll");
if (updating)
{
await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll.cache"), rawPlugin);
return;
}
await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll"), rawPlugin);
await PluginService.ReloadPlugins();
}
}