using System.Text.Json; namespace Zcbot.WindowsNode; internal interface INodeAdapter { string Capability { get; } string DisplayName { get; } string AdapterVersion { get; } IReadOnlyList Features { get; } bool HasActiveJobs { get; } string RunningDetail { get; } AdapterRuntimeStatus DetectRuntime(); bool ValidateRequest(JsonElement request); Task RunAsync(RecoverableJob job); void Cancel(Guid jobId); } internal sealed class OriginPlotNodeAdapter(JobInboxStore inbox) : INodeAdapter { internal const string CurrentAdapterVersion = "0.5.0"; internal const string ContractFilename = "origin.plot.v2.json"; private static readonly NodeAdapterContract Contract = NodeAdapterContract.Load(ContractFilename, CurrentAdapterVersion); private readonly OriginWorkerRunner runner = new(inbox); internal static string CapabilityName => Contract.Capability; public string Capability => Contract.Capability; public string DisplayName => Contract.DisplayName; public string AdapterVersion => CurrentAdapterVersion; public IReadOnlyList Features => Contract.Features; public bool HasActiveJobs => runner.HasActiveJobs; public string RunningDetail => "Origin 正在生成图形"; public AdapterRuntimeStatus DetectRuntime() => OriginRuntimeProbe.Detect(); public bool ValidateRequest(JsonElement request) => JobInboxStore.IsValidOriginRequest(request, Features); public Task RunAsync(RecoverableJob job) => runner.RunAsync(job); public void Cancel(Guid jobId) => runner.Cancel(jobId); } internal sealed record NodeAdapterContract( string Capability, string DisplayName, IReadOnlyList Features) { internal static NodeAdapterContract Load(string filename, string adapterVersion) { var path = Path.GetFullPath( Path.Combine(AppContext.BaseDirectory, "software-contracts", filename)); using var document = JsonDocument.Parse(File.ReadAllBytes(path)); var root = document.RootElement; var capability = root.GetProperty("capability").GetString() ?? throw new InvalidDataException("Adapter contract capability is missing."); var displayName = root.GetProperty("display_name").GetString() ?? throw new InvalidDataException("Adapter contract display name is missing."); var current = ParseVersion(adapterVersion); var features = root.GetProperty("features").EnumerateObject() .Where(item => ParseVersion(item.Value.GetString() ?? "0.0.0") <= current) .Select(item => item.Name) .ToArray(); return new NodeAdapterContract(capability, displayName, features); } private static Version ParseVersion(string value) => Version.TryParse(value, out var version) ? version : new Version(0, 0, 0); } internal sealed class NodeAdapterRegistry { private readonly IReadOnlyDictionary adapters; internal NodeAdapterRegistry(IEnumerable values) { adapters = values.ToDictionary(item => item.Capability, StringComparer.Ordinal); } internal static NodeAdapterRegistry CreateDefault(JobInboxStore inbox) => new([new OriginPlotNodeAdapter(inbox)]); internal static IReadOnlyList InstalledContracts { get; } = [ NodeAdapterContract.Load( OriginPlotNodeAdapter.ContractFilename, OriginPlotNodeAdapter.CurrentAdapterVersion), ]; internal static IReadOnlyList InstalledCapabilities { get; } = InstalledContracts.Select(item => item.Capability).ToArray(); internal IReadOnlyCollection All => adapters.Values.ToArray(); internal INodeAdapter? Find(string capability) => adapters.TryGetValue(capability, out var adapter) ? adapter : null; }