Setup embedded file provider (Closes #1298) (#1305)

This commit is contained in:
2026-01-17 17:01:05 +01:00
parent 4f1398a8d1
commit 21b9cf1726
12 changed files with 162 additions and 10 deletions
@@ -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()
{
@@ -0,0 +1,8 @@
using System.Reflection;
namespace Daybreak.Shared.Services.FileProviders;
public interface IFileProviderProducer
{
void RegisterAssembly(Assembly assembly);
}
+10 -2
View File
@@ -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<IFileProvider, DaybreakFileProvider>();
RegisterHttpClients(services);
services.RegisterClientWebSocket<TradeChatService<KamadanTradeChatOptions>>()
.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
+3 -8
View File
@@ -81,21 +81,16 @@
<WikiFiles Include="$(WikiSourcePath)\**\*.*" Exclude="$(WikiSourcePath)\.git\**" />
</ItemGroup>
<Target Name="CopyWikiToWwwroot" BeforeTargets="BeforeBuild">
<Target Name="CopyWikiToWwwroot" BeforeTargets="PrepareForBuild">
<Message Importance="high" Text="📦 Daybreak.wiki -&gt; wwwroot/wiki" />
<Copy SourceFiles="@(WikiFiles)" DestinationFiles="@(WikiFiles->'wwwroot\wiki\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<!-- Copy wwwroot to output directory for Photino.Blazor -->
<!-- Embed wwwroot files as embedded resources -->
<ItemGroup>
<WwwrootFiles Include="wwwroot\**\*.*" />
<EmbeddedResource Include="wwwroot\**\*.*" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<Target Name="CopyWwwrootToOutput" AfterTargets="Build">
<Message Importance="high" Text="📦 wwwroot -&gt; $(DependencyOutput)\wwwroot" />
<Copy SourceFiles="@(WwwrootFiles)" DestinationFiles="@(WwwrootFiles->'$(DependencyOutput)\wwwroot\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
</Target>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
<Exec Command="echo.&gt;$(Version).version" />
</Target>
+15
View File
@@ -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<Launcher> scopedLogger)
{
try
{
var fileProviderProducer = new AssemblyFileProviderProducer(builder.Services, bootstrap.GetRequiredService<ILogger<AssemblyFileProviderProducer>>());
configuration.RegisterProviderAssemblies(fileProviderProducer);
}
catch (Exception ex)
{
scopedLogger.LogError(ex, "An error occurred while registering file providers");
}
}
}
+7
View File
@@ -0,0 +1,7 @@
using System.Reflection;
namespace Daybreak.Models;
public sealed record FileProviderAssembly(Assembly Assembly, string Name)
{
}
@@ -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<AssemblyFileProviderProducer> logger) : IFileProviderProducer
{
private readonly IServiceCollection services = services;
private readonly ILogger<AssemblyFileProviderProducer> 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);
}
}
@@ -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<string, FileProviderAssembly> manifestMapping = [];
private readonly IEnumerable<FileProviderAssembly> fileProviderAssemblies;
private readonly ILogger<DaybreakFileProvider> logger;
public DaybreakFileProvider(
IEnumerable<FileProviderAssembly> fileProviderAssemblies,
ILogger<DaybreakFileProvider> 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;
}
}
@@ -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<SimpleNotificationMod, SimpleNotificationMod>(true);
}
public override void RegisterProviderAssemblies(IFileProviderProducer fileProviderProducer)
{
fileProviderProducer.RegisterAssembly(typeof(PluginConfiguration).Assembly);
}
}
@@ -10,4 +10,9 @@
<ProjectReference Include="..\..\..\..\Daybreak.Shared\Daybreak.Shared.csproj" />
</ItemGroup>
<!-- Embed wwwroot files as embedded resources -->
<ItemGroup>
<EmbeddedResource Include="wwwroot\**\*.*" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
</Project>
@@ -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);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB