Files
2026-05-29 10:46:43 -05:00

176 lines
5.2 KiB
C#

using Microsoft.Extensions.Options;
using PrintService.Models;
using System.Collections.Concurrent;
using System.Text.Json;
namespace PrintService.Services;
/// <summary>
/// Manages the print job queue with persistence
/// </summary>
public class PrintQueueService
{
private readonly ConcurrentQueue<PrintJob> _queue = new();
private readonly AppSettings _settings;
private readonly ILogger<PrintQueueService> _logger;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public PrintQueueService(IOptions<AppSettings> settings, ILogger<PrintQueueService> logger)
{
_settings = settings.Value;
_logger = logger;
LoadQueueState();
}
/// <summary>
/// Enqueue a new print job
/// </summary>
public void Enqueue(PrintJob job)
{
_queue.Enqueue(job);
_logger.LogInformation("Job {JobId} enqueued for document type: {DocType}", job.Id, job.DocumentType);
SaveQueueState();
}
/// <summary>
/// Try to dequeue the next pending job
/// </summary>
public bool TryDequeue(out PrintJob? job)
{
bool result = _queue.TryDequeue(out job);
if (result && job != null)
{
_logger.LogInformation("Job {JobId} dequeued", job.Id);
SaveQueueState();
}
return result;
}
/// <summary>
/// Peek at the next job without removing it
/// </summary>
public bool TryPeek(out PrintJob? job)
{
return _queue.TryPeek(out job);
}
/// <summary>
/// Get count of jobs in queue
/// </summary>
public int Count => _queue.Count;
/// <summary>
/// Get all jobs in the queue (snapshot)
/// </summary>
public PrintJob[] GetAllJobs()
{
return _queue.ToArray();
}
/// <summary>
/// Save queue state to disk
/// </summary>
private void SaveQueueState()
{
try
{
_semaphore.Wait();
var jobs = _queue.ToArray();
var json = JsonSerializer.Serialize(jobs, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_settings.QueueStateFile, json);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save queue state");
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// Load queue state from disk
/// </summary>
private void LoadQueueState()
{
try
{
if (File.Exists(_settings.QueueStateFile))
{
var json = File.ReadAllText(_settings.QueueStateFile);
var jobs = JsonSerializer.Deserialize<PrintJob[]>(json);
if (jobs != null)
{
foreach (var job in jobs)
{
// Only reload pending or retry-scheduled jobs
if (job.Status == PrintJobStatus.Pending || job.Status == PrintJobStatus.RetryScheduled)
{
_queue.Enqueue(job);
}
}
_logger.LogInformation("Loaded {Count} jobs from queue state", _queue.Count);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load queue state");
}
}
/// <summary>
/// Requeue a job (for retry)
/// </summary>
public void Requeue(PrintJob job)
{
job.RetryCount++;
job.Status = PrintJobStatus.RetryScheduled;
job.UpdatedAt = DateTime.Now;
if (job.RetryCount >= _settings.MaxRetryAttempts)
{
_logger.LogWarning("Job {JobId} exceeded max retries, moving to error folder", job.Id);
MoveToErrorFolder(job);
}
else
{
_logger.LogInformation("Requeueing job {JobId}, attempt {Retry}", job.Id, job.RetryCount);
_queue.Enqueue(job);
SaveQueueState();
}
}
/// <summary>
/// Move a failed job to error folder
/// </summary>
private void MoveToErrorFolder(PrintJob job)
{
try
{
var errorFileName = Path.Combine(_settings.ErrorPath,
$"{Path.GetFileNameWithoutExtension(job.OriginalFilename)}_{job.Id}.txt");
if (File.Exists(job.QueuedFilePath))
{
File.Move(job.QueuedFilePath, errorFileName);
}
// Save error details
var errorInfo = Path.Combine(_settings.ErrorPath,
$"{Path.GetFileNameWithoutExtension(job.OriginalFilename)}_{job.Id}_error.json");
var json = JsonSerializer.Serialize(job, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(errorInfo, json);
job.Status = PrintJobStatus.Failed;
SaveQueueState();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to move job {JobId} to error folder", job.Id);
}
}
}