using System.Text.Json; using MiGu.Server.Launcher; namespace MiGu.Server.Signal; /// /// 优先从 plugins/StandardScene.Signal.dll 反射表和列(Model 上的 SignalTable / DisplayName)。 /// 没有插件 DLL 时才读 signal-tables.json 或内置清单。 /// public static class SignalTableManifestLoader { private const string ManifestFileName = "signal-tables.json"; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; public static IReadOnlyList Load(SimpleLiteLauncher launcher) { var workingDirectory = launcher.ResolveWorkingDirectory(); var pluginsDir = launcher.ResolvePluginsDir(); var fromPlugin = SignalModelSchemaResolver.ResolveTables(pluginsDir, workingDirectory); if (fromPlugin.Count > 0) return fromPlugin; var manifest = TryLoadManifest(launcher); return manifest.Tables .Where(t => !string.IsNullOrWhiteSpace(t.Id) && !string.IsNullOrWhiteSpace(t.FileName)) .Select(e => ToDef(e, pluginsDir, workingDirectory)) .ToList(); } public static long ComputeManifestSignature(SimpleLiteLauncher launcher) { long sig = 0; foreach (var path in ResolveManifestPaths(launcher)) { if (!File.Exists(path)) continue; try { var info = new FileInfo(path); sig ^= info.LastWriteTimeUtc.Ticks; sig ^= info.Length; } catch { /* ignore */ } } var pluginsDir = launcher.ResolvePluginsDir(); var dll = pluginsDir == null ? null : Path.Combine(pluginsDir, "StandardScene.Signal.dll"); if (dll != null && File.Exists(dll)) { try { var info = new FileInfo(dll); sig ^= info.LastWriteTimeUtc.Ticks; } catch { /* ignore */ } } return sig; } private static SignalTableManifest TryLoadManifest(SimpleLiteLauncher launcher) { foreach (var path in ResolveManifestPaths(launcher)) { if (!File.Exists(path)) continue; try { var json = File.ReadAllText(path); var manifest = JsonSerializer.Deserialize(json, JsonOptions); if (manifest?.Tables is { Count: > 0 }) return manifest; } catch { /* try next */ } } return JsonSerializer.Deserialize(EmbeddedFallbackJson, JsonOptions) ?? new SignalTableManifest(); } private static IEnumerable ResolveManifestPaths(SimpleLiteLauncher launcher) { var wd = launcher.ResolveWorkingDirectory(); if (!string.IsNullOrWhiteSpace(wd)) yield return Path.Combine(wd, "Config", "Signal", ManifestFileName); var plugins = launcher.ResolvePluginsDir(); if (string.IsNullOrWhiteSpace(plugins)) yield break; yield return Path.Combine(plugins, ManifestFileName); yield return Path.Combine(plugins, "Config", "Signal", ManifestFileName); } private static SignalTableDef ToDef(SignalTableManifestEntry entry, string? pluginsDir, string? workingDirectory) { var columns = BuildColumns(entry, pluginsDir, workingDirectory); return new SignalTableDef( entry.Id.Trim(), string.IsNullOrWhiteSpace(entry.Title) ? entry.Id.Trim() : entry.Title.Trim(), entry.Category ?? "", entry.FileName.Trim(), columns); } private static IReadOnlyList BuildColumns( SignalTableManifestEntry entry, string? pluginsDir, string? workingDirectory) { var reflected = SignalModelSchemaResolver.ResolveColumns(entry.Model, pluginsDir, workingDirectory); if (reflected.Count > 0) return reflected; if (entry.Columns is { Count: > 0 }) { return entry.Columns .Where(c => !string.IsNullOrWhiteSpace(c.Key)) .Select(c => new SignalColumn( c.Key.Trim(), string.IsNullOrWhiteSpace(c.Label) ? c.Key.Trim() : c.Label.Trim(), string.IsNullOrWhiteSpace(c.Type) ? "string" : c.Type.Trim(), c.Options, string.IsNullOrWhiteSpace(c.Group) ? null : c.Group.Trim())) .ToList(); } return Array.Empty(); } private const string EmbeddedFallbackJson = """ { "version": 1, "tables": [ { "id": "stations", "title": "PLC机构", "category": "PLC数据管理", "fileName": "stations.json", "model": "PlcStationModel" }, { "id": "docks", "title": "机构工位", "category": "PLC数据管理", "fileName": "station-docks.json", "model": "PlcStationDockModel" }, { "id": "handshake", "title": "握手点", "category": "握手点数据管理", "fileName": "handshake-points.json", "model": "HandshakePointModel" }, { "id": "release", "title": "放行点", "category": "放行点数据管理", "fileName": "release-points.json", "model": "ReleasePointModel" }, { "id": "mag-control", "title": "磁条管控区", "category": "磁条交管", "fileName": "mag-control-areas.json", "model": "MagControlAreaModel" } ] } """; }