Add WinUI and core source
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-26 13:27:13 +08:00
parent f59190251d
commit 7ecc6a8923
262 changed files with 137492 additions and 0 deletions
@@ -0,0 +1,206 @@
using YMhut.Box.Core.App;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.Settings;
using System.Security.Cryptography;
using System.Text;
namespace YMhut.Box.Core.Plugins;
public interface IBuiltInPluginInstallerService
{
Task EnsureInstalledAsync(CancellationToken cancellationToken = default);
}
public sealed class BuiltInPluginInstallerService(
AppPaths paths,
ILogService? logService = null,
ISettingsService? settingsService = null) : IBuiltInPluginInstallerService
{
private const string ResourcePrefix = "YMhut.Box.Core.Plugins.BuiltIn.";
private const string FingerprintFileName = ".ymhut-built-in.sha256";
private static readonly string[] KnownRootFiles =
[
"README.md",
"ymhut.plugin.json",
"index.html",
"style.css",
"main.js"
];
private readonly SemaphoreSlim _gate = new(1, 1);
public string PluginsRoot => PluginRegistryService.ResolvePluginsRoot(paths, settingsService?.Current.PluginRootPath);
public async Task EnsureInstalledAsync(CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
Directory.CreateDirectory(PluginsRoot);
var plugins = DiscoverEmbeddedPlugins();
if (plugins.Count == 0)
{
await WriteLogAsync("Warning", "No built-in plugin resources found", PluginsRoot, cancellationToken).ConfigureAwait(false);
return;
}
foreach (var plugin in plugins)
{
var targetRoot = Path.Combine(PluginsRoot, plugin.FolderName);
if (Directory.Exists(targetRoot))
{
if (await IsUnmodifiedBuiltInPluginAsync(targetRoot, cancellationToken).ConfigureAwait(false))
{
Directory.Delete(targetRoot, recursive: true);
await ExtractEmbeddedPluginAsync(plugin, targetRoot, cancellationToken).ConfigureAwait(false);
await WriteLogAsync("Information", "Built-in plugin refreshed", targetRoot, cancellationToken).ConfigureAwait(false);
}
continue;
}
await ExtractEmbeddedPluginAsync(plugin, targetRoot, cancellationToken).ConfigureAwait(false);
await WriteLogAsync("Information", "Built-in plugin installed", targetRoot, cancellationToken).ConfigureAwait(false);
}
}
finally
{
_gate.Release();
}
}
private static async Task<bool> IsUnmodifiedBuiltInPluginAsync(string targetRoot, CancellationToken cancellationToken)
{
var fingerprintPath = Path.Combine(targetRoot, FingerprintFileName);
if (!File.Exists(fingerprintPath))
{
return false;
}
try
{
var saved = (await File.ReadAllTextAsync(fingerprintPath, cancellationToken).ConfigureAwait(false)).Trim();
if (string.IsNullOrWhiteSpace(saved))
{
return false;
}
var current = await ComputeDirectoryFingerprintAsync(targetRoot, cancellationToken).ConfigureAwait(false);
return string.Equals(saved, current, StringComparison.OrdinalIgnoreCase);
}
catch (IOException)
{
return false;
}
catch (UnauthorizedAccessException)
{
return false;
}
}
private static async Task ExtractEmbeddedPluginAsync(EmbeddedBuiltInPlugin plugin, string targetRoot, CancellationToken cancellationToken)
{
var assembly = typeof(BuiltInPluginInstallerService).Assembly;
Directory.CreateDirectory(targetRoot);
foreach (var resource in plugin.Resources)
{
cancellationToken.ThrowIfCancellationRequested();
var target = Path.GetFullPath(Path.Combine(targetRoot, resource.RelativePath));
if (!PluginRegistryService.IsInside(targetRoot, target))
{
throw new InvalidDataException($"Built-in plugin resource escapes target directory: {resource.ResourceName}");
}
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
await using var input = assembly.GetManifestResourceStream(resource.ResourceName)
?? throw new InvalidOperationException($"Built-in plugin resource cannot be opened: {resource.ResourceName}");
await using var output = File.Create(target);
await input.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
}
var fingerprint = await ComputeDirectoryFingerprintAsync(targetRoot, cancellationToken).ConfigureAwait(false);
await File.WriteAllTextAsync(Path.Combine(targetRoot, FingerprintFileName), fingerprint, Encoding.UTF8, cancellationToken).ConfigureAwait(false);
}
private static async Task<string> ComputeDirectoryFingerprintAsync(string targetRoot, CancellationToken cancellationToken)
{
using var sha = SHA256.Create();
var files = Directory.EnumerateFiles(targetRoot, "*", SearchOption.AllDirectories)
.Where(path => !string.Equals(Path.GetFileName(path), FingerprintFileName, StringComparison.OrdinalIgnoreCase))
.Select(path => Path.GetFullPath(path))
.Order(StringComparer.OrdinalIgnoreCase);
foreach (var file in files)
{
cancellationToken.ThrowIfCancellationRequested();
var relative = file.Substring(Path.GetFullPath(targetRoot).Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Replace('\\', '/');
var nameBytes = Encoding.UTF8.GetBytes(relative);
sha.TransformBlock(nameBytes, 0, nameBytes.Length, null, 0);
sha.TransformBlock([0], 0, 1, null, 0);
var content = await File.ReadAllBytesAsync(file, cancellationToken).ConfigureAwait(false);
sha.TransformBlock(content, 0, content.Length, null, 0);
sha.TransformBlock([0], 0, 1, null, 0);
}
sha.TransformFinalBlock([], 0, 0);
return Convert.ToHexString(sha.Hash ?? []);
}
private Task WriteLogAsync(string level, string message, string detail, CancellationToken cancellationToken)
{
return logService?.WriteAsync(level, "plugin", message, detail, cancellationToken) ?? Task.CompletedTask;
}
private static IReadOnlyList<EmbeddedBuiltInPlugin> DiscoverEmbeddedPlugins()
{
var assembly = typeof(BuiltInPluginInstallerService).Assembly;
var resources = assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(ResourcePrefix, StringComparison.Ordinal))
.Select(ParseResource)
.OfType<EmbeddedBuiltInResource>()
.GroupBy(resource => resource.PluginToken, StringComparer.OrdinalIgnoreCase)
.Select(group => new EmbeddedBuiltInPlugin(
DecodePluginFolder(group.Key),
group.OrderBy(resource => resource.RelativePath, StringComparer.OrdinalIgnoreCase).ToArray()))
.Where(plugin => plugin.Resources.Any(resource => string.Equals(resource.RelativePath, PluginManifest.FileName, StringComparison.OrdinalIgnoreCase)))
.OrderBy(plugin => plugin.FolderName, StringComparer.OrdinalIgnoreCase)
.ToArray();
return resources;
}
private static EmbeddedBuiltInResource? ParseResource(string resourceName)
{
var relativeName = resourceName[ResourcePrefix.Length..];
foreach (var fileName in KnownRootFiles)
{
var suffix = "." + fileName;
if (!relativeName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
var token = relativeName[..^suffix.Length];
if (string.IsNullOrWhiteSpace(token))
{
return null;
}
return new EmbeddedBuiltInResource(token, fileName, resourceName);
}
return null;
}
private static string DecodePluginFolder(string token)
{
return token.Replace('_', '-');
}
private sealed record EmbeddedBuiltInPlugin(
string FolderName,
IReadOnlyList<EmbeddedBuiltInResource> Resources);
private sealed record EmbeddedBuiltInResource(
string PluginToken,
string RelativePath,
string ResourceName);
}