using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Domain.Processing
{
public class FolderQueueSettings
{
public string QueueFolderPath { get; set; }
public string ProcessedFolderPath { get; set; }
public string FailedFolderPath { get; set; }
public int MaxRetryAttempts { get; set; } = 3;
public int RetryDelayMs { get; set; } = 2000;
public void Validate()
{
if (string.IsNullOrWhiteSpace(QueueFolderPath))
throw new ArgumentException("QueueFolderPath must be specified.");
Directory.CreateDirectory(QueueFolderPath);
if (!string.IsNullOrWhiteSpace(ProcessedFolderPath))
Directory.CreateDirectory(ProcessedFolderPath);
if (!string.IsNullOrWhiteSpace(FailedFolderPath))
Directory.CreateDirectory(FailedFolderPath);
}
}
public interface ISerializer<T>
{
string Serialize(T item);
T Deserialize(string serialized);
}
/// <summary>
/// Generic queue based on folder storage.
/// Files are created in QueueFolderPath and processed sequentially.
/// </summary>
public class FolderQueue<T>
{
private readonly FolderQueueSettings _settings;
private readonly ISerializer<T> _serializer;
private readonly ILogger<FolderQueue<T>> _logger;
public FolderQueue(FolderQueueSettings settings, ISerializer<T> serializer, ILogger<FolderQueue<T>> logger = null)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
_logger = logger;
_settings.Validate();
}
public async Task AddToQueueAsync(T item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
var serialized = _serializer.Serialize(item);
var fileName = Path.Combine(_settings.QueueFolderPath, $"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid()}.json");
await File.WriteAllTextAsync(fileName, serialized, Encoding.UTF8);
_logger?.LogInformation("Item added to queue: {FileName}", fileName);
}
public async Task ProcessQueueAsync(Func<T, Task> processor)
{
if (processor == null)
throw new ArgumentNullException(nameof(processor));
var files = Directory.GetFiles(_settings.QueueFolderPath, "*.json");
_logger?.LogInformation("Processing {Count} items from queue", files.Length);
foreach (var file in files)
{
await ProcessFileAsync(file, processor);
}
}
private async Task ProcessFileAsync(string filePath, Func<T, Task> processor)
{
var retryCount = 0;
while (retryCount <= _settings.MaxRetryAttempts)
{
try
{
var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
var item = _serializer.Deserialize(content);
await processor(item);
await MoveToProcessedFolderAsync(filePath);
_logger?.LogInformation("Successfully processed item: {FileName}", Path.GetFileName(filePath));
return;
}
catch (Exception ex)
{
retryCount++;
_logger?.LogWarning(ex, "Failed to process file {FileName} (attempt {Attempt}/{Max})", Path.GetFileName(filePath), retryCount, _settings.MaxRetryAttempts + 1);
if (retryCount > _settings.MaxRetryAttempts)
{
await MoveToFailedFolderAsync(filePath);
_logger?.LogError(ex, "File moved to failed folder after {Retries} attempts: {FileName}", retryCount, Path.GetFileName(filePath));
return;
}
if (_settings.RetryDelayMs > 0)
await Task.Delay(_settings.RetryDelayMs);
}
}
}
private async Task MoveToProcessedFolderAsync(string filePath)
{
if (!string.IsNullOrWhiteSpace(_settings.ProcessedFolderPath))
{
var fileName = Path.GetFileName(filePath);
var dest = Path.Combine(_settings.ProcessedFolderPath, fileName);
if (File.Exists(dest)) File.Delete(dest);
File.Move(filePath, dest);
}
else
{
File.Delete(filePath);
}
}
private async Task MoveToFailedFolderAsync(string filePath)
{
if (!string.IsNullOrWhiteSpace(_settings.FailedFolderPath))
{
var fileName = Path.GetFileName(filePath);
var dest = Path.Combine(_settings.FailedFolderPath, fileName);
if (File.Exists(dest)) File.Delete(dest);
File.Move(filePath, dest);
}
}
}
}
Example mail queue
using System.Net.Mail;
using Microsoft.Extensions.Logging;
namespace Domain.Mail
{
public class MailQueueService : IMailQueueService
{
private readonly FolderQueue<MailMessage> _queue;
private readonly ILogger<MailQueueService> _logger;
public MailQueueService(MailQueueSettings settings, IMailSerializer mailSerializer, ILogger<MailQueueService> logger = null)
{
var folderSettings = new FolderQueueSettings
{
QueueFolderPath = settings.QueueFolderPath,
FailedFolderPath = settings.FailedFolderPath,
ProcessedFolderPath = settings.ProcessedFolderPath,
MaxRetryAttempts = settings.MaxRetryAttempts,
RetryDelayMs = settings.RetryDelayMs
};
_queue = new FolderQueue<MailMessage>(folderSettings, mailSerializer, logger);
_logger = logger;
}
public Task AddToQueueAsync(MailMessage message) => _queue.AddToQueueAsync(message);
public Task ProcessQueueAsync() =>
_queue.ProcessQueueAsync(async mail =>
{
using var smtp = new SmtpClientWrapper(new MailQueueSettings()); // of gebruik _settings
await smtp.SendMailAsync(mail);
});
}
}
Simple Json serializer
using System.Text.Json;
public class JsonSerializerAdapter<T> : ISerializer<T>
{
public string Serialize(T item) => JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true });
public T Deserialize(string serialized) => JsonSerializer.Deserialize<T>(serialized);
}
979200cookie-checkC# FolderQueue