using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Gott.Services; public class RemoteConnection { public string Id { get; set; } public string Hostname { get; set; } public string Username { get; set; } public string OsVersion { get; set; } public DateTime ConnectedAt { get; set; } public string AssignedSessionId { get; set; } public WebSocket Socket { get; set; } // Für synchrone Command-Execution: ein Befehl wird gesendet, Ergebnis abgewartet public SemaphoreSlim CommandLock { get; } = new SemaphoreSlim(1, 1); public TaskCompletionSource PendingResult { get; set; } } public class RemoteCommandResult { public int ExitCode { get; set; } public string Output { get; set; } public string Error { get; set; } } public class RemoteConnectionManager { private readonly ConcurrentDictionary _connections = new(); public RemoteConnection Register(WebSocket socket) { var conn = new RemoteConnection { Id = Guid.NewGuid().ToString("N")[..12], ConnectedAt = DateTime.UtcNow, Socket = socket }; _connections[conn.Id] = conn; return conn; } public void Unregister(string connectionId) { if (_connections.TryRemove(connectionId, out var conn)) { // Falls ein Befehl auf Antwort wartet, abbrechen conn.PendingResult?.TrySetCanceled(); conn.AssignedSessionId = null; } } public List GetAll() { return _connections.Values.ToList(); } public RemoteConnection GetById(string connectionId) { _connections.TryGetValue(connectionId, out var conn); return conn; } public RemoteConnection GetBySessionId(string sessionId) { return _connections.Values.FirstOrDefault(c => c.AssignedSessionId == sessionId); } public bool Assign(string connectionId, string sessionId) { if (!_connections.TryGetValue(connectionId, out var conn)) return false; conn.AssignedSessionId = sessionId; return true; } public bool Unassign(string connectionId) { if (!_connections.TryGetValue(connectionId, out var conn)) return false; conn.AssignedSessionId = null; return true; } /// /// Sendet einen Befehl an den Remote-Client und wartet auf das Ergebnis. /// public async Task ExecuteCommand(string connectionId, string command, int timeoutSeconds = 180, CancellationToken ct = default) { if (!_connections.TryGetValue(connectionId, out var conn)) return new RemoteCommandResult { ExitCode = -1, Error = "Verbindung nicht gefunden" }; if (conn.Socket.State != WebSocketState.Open) return new RemoteCommandResult { ExitCode = -1, Error = "WebSocket nicht verbunden" }; await conn.CommandLock.WaitAsync(ct); try { conn.PendingResult = new TaskCompletionSource(); // Befehl senden var msg = JsonSerializer.Serialize(new { type = "execute", command }); var bytes = Encoding.UTF8.GetBytes(msg); await conn.Socket.SendAsync(bytes, WebSocketMessageType.Text, true, ct); // Auf Ergebnis warten (mit Timeout) using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds)); try { var registration = cts.Token.Register(() => conn.PendingResult.TrySetCanceled()); var result = await conn.PendingResult.Task; registration.Dispose(); return result; } catch (TaskCanceledException) { return new RemoteCommandResult { ExitCode = -1, Error = $"Timeout nach {timeoutSeconds} Sekunden" }; } } finally { conn.PendingResult = null; conn.CommandLock.Release(); } } /// /// Wird vom WebSocket-Receiver aufgerufen wenn ein Ergebnis vom Client kommt. /// public void HandleResult(string connectionId, RemoteCommandResult result) { if (_connections.TryGetValue(connectionId, out var conn)) { conn.PendingResult?.TrySetResult(result); } } }