using Microsoft.Extensions.Options; using PrintService.Models; using System.IO.Pipes; using System.Text; using System.Text.Json; namespace PrintService.Services; /// /// Named pipe server for IPC communication with CLI /// public class IpcService : IDisposable { private readonly AppSettings _settings; private readonly PrintQueueService _queueService; private readonly FileMonitorService _fileMonitorService; private readonly ILogger _logger; private CancellationTokenSource? _cancellationTokenSource; private Task? _listenerTask; public IpcService( IOptions settings, PrintQueueService queueService, FileMonitorService fileMonitorService, ILogger logger) { _settings = settings.Value; _queueService = queueService; _fileMonitorService = fileMonitorService; _logger = logger; } /// /// Start listening for IPC commands /// public void Start() { _cancellationTokenSource = new CancellationTokenSource(); _listenerTask = Task.Run(() => ListenForConnections(_cancellationTokenSource.Token)); _logger.LogInformation("IPC service started on pipe: {PipeName}", _settings.PipeName); } /// /// Stop listening /// public void Stop() { _cancellationTokenSource?.Cancel(); _listenerTask?.Wait(TimeSpan.FromSeconds(5)); _logger.LogInformation("IPC service stopped"); } /// /// Listen for incoming connections /// private async Task ListenForConnections(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { NamedPipeServerStream? pipeServer = null; try { pipeServer = new NamedPipeServerStream( _settings.PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Message, PipeOptions.Asynchronous); await pipeServer.WaitForConnectionAsync(cancellationToken); // Handle client in separate task, passing ownership of pipeServer var serverToHandle = pipeServer; pipeServer = null; // Clear reference so we don't dispose it in catch _ = Task.Run(async () => { using (serverToHandle) { await HandleClient(serverToHandle); } }, cancellationToken); } catch (OperationCanceledException) { pipeServer?.Dispose(); break; } catch (Exception ex) { _logger.LogError(ex, "Error in IPC listener"); pipeServer?.Dispose(); await Task.Delay(1000, cancellationToken); } } } /// /// Handle a client connection /// private async Task HandleClient(NamedPipeServerStream pipeServer) { try { if (!pipeServer.IsConnected) { return; } // Read command var buffer = new byte[4096]; var bytesRead = await pipeServer.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { return; // Client disconnected } var message = Encoding.UTF8.GetString(buffer, 0, bytesRead); if (_settings.VerboseLogging) { _logger.LogDebug("Received IPC message: {Message}", message); } var command = JsonSerializer.Deserialize(message); if (command == null) { await SendResponse(pipeServer, false, "Invalid command format"); return; } // Process command var result = ProcessCommand(command); await SendResponse(pipeServer, result.Success, result.Message); } catch (ObjectDisposedException) { // Client disconnected, this is normal if (_settings.VerboseLogging) { _logger.LogDebug("Client disconnected"); } } catch (IOException ex) { // Pipe communication error if (_settings.VerboseLogging) { _logger.LogDebug(ex, "Pipe communication error"); } } catch (Exception ex) { _logger.LogError(ex, "Error handling IPC client"); try { if (pipeServer.IsConnected) { await SendResponse(pipeServer, false, $"Error: {ex.Message}"); } } catch { } } } /// /// Process a print command /// private (bool Success, string Message) ProcessCommand(PrintCommand command) { try { if (string.IsNullOrEmpty(command.FilePath) || !File.Exists(command.FilePath)) { return (false, $"File not found: {command.FilePath}"); } if (string.IsNullOrEmpty(command.DocumentType)) { return (false, "Document type is required"); } // Create job and enqueue var job = _fileMonitorService.CreateJobFromFile( command.FilePath, command.DocumentType, command.OrderNumber); _queueService.Enqueue(job); return (true, $"Job {job.Id} queued successfully"); } catch (Exception ex) { _logger.LogError(ex, "Failed to process command"); return (false, ex.Message); } } /// /// Send response back to client /// private async Task SendResponse(NamedPipeServerStream pipeServer, bool success, string message) { var response = new PrintResponse { Success = success, Message = message }; var json = JsonSerializer.Serialize(response); var bytes = Encoding.UTF8.GetBytes(json); await pipeServer.WriteAsync(bytes, 0, bytes.Length); await pipeServer.FlushAsync(); } public void Dispose() { Stop(); _cancellationTokenSource?.Dispose(); } } /// /// Command sent from CLI to service /// public class PrintCommand { public string FilePath { get; set; } = string.Empty; public string DocumentType { get; set; } = string.Empty; public string? OrderNumber { get; set; } } /// /// Response from service to CLI /// public class PrintResponse { public bool Success { get; set; } public string Message { get; set; } = string.Empty; }