using System.Diagnostics; using System.Text.Json; namespace XFE.SeAgent.Cli; public sealed record AgentEndpoint(string Version, int ProtocolVersion, string PipeName, int Pid, DateTimeOffset StartTimeUtc, string ExecutablePath, DateTimeOffset CreatedUtc); public sealed record ProcessIdentity(string ExecutablePath, DateTimeOffset StartTimeUtc); public interface IProcessInspector { ProcessIdentity? Inspect(int pid); } public sealed class LocalProcessInspector : IProcessInspector { public ProcessIdentity? Inspect(int pid) { try { using var process = Process.GetProcessById(pid); if (process.HasExited || process.MainModule?.FileName is not string executable) return null; return new(executable, new DateTimeOffset(process.StartTime.ToUniversalTime())); } catch (ArgumentException) { return null; } catch (InvalidOperationException) { return null; } catch (System.ComponentModel.Win32Exception) { return null; } } } public sealed class EndpointDiscovery(IProcessInspector? inspector = null) { private readonly IProcessInspector inspector = inspector ?? new LocalProcessInspector(); private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web); public static string DefaultDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "XFE", "SpaceEngineersAgent", "endpoints"); public IReadOnlyList Discover(string? directory = null) { directory ??= DefaultDirectory; if (!Directory.Exists(directory)) return []; var found = new List(); foreach (string file in Directory.EnumerateFiles(directory, "*.json").Take(1024)) { try { if (new FileInfo(file).Length > 65536) continue; var endpoint = JsonSerializer.Deserialize(File.ReadAllText(file), Json); if (endpoint == null || endpoint.ProtocolVersion != 1 || endpoint.Pid <= 0 || string.IsNullOrWhiteSpace(endpoint.Version) || endpoint.PipeName != "XFE.SE.Agent." + endpoint.Pid || !Path.IsPathFullyQualified(endpoint.ExecutablePath) || Path.GetFileNameWithoutExtension(file) != endpoint.Pid.ToString(System.Globalization.CultureInfo.InvariantCulture)) continue; var process = inspector.Inspect(endpoint.Pid); if (process == null || process.StartTimeUtc.UtcTicks != endpoint.StartTimeUtc.UtcTicks || !Path.GetFullPath(process.ExecutablePath).Equals(Path.GetFullPath(endpoint.ExecutablePath), StringComparison.OrdinalIgnoreCase)) continue; found.Add(endpoint); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException or ArgumentException) { } } return found.OrderBy(item => item.Pid).ToArray(); } }