Started adding asset api/auto import
This commit is contained in:
37
Moonlight.ApiServer/Helpers/PluginAssetFileProvider.cs
Normal file
37
Moonlight.ApiServer/Helpers/PluginAssetFileProvider.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.FileProviders.Physical;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Moonlight.ApiServer.Services;
|
||||
|
||||
namespace Moonlight.ApiServer.Helpers;
|
||||
|
||||
public class PluginAssetFileProvider : IFileProvider
|
||||
{
|
||||
private readonly PluginService PluginService;
|
||||
|
||||
public PluginAssetFileProvider(PluginService pluginService)
|
||||
{
|
||||
PluginService = pluginService;
|
||||
}
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
return NotFoundDirectoryContents.Singleton;
|
||||
}
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
if (!PluginService.AssetMap.TryGetValue(subpath, out var physicalPath))
|
||||
return new NotFoundFileInfo(subpath);
|
||||
|
||||
if (!File.Exists(physicalPath))
|
||||
return new NotFoundFileInfo(subpath);
|
||||
|
||||
return new PhysicalFileInfo(new FileInfo(physicalPath));
|
||||
}
|
||||
|
||||
public IChangeToken Watch(string filter)
|
||||
{
|
||||
return NullChangeToken.Singleton;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Caching.Memory;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Models;
|
||||
using Moonlight.ApiServer.Services;
|
||||
using Moonlight.Shared.Http.Responses.PluginsStream;
|
||||
|
||||
namespace Moonlight.ApiServer.Http.Controllers;
|
||||
|
||||
@@ -22,36 +23,13 @@ public class PluginsStreamController : Controller
|
||||
[HttpGet]
|
||||
public Task<HostedPluginsManifest> GetManifest()
|
||||
{
|
||||
var assembliesMap = Cache.GetOrCreate("clientPluginAssemblies", entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15);
|
||||
|
||||
return PluginService.GetAssemblies("client");
|
||||
})!;
|
||||
|
||||
var entrypoints = Cache.GetOrCreate("clientPluginEntrypoints", entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15);
|
||||
|
||||
return PluginService.GetEntrypoints("client");
|
||||
})!;
|
||||
|
||||
return Task.FromResult(new HostedPluginsManifest()
|
||||
{
|
||||
Assemblies = assembliesMap.Keys.ToArray(),
|
||||
Entrypoints = entrypoints
|
||||
});
|
||||
return Task.FromResult(PluginService.HostedPluginsManifest);
|
||||
}
|
||||
|
||||
[HttpGet("stream")]
|
||||
public async Task GetAssembly([FromQuery(Name = "assembly")] string assembly)
|
||||
{
|
||||
var assembliesMap = Cache.GetOrCreate("clientPluginAssemblies", entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15);
|
||||
|
||||
return PluginService.GetAssemblies("client");
|
||||
})!;
|
||||
var assembliesMap = PluginService.AssemblyMap;
|
||||
|
||||
if (assembliesMap.ContainsKey(assembly))
|
||||
throw new HttpApiException("The requested assembly could not be found", 404);
|
||||
@@ -60,4 +38,10 @@ public class PluginsStreamController : Controller
|
||||
|
||||
await Results.File(path).ExecuteAsync(HttpContext);
|
||||
}
|
||||
|
||||
[HttpGet("assets")]
|
||||
public Task<PluginsAssetManifest> GetAssetManifest()
|
||||
{
|
||||
return Task.FromResult(PluginService.PluginsAssetManifest);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
using System.Text.Json;
|
||||
using MoonCore.Helpers;
|
||||
using MoonCore.Models;
|
||||
using Moonlight.ApiServer.Models;
|
||||
using Moonlight.Shared.Http.Responses.PluginsStream;
|
||||
|
||||
namespace Moonlight.ApiServer.Services;
|
||||
|
||||
public class PluginService
|
||||
{
|
||||
public readonly List<PluginMeta> Plugins = new();
|
||||
public readonly Dictionary<string, string> AssetMap = new();
|
||||
public HostedPluginsManifest HostedPluginsManifest;
|
||||
public PluginsAssetManifest PluginsAssetManifest;
|
||||
public Dictionary<string, string> AssemblyMap;
|
||||
|
||||
private static string PluginsFolder = PathBuilder.Dir("storage", "plugins");
|
||||
private readonly ILogger<PluginService> Logger;
|
||||
@@ -82,6 +88,26 @@ public class PluginService
|
||||
|
||||
// Remove unloadable plugins from cache
|
||||
Plugins.RemoveAll(x => pluginsToNotLoad.Contains(x.Manifest.Id));
|
||||
|
||||
// Generate assembly map for client
|
||||
AssemblyMap = GetAssemblies("client");
|
||||
|
||||
// Generate plugin stream manifest for client
|
||||
HostedPluginsManifest = new()
|
||||
{
|
||||
Assemblies = AssemblyMap.Keys.ToArray(),
|
||||
Entrypoints = GetEntrypoints("client")
|
||||
};
|
||||
|
||||
// Generate asset map
|
||||
GenerateAssetMap();
|
||||
|
||||
// Generate asset manifest
|
||||
PluginsAssetManifest = new()
|
||||
{
|
||||
CssFiles = AssetMap.Keys.Where(x => x.EndsWith(".css")).ToArray(),
|
||||
JavascriptFiles = AssetMap.Keys.Where(x => x.EndsWith(".js")).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetAssemblies(string section)
|
||||
@@ -110,4 +136,49 @@ public class PluginService
|
||||
.SelectMany(x => x.Manifest.Entrypoints[section])
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private void GenerateAssetMap()
|
||||
{
|
||||
AssetMap.Clear();
|
||||
|
||||
foreach (var plugin in Plugins)
|
||||
{
|
||||
var assetPath = PathBuilder.Dir(plugin.Path, "wwwroot");
|
||||
|
||||
if (!Directory.Exists(assetPath))
|
||||
continue;
|
||||
|
||||
var files = new List<string>();
|
||||
GetFilesInDirectory(assetPath, files);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var mapPath = Formatter.ReplaceStart(file, assetPath, "");
|
||||
|
||||
mapPath = mapPath.Replace("\\", "/"); // To handle fucking windows
|
||||
mapPath = mapPath.StartsWith("/") ? mapPath : "/" + mapPath; // Ensure starting /
|
||||
|
||||
if (AssetMap.ContainsKey(mapPath))
|
||||
{
|
||||
Logger.LogWarning(
|
||||
"The plugin '{name}' tries to map an asset to the path '{path}' which is already used by another plugin. Ignoring asset mapping",
|
||||
plugin.Manifest.Id,
|
||||
mapPath
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
AssetMap[mapPath] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetFilesInDirectory(string directory, List<string> files)
|
||||
{
|
||||
files.AddRange(Directory.EnumerateFiles(directory));
|
||||
|
||||
foreach (var dir in Directory.EnumerateDirectories(directory))
|
||||
GetFilesInDirectory(dir, files);
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@ public class Startup
|
||||
await UseOAuth2();
|
||||
await UseBaseMiddleware();
|
||||
await HookPluginConfigure();
|
||||
await UsePluginAssets();
|
||||
|
||||
await MapBase();
|
||||
await MapOAuth2();
|
||||
@@ -271,6 +272,16 @@ public class Startup
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task UsePluginAssets()
|
||||
{
|
||||
WebApplication.UseStaticFiles(new StaticFileOptions()
|
||||
{
|
||||
FileProvider = new PluginAssetFileProvider(PluginService)
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#region Hooks
|
||||
|
||||
private async Task HookPluginBuild()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.JSInterop;
|
||||
using MoonCore.Blazor.Extensions;
|
||||
using MoonCore.Blazor.Services;
|
||||
using MoonCore.Blazor.Tailwind.Extensions;
|
||||
@@ -14,6 +15,7 @@ using Moonlight.Client.Interfaces;
|
||||
using Moonlight.Client.Services;
|
||||
using Moonlight.Client.UI;
|
||||
using Moonlight.Client.UI.Forms;
|
||||
using Moonlight.Shared.Http.Responses.PluginsStream;
|
||||
|
||||
namespace Moonlight.Client;
|
||||
|
||||
@@ -59,6 +61,8 @@ public class Startup
|
||||
|
||||
await BuildWebAssemblyHost();
|
||||
|
||||
await LoadPluginAssets();
|
||||
|
||||
await WebAssemblyHost.RunAsync();
|
||||
}
|
||||
|
||||
@@ -164,6 +168,20 @@ public class Startup
|
||||
WebAssemblyHostBuilder.Services.AddSingleton(ApplicationAssemblyService);
|
||||
}
|
||||
|
||||
private async Task LoadPluginAssets()
|
||||
{
|
||||
var apiClient = WebAssemblyHost.Services.GetRequiredService<HttpApiClient>();
|
||||
var assetManifest = await apiClient.GetJson<PluginsAssetManifest>("api/pluginsStream/assets");
|
||||
|
||||
var jsRuntime = WebAssemblyHost.Services.GetRequiredService<IJSRuntime>();
|
||||
|
||||
foreach (var cssFile in assetManifest.CssFiles)
|
||||
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadCss", cssFile);
|
||||
|
||||
foreach (var javascriptFile in assetManifest.JavascriptFiles)
|
||||
await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", javascriptFile);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Logging
|
||||
|
||||
@@ -25,5 +25,24 @@ window.moonlight = {
|
||||
closeCurrent() {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
assets: {
|
||||
loadCss: function (url) {
|
||||
let linkElement = document.createElement('link');
|
||||
|
||||
linkElement.href = url;
|
||||
linkElement.rel = 'stylesheet';
|
||||
linkElement.type = 'text/css';
|
||||
|
||||
(document.head || document.documentElement).appendChild(linkElement);
|
||||
},
|
||||
loadJavascript: function (url) {
|
||||
let scriptElement = document.createElement('script');
|
||||
|
||||
scriptElement.src = url;
|
||||
scriptElement.type = 'text/javascript';
|
||||
|
||||
(document.head || document.documentElement).appendChild(scriptElement);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Moonlight.Shared.Http.Responses.PluginsStream;
|
||||
|
||||
public class PluginsAssetManifest
|
||||
{
|
||||
public string[] CssFiles { get; set; }
|
||||
public string[] JavascriptFiles { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user