using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace CtrEditor.Services { /// /// Servidor TCP que captura y retransmite mensajes de debug console /// Permite al MCP Proxy escuchar los logs de debug de CtrEditor /// public class DebugConsoleServer : IDisposable { private readonly int _port; private TcpListener _tcpListener; private bool _isRunning; private CancellationTokenSource _cancellationTokenSource; private readonly object _lockObject = new object(); private readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); private readonly ConcurrentBag _connectedClients = new ConcurrentBag(); // Custom TraceListener para capturar Debug.WriteLine private DebugTraceListener _traceListener; public DebugConsoleServer(int port = 5007) { _port = port; _cancellationTokenSource = new CancellationTokenSource(); } /// /// Inicia el servidor de debug console /// public void Start() { if (_isRunning) return; try { _tcpListener = new TcpListener(IPAddress.Loopback, _port); _tcpListener.Start(); _isRunning = true; // Instalar el trace listener personalizado solo para Trace _traceListener = new DebugTraceListener(this); Trace.Listeners.Add(_traceListener); Trace.WriteLine($"[Debug Console Server] Servidor de debug iniciado en puerto {_port}"); // Procesar conexiones en background _ = Task.Run(async () => await AcceptConnectionsAsync(_cancellationTokenSource.Token)); // Procesar cola de mensajes _ = Task.Run(async () => await ProcessMessageQueueAsync(_cancellationTokenSource.Token)); } catch (Exception ex) { Debug.WriteLine($"[Debug Console Server] Error al iniciar servidor: {ex.Message}"); throw; } } /// /// Detiene el servidor de debug console /// public void Stop() { if (!_isRunning) return; lock (_lockObject) { if (!_isRunning) return; _isRunning = false; _cancellationTokenSource?.Cancel(); // Remover el trace listener if (_traceListener != null) { Trace.Listeners.Remove(_traceListener); _traceListener = null; } _tcpListener?.Stop(); // Cerrar todas las conexiones de clientes foreach (var client in _connectedClients) { try { client?.Close(); } catch { } } Debug.WriteLine("[Debug Console Server] Servidor de debug detenido"); } } /// /// Acepta conexiones de clientes /// private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) { try { while (_isRunning && !cancellationToken.IsCancellationRequested) { Debug.WriteLine("[Debug Console Server] Esperando conexión de cliente..."); var tcpClient = await _tcpListener.AcceptTcpClientAsync(); _connectedClients.Add(tcpClient); Debug.WriteLine("[Debug Console Server] Cliente debug conectado"); // Manejar cliente en background _ = Task.Run(async () => await HandleClientAsync(tcpClient, cancellationToken)); } } catch (Exception ex) { if (_isRunning) { Debug.WriteLine($"[Debug Console Server] Error aceptando conexión: {ex.Message}"); } } } /// /// Maneja un cliente conectado /// private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken) { try { var stream = client.GetStream(); // Enviar mensaje de bienvenida var welcomeMessage = $"[Debug Console Server] Conectado al servidor de debug de CtrEditor en puerto {_port}\n"; await SendMessageToClientAsync(stream, welcomeMessage); // Mantener conexión viva hasta cancelación while (_isRunning && client.Connected && !cancellationToken.IsCancellationRequested) { await Task.Delay(1000, cancellationToken); } } catch (Exception ex) { Debug.WriteLine($"[Debug Console Server] Error en cliente debug: {ex.Message}"); } finally { try { client?.Close(); } catch { } Debug.WriteLine("[Debug Console Server] Cliente debug desconectado"); } } /// /// Procesa la cola de mensajes y los envía a todos los clientes conectados /// private async Task ProcessMessageQueueAsync(CancellationToken cancellationToken) { try { while (_isRunning && !cancellationToken.IsCancellationRequested) { if (_messageQueue.TryDequeue(out string message)) { await BroadcastMessageAsync(message); } else { await Task.Delay(50, cancellationToken); // Pequeña pausa si no hay mensajes } } } catch (Exception ex) { if (_isRunning) { Debug.WriteLine($"[Debug Console Server] Error procesando cola de mensajes: {ex.Message}"); } } } /// /// Envía un mensaje a todos los clientes conectados /// private async Task BroadcastMessageAsync(string message) { var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); var formattedMessage = $"[{timestamp}] {message}\n"; var clientsToRemove = new List(); foreach (var client in _connectedClients) { try { if (client.Connected) { var stream = client.GetStream(); await SendMessageToClientAsync(stream, formattedMessage); } else { clientsToRemove.Add(client); } } catch { clientsToRemove.Add(client); } } // Remover clientes desconectados // Nota: ConcurrentBag no permite eliminación directa, pero no es crítico // Los clientes desconectados se manejan en HandleClientAsync } /// /// Envía un mensaje a un cliente específico /// private async Task SendMessageToClientAsync(NetworkStream stream, string message) { var data = Encoding.UTF8.GetBytes(message); await stream.WriteAsync(data, 0, data.Length); await stream.FlushAsync(); } /// /// Agrega un mensaje a la cola para transmisión /// public void QueueMessage(string message) { if (_isRunning) { _messageQueue.Enqueue(message); } } public void Dispose() { Stop(); _cancellationTokenSource?.Dispose(); } } /// /// TraceListener personalizado que captura mensajes de Debug.WriteLine /// y los envía al servidor de debug console /// internal class DebugTraceListener : TraceListener { private readonly DebugConsoleServer _server; public DebugTraceListener(DebugConsoleServer server) { _server = server; } public override void Write(string message) { _server?.QueueMessage(message); } public override void WriteLine(string message) { _server?.QueueMessage(message); } } }