Skip to content

Commit

Permalink
Mount file and mount folder (#22)
Browse files Browse the repository at this point in the history
* Add configuration source ini, keyPerFile and xml

* Test Parse mountFile and mountFolder

* Implement MountFolder and test
  • Loading branch information
thohng authored May 31, 2024
1 parent 3a0dda6 commit 535e10b
Show file tree
Hide file tree
Showing 11 changed files with 487 additions and 23 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,7 @@ ASALocalRun/
.localhistory/

# BeatPulse healthcheck temp database
healthchecksdb
healthchecksdb

# Local development
.local-dev
6 changes: 6 additions & 0 deletions exclusion.dic
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
00240000048000009400000006020000002400005253413100040000010001004d00e99319a4318191d83271ebfe50641f7d06bf155b6577bdf3d8cfc4acd1b1c4423dad9ef8f96273fb89f04f9f38f46b8311ced3dcc18d302b860db3b8a12e93bcf5af95a178deb289dab8ce14ef01994a90b3623ddcec9675a8bc1a9c03c3c73da3c103777e1228438b7eacf8a205e092b2a5a480a7b1ff37c8446b4b47b4
ddcec
eacf
ebfe
buildtime
dest
ffff
fffff
ffffff
fffffff
fffzzz
healthz
hsts
json'
subpath
testhost
urls
Expand Down
4 changes: 4 additions & 0 deletions src/Hosting/Hosting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="Hosting.Test" Key="00240000048000009400000006020000002400005253413100040000010001004d00e99319a4318191d83271ebfe50641f7d06bf155b6577bdf3d8cfc4acd1b1c4423dad9ef8f96273fb89f04f9f38f46b8311ced3dcc18d302b860db3b8a12e93bcf5af95a178deb289dab8ce14ef01994a90b3623ddcec9675a8bc1a9c03c3c73da3c103777e1228438b7eacf8a205e092b2a5a480a7b1ff37c8446b4b47b4" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
Expand Down
83 changes: 72 additions & 11 deletions src/Hosting/MountFileHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,35 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NetLah.Extensions.Logging;
using NetLah.Extensions.SpaServices.Hosting;

namespace Microsoft.AspNetCore.Builder;
namespace NetLah.Extensions.SpaServices.Hosting;

internal static class MountFileHelpers
{
private static readonly Lazy<ILogger?> _loggerLazy = new(() => AppLogReference.GetAppLogLogger(typeof(AppOptions).Namespace + ".MountFile"));
private static readonly StringComparer _stringComparer = OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
// private static readonly StringComparison _stringComparison = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal

internal static Func<string, bool> ValidateDirectoryExistsDelegate { get; set; } = Directory.Exists;

private static ILogger GetLogger()
{
return _loggerLazy.Value ?? NullLogger.Instance;
}

public static void Configure(IServiceCollection services, IConfigurationRoot configuration)
{
var logger = GetLogger();
var options = ParserOptions(configuration, GetLogger());

if (options.Files.Count > 0 || options.Folders.Count > 0)
{
services.AddSingleton(options);
services.AddDecoratorAsLifetime<ISpaStaticFileProvider, MountSpaStaticFileProvider>();
}
}

public static MountFileProviderOptions ParserOptions(IConfigurationRoot configuration, ILogger logger)
{
var options = new MountFileProviderOptions
{
Files = new Dictionary<string, string>(_stringComparer),
Expand All @@ -33,13 +43,13 @@ public static void Configure(IServiceCollection services, IConfigurationRoot con
{
if (item.Value is { } keyValue)
{
if (TryParse(keyValue, out var target, out var source) && !string.IsNullOrEmpty(target) && !string.IsNullOrEmpty(source))
if (TryParseKeyValue(keyValue, out var target, out var source) && !string.IsNullOrEmpty(target) && !string.IsNullOrEmpty(source))
{
TryAddFile(target, source);
}
else
{
throw new InvalidOperationException("Invalid MountFile " + keyValue);
throw new InvalidOperationException("Invalid MountFile '" + keyValue + "'");
}
}
else
Expand All @@ -50,13 +60,28 @@ public static void Configure(IServiceCollection services, IConfigurationRoot con
}
}

if (options.Files.Count > 0 || options.Folders.Count > 0)
foreach (var item in configuration.GetSection("MountFolder").GetChildren().Concat(configuration.GetSection("MountFolders").GetChildren()))
{
services.AddSingleton(options);
services.AddDecoratorAsLifetime<ISpaStaticFileProvider, MountSpaStaticFileProvider>();
if (item.Value is { } keyValue)
{
if (TryParseKeyValue(keyValue, out var target, out var source) && !string.IsNullOrEmpty(target) && !string.IsNullOrEmpty(source))
{
TryAddFolder(target, source);
}
else
{
throw new InvalidOperationException("Invalid MountFolder '" + keyValue + "'");
}
}
else
{
var source = item["From"] ?? item["Source"] ?? item["Src"];
var target = item["Target"] ?? item["To"] ?? item["Destination"] ?? item["Dest"] ?? item["Dst"];
TryAddFolder(target, source);
}
}

bool TryParse(string keyValue, out string key, out string? value)
bool TryParseKeyValue(string keyValue, out string key, out string? value)
{
var pos = keyValue.IndexOf('=');
if (pos > 0 && pos < keyValue.Length - 1)
Expand All @@ -75,7 +100,7 @@ void TryAddFile(string? target, string? source)
{
if (string.IsNullOrEmpty(target) || !target.StartsWith('/'))
{
throw new InvalidOperationException("target have to start with /");
throw new InvalidOperationException("target has to start with / '" + target + "'");
}

if (string.IsNullOrEmpty(source))
Expand All @@ -85,7 +110,7 @@ void TryAddFile(string? target, string? source)

if (options.Files.ContainsKey(target))
{
throw new InvalidOperationException("Duplicate target " + target);
throw new InvalidOperationException("Duplicated target '" + target + "'");
}

var sourceFullPath = Path.GetFullPath(source);
Expand All @@ -94,5 +119,41 @@ void TryAddFile(string? target, string? source)

options.Files[target] = sourceFullPath;
}

void TryAddFolder(string? target, string? source)
{
if (string.IsNullOrEmpty(target) || !target.StartsWith('/'))
{
throw new InvalidOperationException("target has to start with / '" + target + "'");
}

if (string.IsNullOrEmpty(source))
{
throw new InvalidOperationException("source is required");
}

if (!target.EndsWith('/'))
{
target += '/';
}

if (options.Folders.ContainsKey(target))
{
throw new InvalidOperationException("Duplicated target '" + target + "'");
}

var sourceFullPath = Path.GetFullPath(source);

if (!ValidateDirectoryExistsDelegate(sourceFullPath))
{
throw new DirectoryNotFoundException(sourceFullPath);
}

logger.LogDebug("MountFolder target={target} source={source} {sourceFullPath}", target, source, sourceFullPath);

options.Folders[target] = sourceFullPath;
}

return options;
}
}
34 changes: 25 additions & 9 deletions src/Hosting/MountFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

namespace NetLah.Extensions.SpaServices.Hosting;

internal class MountFileProvider : IFileProvider
internal class MountFileProvider(IFileProvider fileProvider, MountFileProviderOptions options) : IFileProvider
{
private readonly IFileProvider _fileProvider;
private readonly MountFileProviderOptions _options;
private readonly IFileProvider _fileProvider = fileProvider;
private readonly IDictionary<string, string> _fileMap = options?.Files ?? new Dictionary<string, string>();
private readonly (string path, Entry value)[] _folderMap = options?
.Folders?
.Select(kv => (kv.Key, new Entry(kv.Key.Length - 1, new PhysicalFileProvider(kv.Value))))
.ToArray()
?? [];

public MountFileProvider(IFileProvider fileProvider, MountFileProviderOptions options)
private class Entry(int length, IFileProvider fileProvider)
{
_fileProvider = fileProvider;
_options = options;
public int Length { get; } = length;
public IFileProvider FileProvider { get; } = fileProvider;
}

public IDirectoryContents GetDirectoryContents(string subpath)
Expand All @@ -22,10 +27,21 @@ public IDirectoryContents GetDirectoryContents(string subpath)

public IFileInfo GetFileInfo(string subpath)
{
if (_options.Files.TryGetValue(subpath, out var source))
if (!string.IsNullOrEmpty(subpath))
{
var fileInfo = new FileInfo(source);
return fileInfo.Exists ? new PhysicalFileInfo(fileInfo) : new NotFoundFileInfo(subpath);
if (_fileMap.TryGetValue(subpath, out var source))
{
var fileInfo = new FileInfo(source);
return fileInfo.Exists ? new PhysicalFileInfo(fileInfo) : new NotFoundFileInfo(subpath);
}

foreach (var (path, entry) in _folderMap)
{
if (subpath.StartsWith(path))
{
return entry.FileProvider.GetFileInfo(subpath[entry.Length..]);
}
}
}

var result = _fileProvider.GetFileInfo(subpath);
Expand Down
8 changes: 7 additions & 1 deletion src/WebApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.SpaServices;
using NetLah.Diagnostics;
using NetLah.Extensions.ApplicationInsights;
using NetLah.Extensions.Configuration;
using NetLah.Extensions.Logging;
using NetLah.Extensions.SpaServices.Hosting;

Expand All @@ -12,7 +13,12 @@
ApplicationInfo.TryInitialize(null);
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
.AddAddFileConfiguration()
.AddAddFileConfiguration(options =>
{
options.AddProvider("keyPerFile", KeyPerFileConfigurationBuilderExtensions.AddKeyPerFile, resolveAbsolute: true);
options.AddProvider(".ini", IniConfigurationExtensions.AddIniFile);
options.AddProvider(".xml", XmlConfigurationExtensions.AddXmlFile);
})
.AddTransformConfiguration()
.AddMapConfiguration();

Expand Down
2 changes: 1 addition & 1 deletion src/WebApp/WebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PackageReference Include="NetLah.Extensions.Configuration" />
</ItemGroup>

<ItemGroup Condition="'$(NET_6_0)' == true">
<ItemGroup Condition="'$(NET_6_0)' == true">
<PackageReference Include="Azure.Core" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
Expand Down
4 changes: 4 additions & 0 deletions test/Hosting.Test/Hosting.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
</ItemGroup>

<ItemGroup>
<None Update="mount-file-folder/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="public*/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand All @@ -28,6 +31,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="NetLah.Extensions.Configuration" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Loading

0 comments on commit 535e10b

Please sign in to comment.