118 lines
3.7 KiB
C#
118 lines
3.7 KiB
C#
using System.Text.Json;
|
|
using MAF1.PluginContract;
|
|
|
|
namespace MAF1.Plugins;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|