diff --git a/Daybreak.Shared/Models/Plugins/PluginConfigurationBase.cs b/Daybreak.Shared/Models/Plugins/PluginConfigurationBase.cs index 87a38b66..5ce8dde7 100644 --- a/Daybreak.Shared/Models/Plugins/PluginConfigurationBase.cs +++ b/Daybreak.Shared/Models/Plugins/PluginConfigurationBase.cs @@ -1,4 +1,5 @@ using Daybreak.Shared.Services.ApplicationArguments; +using Daybreak.Shared.Services.FileProviders; using Daybreak.Shared.Services.Initialization; using Daybreak.Shared.Services.Metrics; using Daybreak.Shared.Utils; @@ -25,6 +26,7 @@ public abstract class PluginConfigurationBase public virtual void RegisterLaunchArgumentHandlers(IArgumentHandlerProducer argumentHandlerProducer) { } public virtual void RegisterMenuButtons(IMenuServiceProducer menuServiceProducer) { } public virtual void RegisterThemes(IThemeProducer themeProducer) { } + public virtual void RegisterProviderAssemblies(IFileProviderProducer fileProviderProducer) { } public PluginConfigurationBase() { diff --git a/Daybreak.Shared/Services/FileProviders/IFileProviderProducer.cs b/Daybreak.Shared/Services/FileProviders/IFileProviderProducer.cs new file mode 100644 index 00000000..02c5f370 --- /dev/null +++ b/Daybreak.Shared/Services/FileProviders/IFileProviderProducer.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Daybreak.Shared.Services.FileProviders; + +public interface IFileProviderProducer +{ + void RegisterAssembly(Assembly assembly); +} diff --git a/Daybreak/Configuration/ProjectConfiguration.cs b/Daybreak/Configuration/ProjectConfiguration.cs index d2a14d07..103f47c3 100644 --- a/Daybreak/Configuration/ProjectConfiguration.cs +++ b/Daybreak/Configuration/ProjectConfiguration.cs @@ -15,6 +15,7 @@ using Daybreak.Services.Events; using Daybreak.Services.ExceptionHandling; using Daybreak.Services.ExecutableManagement; using Daybreak.Services.Experience; +using Daybreak.Services.FileProviders; using Daybreak.Services.Graph; using Daybreak.Services.Graph.Models; using Daybreak.Services.Guildwars; @@ -63,6 +64,7 @@ using Daybreak.Shared.Services.Downloads; using Daybreak.Shared.Services.Events; using Daybreak.Shared.Services.ExecutableManagement; using Daybreak.Shared.Services.Experience; +using Daybreak.Shared.Services.FileProviders; using Daybreak.Shared.Services.Guildwars; using Daybreak.Shared.Services.Initialization; using Daybreak.Shared.Services.Injection; @@ -97,6 +99,7 @@ using Daybreak.Views.Installation; using Daybreak.Views.Mods; using Daybreak.Views.Trade; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.FluentUI.AspNetCore.Components; using Microsoft.Identity.Client; @@ -118,10 +121,10 @@ public class ProjectConfiguration : PluginConfigurationBase { services.ThrowIfNull(); - // Add FluentUI components services.AddFluentUIComponents(); - services.AddTrailBlazr(); + services.AddSingleton(); + RegisterHttpClients(services); services.RegisterClientWebSocket>() .Build(); @@ -417,6 +420,11 @@ public class ProjectConfiguration : PluginConfigurationBase } } + public override void RegisterProviderAssemblies(IFileProviderProducer fileProviderProducer) + { + fileProviderProducer.RegisterAssembly(typeof(ProjectConfiguration).Assembly); + } + private static IServiceCollection RegisterHttpClients(IServiceCollection services) { return services diff --git a/Daybreak/Daybreak.csproj b/Daybreak/Daybreak.csproj index e39931a7..3200c95d 100644 --- a/Daybreak/Daybreak.csproj +++ b/Daybreak/Daybreak.csproj @@ -81,21 +81,16 @@ - + - + - + - - - - - diff --git a/Daybreak/Launch/Launcher.cs b/Daybreak/Launch/Launcher.cs index 223b73ed..08dcd185 100644 --- a/Daybreak/Launch/Launcher.cs +++ b/Daybreak/Launch/Launcher.cs @@ -1,4 +1,5 @@ using Daybreak.Configuration; +using Daybreak.Services.FileProviders; using Daybreak.Services.Initialization; using Daybreak.Shared.Models.Guildwars; using Daybreak.Shared.Models.Menu; @@ -103,6 +104,7 @@ public partial class Launcher RegisterNotificationHandlers(bootstrap, builder, configuration, scopedLogger); RegisterArgumentHandlers(bootstrap, builder, configuration, scopedLogger); RegisterMenuEntries(configuration, menuProducer, scopedLogger); + RegisterFileProviders(bootstrap, builder, configuration, scopedLogger); scopedLogger.LogInformation("Finished loading configuration for {Configuration.Name}", name); } @@ -234,4 +236,17 @@ public partial class Launcher scopedLogger.LogError(ex, "An error occurred while registering menu entries"); } } + + private static void RegisterFileProviders(IServiceProvider bootstrap, PhotinoBlazorAppBuilder builder, PluginConfigurationBase configuration, ScopedLogger scopedLogger) + { + try + { + var fileProviderProducer = new AssemblyFileProviderProducer(builder.Services, bootstrap.GetRequiredService>()); + configuration.RegisterProviderAssemblies(fileProviderProducer); + } + catch (Exception ex) + { + scopedLogger.LogError(ex, "An error occurred while registering file providers"); + } + } } diff --git a/Daybreak/Models/FileProviderAssembly.cs b/Daybreak/Models/FileProviderAssembly.cs new file mode 100644 index 00000000..be0146e5 --- /dev/null +++ b/Daybreak/Models/FileProviderAssembly.cs @@ -0,0 +1,7 @@ +using System.Reflection; + +namespace Daybreak.Models; + +public sealed record FileProviderAssembly(Assembly Assembly, string Name) +{ +} diff --git a/Daybreak/Services/FileProviders/AssemblyFileProviderProducer.cs b/Daybreak/Services/FileProviders/AssemblyFileProviderProducer.cs new file mode 100644 index 00000000..b9353060 --- /dev/null +++ b/Daybreak/Services/FileProviders/AssemblyFileProviderProducer.cs @@ -0,0 +1,24 @@ +using Daybreak.Models; +using Daybreak.Shared.Services.FileProviders; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Extensions.Core; +using System.Reflection; + +namespace Daybreak.Services.FileProviders; + +public sealed class AssemblyFileProviderProducer( + IServiceCollection services, + ILogger logger) : IFileProviderProducer +{ + private readonly IServiceCollection services = services; + private readonly ILogger logger = logger; + + public void RegisterAssembly(Assembly assembly) + { + var assemblyName = assembly.FullName ?? string.Empty; + var scopedLogger = this.logger.CreateScopedLogger(); + this.services.AddSingleton(new FileProviderAssembly(assembly, assemblyName)); + scopedLogger.LogDebug("Registered provider assembly {assemblyName}", assemblyName); + } +} diff --git a/Daybreak/Services/FileProviders/DaybreakFileProvider.cs b/Daybreak/Services/FileProviders/DaybreakFileProvider.cs new file mode 100644 index 00000000..9a08a640 --- /dev/null +++ b/Daybreak/Services/FileProviders/DaybreakFileProvider.cs @@ -0,0 +1,71 @@ +using Daybreak.Models; +using Daybreak.Shared.Utils; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; + +namespace Daybreak.Services.FileProviders; + +internal sealed class DaybreakFileProvider + : IFileProvider +{ + private static readonly string WwwrootPath = PathUtils.GetAbsolutePathFromRoot("wwwroot"); + private readonly Dictionary manifestMapping = []; + private readonly IEnumerable fileProviderAssemblies; + private readonly ILogger logger; + + public DaybreakFileProvider( + IEnumerable fileProviderAssemblies, + ILogger logger) + { + this.fileProviderAssemblies = fileProviderAssemblies; + this.logger = logger; + + foreach(var provider in this.fileProviderAssemblies) + { + var manifestNames = provider.Assembly.GetManifestResourceNames(); + foreach(var name in manifestNames) + { + this.manifestMapping[name] = provider; + } + } + } + + public IDirectoryContents GetDirectoryContents(string subpath) + { + var path = Path.Combine(WwwrootPath, subpath); + if (Directory.Exists(path)) + { + return new Microsoft.Extensions.FileProviders.Physical.PhysicalDirectoryInfo(new DirectoryInfo(path)); + } + + return NotFoundDirectoryContents.Singleton; + } + + public IFileInfo GetFileInfo(string subpath) + { + var key = subpath.Replace("/", "\\"); + if (this.manifestMapping.TryGetValue(key, out var provider)) + { + var name = Path.GetFileName(subpath); + return new Microsoft.Extensions.FileProviders.Embedded.EmbeddedResourceFileInfo( + provider.Assembly, + key, + name, + DateTime.UtcNow); + } + + var path = Path.Combine(WwwrootPath, subpath); + if (File.Exists(path)) + { + return new Microsoft.Extensions.FileProviders.Physical.PhysicalFileInfo(new FileInfo(path)); + } + + return new NotFoundFileInfo(subpath); + } + + public IChangeToken Watch(string filter) + { + return NullChangeToken.Singleton; + } +} diff --git a/Examples/Plugins/SimplePlugin/SimplePlugin/Configuration/PluginConfiguration.cs b/Examples/Plugins/SimplePlugin/SimplePlugin/Configuration/PluginConfiguration.cs index 372fa8e3..32e6119c 100644 --- a/Examples/Plugins/SimplePlugin/SimplePlugin/Configuration/PluginConfiguration.cs +++ b/Examples/Plugins/SimplePlugin/SimplePlugin/Configuration/PluginConfiguration.cs @@ -1,9 +1,11 @@ using Daybreak.Shared.Models.Plugins; +using Daybreak.Shared.Services.FileProviders; using Daybreak.Shared.Services.Initialization; using Daybreak.Shared.Services.Notifications; using Microsoft.Extensions.DependencyInjection; using SimplePlugin.Options; using SimplePlugin.Services; +using SimplePlugin.Themes; namespace SimplePlugin.Configuration; @@ -20,6 +22,7 @@ public sealed class PluginConfiguration : PluginConfigurationBase public override void RegisterThemes(IThemeProducer themeProducer) { + themeProducer.RegisterTheme(SimpleTheme.Instance); } public override void RegisterStartupActions(IStartupActionProducer startupActionProducer) @@ -58,4 +61,9 @@ public sealed class PluginConfiguration : PluginConfigurationBase { modsProducer.RegisterMod(true); } + + public override void RegisterProviderAssemblies(IFileProviderProducer fileProviderProducer) + { + fileProviderProducer.RegisterAssembly(typeof(PluginConfiguration).Assembly); + } } diff --git a/Examples/Plugins/SimplePlugin/SimplePlugin/SimplePlugin.csproj b/Examples/Plugins/SimplePlugin/SimplePlugin/SimplePlugin.csproj index 2db0ad99..959ee190 100644 --- a/Examples/Plugins/SimplePlugin/SimplePlugin/SimplePlugin.csproj +++ b/Examples/Plugins/SimplePlugin/SimplePlugin/SimplePlugin.csproj @@ -10,4 +10,9 @@ + + + + + diff --git a/Examples/Plugins/SimplePlugin/SimplePlugin/Themes/SimpleTheme.cs b/Examples/Plugins/SimplePlugin/SimplePlugin/Themes/SimpleTheme.cs new file mode 100644 index 00000000..f535ba8e --- /dev/null +++ b/Examples/Plugins/SimplePlugin/SimplePlugin/Themes/SimpleTheme.cs @@ -0,0 +1,9 @@ +using Daybreak.Shared.Models.ColorPalette; +using Daybreak.Shared.Models.Themes; + +namespace SimplePlugin.Themes; + +internal class SimpleTheme +{ + public static readonly Theme Instance = new ("Simple", AccentColor.Red, new StaticBackground("img/Simple.png"), Theme.LightDarkMode.Light, string.Empty); +} diff --git a/Examples/Plugins/SimplePlugin/SimplePlugin/wwwroot/img/Simple.png b/Examples/Plugins/SimplePlugin/SimplePlugin/wwwroot/img/Simple.png new file mode 100644 index 00000000..9d0ab9be Binary files /dev/null and b/Examples/Plugins/SimplePlugin/SimplePlugin/wwwroot/img/Simple.png differ