Files

135 lines
4.4 KiB
C#
Raw Permalink Normal View History

2026-08-26 18:06:02 +08:00
using System.Text.Json;
using MAF1.PluginContract;
namespace MAF1.Plugins;
/// <summary>
/// 扫描 exe 旁边的 plugins/*/plugin.json。源码目录 Plugins/ 不会被运行时直接读到,要先 build 复制到输出目录。
/// </summary>
2026-08-26 18:06:02 +08:00
public sealed class PluginScanner(PluginOptions options)
{
public PluginScanResult Scan()
{
string root = options.ResolveRoot();
Directory.CreateDirectory(root);
List<LoadedPlugin> plugins = [];
List<CatalogIssue> issues = [];
HashSet<string> ids = new(StringComparer.OrdinalIgnoreCase);
foreach (string folder in Directory.GetDirectories(root))
{
string folderName = Path.GetFileName(folder);
if (folderName.StartsWith('_') || folderName.StartsWith('.'))
{
continue;
}
string manifestPath = Path.Combine(folder, "plugin.json");
if (!File.Exists(manifestPath))
{
issues.Add(new CatalogIssue
{
Level = "warning",
Source = folderName,
Message = "目录里没有 plugin.json,已跳过。",
});
continue;
}
PluginManifest? manifest;
try
{
string json = File.ReadAllText(manifestPath);
manifest = JsonSerializer.Deserialize<PluginManifest>(json, PluginJson.Options);
}
catch (Exception ex)
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = folderName,
Message = $"plugin.json 无法解析:{ex.Message}",
});
continue;
}
if (manifest is null || string.IsNullOrWhiteSpace(manifest.Id))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = folderName,
Message = "plugin.json 缺少 id。",
});
continue;
}
if (string.IsNullOrWhiteSpace(manifest.Launch.Command))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = manifest.Id,
Message = "plugin.json 缺少 launch.command,宿主不会替插件拼命令。",
});
continue;
}
int protocol = PluginProtocol.Normalize(manifest.ProtocolVersion);
if (!PluginProtocol.IsSupported(manifest.ProtocolVersion))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = manifest.Id,
Message = $"plugin.json 的 protocolVersion={protocol} 不受支持。当前宿主只接受 1(缺省视为 1)。",
});
continue;
}
manifest.ProtocolVersion = protocol;
2026-08-26 18:06:02 +08:00
if (!ids.Add(manifest.Id))
{
issues.Add(new CatalogIssue
{
Level = "error",
Source = manifest.Id,
Message = $"插件 id `{manifest.Id}` 重复,已忽略目录 {folderName}。",
});
continue;
}
if (DuplicatePortNames(manifest.Inputs))
{
issues.Add(new CatalogIssue { Level = "error", Source = manifest.Id, Message = "inputs 字段名重复。" });
continue;
}
if (DuplicatePortNames(manifest.Outputs))
{
issues.Add(new CatalogIssue { Level = "error", Source = manifest.Id, Message = "outputs 字段名重复。" });
continue;
}
plugins.Add(new LoadedPlugin
{
FolderName = folderName,
FolderPath = folder,
Manifest = manifest,
});
}
return new PluginScanResult(root, plugins, issues);
}
private static bool DuplicatePortNames(List<PluginPort> ports)
=> ports.GroupBy(p => p.Name, StringComparer.OrdinalIgnoreCase).Any(g => g.Count() > 1);
}
public sealed record PluginScanResult(
string Root,
IReadOnlyList<LoadedPlugin> Plugins,
IReadOnlyList<CatalogIssue> Issues);