Files
Print_server/Inspectron.Epson/PrintServer/JobSources/SignalRPrintJobSource.cs
2026-01-20 11:41:01 +01:00

130 lines
4.5 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
namespace Inspectron.Epson.PrintServer.JobSources;
public class SignalRPrintJobSource: IPrintJobSource
{
private readonly EpsonPrintServiceConfiguration _groupConfiguration;
private readonly ILogger _logger;
private readonly HubConnection _connection;
private readonly Channel<PrintJob> _printJobChannel = Channel.CreateUnbounded<PrintJob>();
public SignalRPrintJobSource(EpsonPrintServiceConfiguration groupConfiguration, ILogger logger)
{
_groupConfiguration = groupConfiguration;
_logger = logger;
var hubUrl = $"{groupConfiguration.ApiUrl?.TrimEnd('/')}/hubs/printer-servers";
_connection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect(new InfiniteRetryPolicy())
.Build();
// Register handlers BEFORE starting connection
_connection.On<PrintJobFromSignalR>("PrintJob", OnPrintJobReceived);
_connection.Reconnecting += OnReconnecting;
_connection.Reconnected += OnReconnected;
_connection.Closed += OnClosed;
_ = InitializeConnectionAsync();
}
private async Task InitializeConnectionAsync()
{
try
{
await _connection.StartAsync();
await _connection.InvokeAsync("JoinGroup", _groupConfiguration.GroupId);
_logger.LogInformation("SignalR connection started and joined group {GroupId}.", _groupConfiguration.GroupId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize SignalR connection.");
}
}
private Task OnReconnecting(Exception? exception)
{
_logger.LogWarning(exception, "SignalR connection lost. Reconnecting...");
return Task.CompletedTask;
}
private async Task OnReconnected(string? connectionId)
{
_logger.LogInformation("SignalR reconnected with connection ID: {ConnectionId}. Rejoining group...", connectionId);
try
{
await _connection.InvokeAsync("JoinGroup", _groupConfiguration.GroupId);
_logger.LogInformation("Rejoined SignalR group {GroupId} after reconnection.", _groupConfiguration.GroupId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to rejoin group after reconnection.");
}
}
private async Task OnClosed(Exception? exception)
{
_logger.LogError(exception, "SignalR connection closed. Attempting manual restart...");
// Manual reconnection loop if automatic reconnection exhausted (shouldn't happen with infinite retry)
await Task.Delay(5000); // Wait before retry
try
{
await _connection.StartAsync();
await _connection.InvokeAsync("JoinGroup", _groupConfiguration.GroupId);
_logger.LogInformation("Manually reconnected to SignalR.");
}
catch (Exception ex)
{
_logger.LogError(ex, "Manual reconnection failed. Will retry on next connection closed event.");
}
}
private void OnPrintJobReceived(PrintJobFromSignalR args)
{
_logger.LogInformation("Received print job for working area: {PrintJob}", JsonSerializer.Serialize(args));
_printJobChannel.Writer.TryWrite(new PrintJob
{
IP = args.PrinterIp,
Document = args,
});
}
public async Task<PrintJob> GetNextJobAsync(CancellationToken cancellationToken)
{
return await _printJobChannel.Reader.ReadAsync(cancellationToken);
}
public class PrintJobFromSignalR
{
[JsonPropertyName("printerIp")]
public string PrinterIp { get; set; }
[JsonPropertyName("logoUrl")]
public string? LogoUrl { get; set; }
[JsonPropertyName("receiptType")]
public int ReceiptType { get; set; }
[JsonPropertyName("content")]
public string Content { get; set; }
}
private class InfiniteRetryPolicy : IRetryPolicy
{
public TimeSpan? NextRetryDelay(RetryContext retryContext)
{
// Exponential backoff with a cap at 30 seconds
var delay = Math.Min(Math.Pow(2, retryContext.PreviousRetryCount), 30);
return TimeSpan.FromSeconds(delay);
}
}
}