242 lines
7.3 KiB
C#
242 lines
7.3 KiB
C#
using Microsoft.Extensions.Options;
|
|
using PrintService.Models;
|
|
using System.IO.Pipes;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace PrintService.Services;
|
|
|
|
/// <summary>
|
|
/// Named pipe server for IPC communication with CLI
|
|
/// </summary>
|
|
public class IpcService : IDisposable
|
|
{
|
|
private readonly AppSettings _settings;
|
|
private readonly PrintQueueService _queueService;
|
|
private readonly FileMonitorService _fileMonitorService;
|
|
private readonly ILogger<IpcService> _logger;
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
private Task? _listenerTask;
|
|
|
|
public IpcService(
|
|
IOptions<AppSettings> settings,
|
|
PrintQueueService queueService,
|
|
FileMonitorService fileMonitorService,
|
|
ILogger<IpcService> logger)
|
|
{
|
|
_settings = settings.Value;
|
|
_queueService = queueService;
|
|
_fileMonitorService = fileMonitorService;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start listening for IPC commands
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
_listenerTask = Task.Run(() => ListenForConnections(_cancellationTokenSource.Token));
|
|
_logger.LogInformation("IPC service started on pipe: {PipeName}", _settings.PipeName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop listening
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
_cancellationTokenSource?.Cancel();
|
|
_listenerTask?.Wait(TimeSpan.FromSeconds(5));
|
|
_logger.LogInformation("IPC service stopped");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Listen for incoming connections
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle a client connection
|
|
/// </summary>
|
|
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<PrintCommand>(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 { }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process a print command
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send response back to client
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Command sent from CLI to service
|
|
/// </summary>
|
|
public class PrintCommand
|
|
{
|
|
public string FilePath { get; set; } = string.Empty;
|
|
public string DocumentType { get; set; } = string.Empty;
|
|
public string? OrderNumber { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Response from service to CLI
|
|
/// </summary>
|
|
public class PrintResponse
|
|
{
|
|
public bool Success { get; set; }
|
|
public string Message { get; set; } = string.Empty;
|
|
}
|