diff --git a/Moonlight.ApiServer/Helpers/BundleAssetFileProvider.cs b/Moonlight.ApiServer/Helpers/BundleAssetFileProvider.cs new file mode 100644 index 00000000..06669dc5 --- /dev/null +++ b/Moonlight.ApiServer/Helpers/BundleAssetFileProvider.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.FileProviders.Physical; +using Microsoft.Extensions.Primitives; +using MoonCore.Helpers; + +namespace Moonlight.ApiServer.Helpers; + +public class BundleAssetFileProvider : IFileProvider +{ + public IDirectoryContents GetDirectoryContents(string subpath) + => NotFoundDirectoryContents.Singleton; + + public IFileInfo GetFileInfo(string subpath) + { + if(subpath != "/css/bundle.css") + return new NotFoundFileInfo(subpath); + + var physicalPath = PathBuilder.File("storage", "tmp", "bundle.css"); + + if(!File.Exists(physicalPath)) + return new NotFoundFileInfo(subpath); + + return new PhysicalFileInfo(new FileInfo(physicalPath)); + } + + public IChangeToken Watch(string filter) + => NullChangeToken.Singleton; +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index a7883ea6..09aad5d8 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -18,6 +18,7 @@ + all diff --git a/Moonlight.ApiServer/Services/BundleService.cs b/Moonlight.ApiServer/Services/BundleService.cs new file mode 100644 index 00000000..aa1d4892 --- /dev/null +++ b/Moonlight.ApiServer/Services/BundleService.cs @@ -0,0 +1,132 @@ +using ExCSS; +using Microsoft.Extensions.Hosting.Internal; +using MoonCore.Helpers; + +namespace Moonlight.ApiServer.Services; + +public class BundleService : IHostedService +{ + private readonly ILogger Logger; + private readonly List CssFiles = new(); + + private readonly AssetService AssetService; + private readonly IWebHostEnvironment HostEnvironment; + + public BundleService(ILogger logger, PluginService pluginService, IWebHostEnvironment hostEnvironment, AssetService assetService) + { + Logger = logger; + HostEnvironment = hostEnvironment; + AssetService = assetService; + } + + public async Task Bundle(CancellationToken cancellationToken) + { + Logger.LogInformation("Collecting css files..."); + + var fi = HostEnvironment.WebRootFileProvider.GetFileInfo("css/core.min.css"); + + CssFiles.Add(fi.PhysicalPath!); + CssFiles.AddRange(AssetService.GetCssAssets().Select(webPath => + { + var fii = HostEnvironment.WebRootFileProvider.GetFileInfo(webPath.TrimStart('/')); + + return fii.PhysicalPath!; + })); + + Logger.LogInformation("Bundling css files..."); + + var content = ""; + + if (CssFiles.Count > 0) + { + var cssFileContents = new List(); + + foreach (var cssFile in CssFiles) + { + var fileContent = await File.ReadAllTextAsync(cssFile, cancellationToken); + cssFileContents.Add(fileContent); + } + + content = cssFileContents[0]; + + if (cssFileContents.Count > 1) + { + var styleSheets = new List(); + + var parser = new StylesheetParser(); + + foreach (var fileContent in cssFileContents) + { + try + { + var sh = await parser.ParseAsync(fileContent, cancellationToken); + styleSheets.Add(sh); + } + catch (Exception e) + { + Logger.LogWarning("An error occured while parsing css file: {e}", e); + } + } + + var mainStylesheet = styleSheets.First(); + + foreach (var stylesheet in styleSheets.Skip(1)) + { + // Style + foreach (var styleRule in stylesheet.StyleRules) + { + if (mainStylesheet.StyleRules.Any(x => x.Selector.Text == styleRule.Selector.Text)) + continue; + + content += styleRule.ToCss() + "\n"; + } + + // Container + foreach (var containerRule in stylesheet.ContainerRules) + { + if (mainStylesheet.ContainerRules.Any(x => x.ConditionText == containerRule.ConditionText)) + continue; + + content += containerRule.ToCss() + "\n"; + } + + // Import Rule + foreach (var importRule in stylesheet.ImportRules) + { + if (mainStylesheet.ImportRules.Any(x => x.Text == importRule.Text)) + continue; + + content += importRule.ToCss() + "\n"; + } + + // Media Rules + foreach (var mediaRule in stylesheet.MediaRules) + content += mediaRule.StylesheetText.Text + "\n"; + + // Page Rules + foreach (var pageRule in stylesheet.PageRules) + { + if (mainStylesheet.PageRules.Any(x => x.SelectorText == pageRule.SelectorText)) + continue; + + content += pageRule.ToCss() + "\n"; + } + } + } + } + + Directory.CreateDirectory(PathBuilder.Dir("storage", "tmp")); + await File.WriteAllTextAsync(PathBuilder.File("storage", "tmp", "bundle.css"), content, cancellationToken); + + Logger.LogInformation("Successfully built css bundle"); + } + + public void AddCssFile(string file) => CssFiles.Add(file); + + // + public Task StartAsync(CancellationToken cancellationToken) + => Bundle(cancellationToken); + + public Task StopAsync(CancellationToken cancellationToken) + => Task.CompletedTask; +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index 0a686fca..767c5826 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -79,6 +79,7 @@ public class Startup await RegisterCaching(); await HookPluginBuild(); await HandleConfigureArguments(); + await RegisterPluginAssets(); await BuildWebApplication(); @@ -341,8 +342,21 @@ public class Startup return Task.CompletedTask; } + private Task RegisterPluginAssets() + { + WebApplicationBuilder.Services.AddHostedService(sp => sp.GetRequiredService()); + WebApplicationBuilder.Services.AddSingleton(); + + return Task.CompletedTask; + } + private Task UsePluginAssets() { + WebApplication.UseStaticFiles(new StaticFileOptions() + { + FileProvider = new BundleAssetFileProvider() + }); + WebApplication.UseStaticFiles(new StaticFileOptions() { FileProvider = new PluginAssetFileProvider(PluginService)