using System.Diagnostics; using System.Text.Json; using YamlDotNet.RepresentationModel; namespace Neta.Tray; public sealed class RuntimeBootstrapInfo { public string DataDir { get; set; } = string.Empty; public string ControlBaseUrl { get; set; } = string.Empty; public string ControlSecret { get; set; } = string.Empty; public int Pid { get; set; } public bool Ready { get; set; } } public static class RuntimeInfoStore { public static RuntimeBootstrapInfo ParseConfigAndRuntimeInfo(string configYaml, string runtimeInfoJson) { var dataDir = GetDataDirFromConfigYaml(configYaml); var runtime = JsonSerializer.Deserialize(runtimeInfoJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; runtime.DataDir = dataDir; return runtime; } public static RuntimeBootstrapInfo? LoadFromInstalledConfig(string baseDirectory) { var configPath = Path.Combine(baseDirectory, "config.yaml"); if (!File.Exists(configPath)) return null; var configYaml = File.ReadAllText(configPath); var dataDir = GetDataDirFromConfigYaml(configYaml); var runtimeInfoPath = Path.Combine(dataDir, "runtime-info.json"); if (!File.Exists(runtimeInfoPath)) return null; return ParseConfigAndRuntimeInfo(configYaml, File.ReadAllText(runtimeInfoPath)); } public static RuntimeBootstrapInfo WaitUntilAvailable(string baseDirectory, TimeSpan timeout) { var deadline = DateTime.UtcNow + timeout; while (DateTime.UtcNow < deadline) { var loaded = LoadFromInstalledConfig(baseDirectory); if (loaded is not null) return loaded; Thread.Sleep(200); } throw new TimeoutException("等待 runtime-info.json 超时"); } public static bool HasRuntimeLock(string baseDirectory) { var configPath = Path.Combine(baseDirectory, "config.yaml"); if (!File.Exists(configPath)) return false; var dataDir = GetDataDirFromConfigYaml(File.ReadAllText(configPath)); return File.Exists(Path.Combine(dataDir, "neta.lock")); } public static void OpenLogs(string baseDirectory) { var runtime = LoadFromInstalledConfig(baseDirectory); if (runtime is null) return; Process.Start(new ProcessStartInfo(Path.Combine(runtime.DataDir, "logs")) { UseShellExecute = true }); } public static void OpenConfigDir(string baseDirectory) { Process.Start(new ProcessStartInfo(baseDirectory) { UseShellExecute = true }); } private static string GetDataDirFromConfigYaml(string configYaml) { using var input = new StringReader(configYaml); var yaml = new YamlStream(); yaml.Load(input); var root = (YamlMappingNode)yaml.Documents[0].RootNode; return ((YamlScalarNode)((YamlMappingNode)root.Children[new YamlScalarNode("data")]).Children[new YamlScalarNode("dir")]).Value!.Replace("\\\\", "\\"); } }