176 lines
5.2 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|