{"id":9792,"date":"2025-10-08T08:10:45","date_gmt":"2025-10-08T07:10:45","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=9792"},"modified":"2025-10-08T08:10:58","modified_gmt":"2025-10-08T07:10:58","slug":"c-folderqueue","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/c-folderqueue\/","title":{"rendered":"C# FolderQueue"},"content":{"rendered":"\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Logging;\n\nnamespace Domain.Processing\n{\n    public class FolderQueueSettings\n    {\n        public string QueueFolderPath { get; set; }\n        public string ProcessedFolderPath { get; set; }\n        public string FailedFolderPath { get; set; }\n\n        public int MaxRetryAttempts { get; set; } = 3;\n        public int RetryDelayMs { get; set; } = 2000;\n\n        public void Validate()\n        {\n            if (string.IsNullOrWhiteSpace(QueueFolderPath))\n                throw new ArgumentException(\"QueueFolderPath must be specified.\");\n\n            Directory.CreateDirectory(QueueFolderPath);\n\n            if (!string.IsNullOrWhiteSpace(ProcessedFolderPath))\n                Directory.CreateDirectory(ProcessedFolderPath);\n\n            if (!string.IsNullOrWhiteSpace(FailedFolderPath))\n                Directory.CreateDirectory(FailedFolderPath);\n        }\n    }\n\n    public interface ISerializer&lt;T>\n    {\n        string Serialize(T item);\n        T Deserialize(string serialized);\n    }\n\n    \/\/\/ &lt;summary>\n    \/\/\/ Generic queue based on folder storage.\n    \/\/\/ Files are created in QueueFolderPath and processed sequentially.\n    \/\/\/ &lt;\/summary>\n    public class FolderQueue&lt;T>\n    {\n        private readonly FolderQueueSettings _settings;\n        private readonly ISerializer&lt;T> _serializer;\n        private readonly ILogger&lt;FolderQueue&lt;T>> _logger;\n\n        public FolderQueue(FolderQueueSettings settings, ISerializer&lt;T> serializer, ILogger&lt;FolderQueue&lt;T>> logger = null)\n        {\n            _settings = settings ?? throw new ArgumentNullException(nameof(settings));\n            _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));\n            _logger = logger;\n            _settings.Validate();\n        }\n\n        public async Task AddToQueueAsync(T item)\n        {\n            if (item == null)\n                throw new ArgumentNullException(nameof(item));\n\n            var serialized = _serializer.Serialize(item);\n            var fileName = Path.Combine(_settings.QueueFolderPath, $\"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid()}.json\");\n            await File.WriteAllTextAsync(fileName, serialized, Encoding.UTF8);\n\n            _logger?.LogInformation(\"Item added to queue: {FileName}\", fileName);\n        }\n\n        public async Task ProcessQueueAsync(Func&lt;T, Task> processor)\n        {\n            if (processor == null)\n                throw new ArgumentNullException(nameof(processor));\n\n            var files = Directory.GetFiles(_settings.QueueFolderPath, \"*.json\");\n            _logger?.LogInformation(\"Processing {Count} items from queue\", files.Length);\n\n            foreach (var file in files)\n            {\n                await ProcessFileAsync(file, processor);\n            }\n        }\n\n        private async Task ProcessFileAsync(string filePath, Func&lt;T, Task> processor)\n        {\n            var retryCount = 0;\n\n            while (retryCount &lt;= _settings.MaxRetryAttempts)\n            {\n                try\n                {\n                    var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8);\n                    var item = _serializer.Deserialize(content);\n\n                    await processor(item);\n\n                    await MoveToProcessedFolderAsync(filePath);\n                    _logger?.LogInformation(\"Successfully processed item: {FileName}\", Path.GetFileName(filePath));\n                    return;\n                }\n                catch (Exception ex)\n                {\n                    retryCount++;\n                    _logger?.LogWarning(ex, \"Failed to process file {FileName} (attempt {Attempt}\/{Max})\", Path.GetFileName(filePath), retryCount, _settings.MaxRetryAttempts + 1);\n\n                    if (retryCount > _settings.MaxRetryAttempts)\n                    {\n                        await MoveToFailedFolderAsync(filePath);\n                        _logger?.LogError(ex, \"File moved to failed folder after {Retries} attempts: {FileName}\", retryCount, Path.GetFileName(filePath));\n                        return;\n                    }\n\n                    if (_settings.RetryDelayMs > 0)\n                        await Task.Delay(_settings.RetryDelayMs);\n                }\n            }\n        }\n\n        private async Task MoveToProcessedFolderAsync(string filePath)\n        {\n            if (!string.IsNullOrWhiteSpace(_settings.ProcessedFolderPath))\n            {\n                var fileName = Path.GetFileName(filePath);\n                var dest = Path.Combine(_settings.ProcessedFolderPath, fileName);\n                if (File.Exists(dest)) File.Delete(dest);\n                File.Move(filePath, dest);\n            }\n            else\n            {\n                File.Delete(filePath);\n            }\n        }\n\n        private async Task MoveToFailedFolderAsync(string filePath)\n        {\n            if (!string.IsNullOrWhiteSpace(_settings.FailedFolderPath))\n            {\n                var fileName = Path.GetFileName(filePath);\n                var dest = Path.Combine(_settings.FailedFolderPath, fileName);\n                if (File.Exists(dest)) File.Delete(dest);\n                File.Move(filePath, dest);\n            }\n        }\n    }\n}\n<\/pre><\/div>\n\n\n\n<p>Example mail queue<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System.Net.Mail;\nusing Microsoft.Extensions.Logging;\n\nnamespace Domain.Mail\n{\n    public class MailQueueService : IMailQueueService\n    {\n        private readonly FolderQueue&lt;MailMessage> _queue;\n        private readonly ILogger&lt;MailQueueService> _logger;\n\n        public MailQueueService(MailQueueSettings settings, IMailSerializer mailSerializer, ILogger&lt;MailQueueService> logger = null)\n        {\n            var folderSettings = new FolderQueueSettings\n            {\n                QueueFolderPath = settings.QueueFolderPath,\n                FailedFolderPath = settings.FailedFolderPath,\n                ProcessedFolderPath = settings.ProcessedFolderPath,\n                MaxRetryAttempts = settings.MaxRetryAttempts,\n                RetryDelayMs = settings.RetryDelayMs\n            };\n\n            _queue = new FolderQueue&lt;MailMessage>(folderSettings, mailSerializer, logger);\n            _logger = logger;\n        }\n\n        public Task AddToQueueAsync(MailMessage message) => _queue.AddToQueueAsync(message);\n\n        public Task ProcessQueueAsync() =>\n            _queue.ProcessQueueAsync(async mail =>\n            {\n                using var smtp = new SmtpClientWrapper(new MailQueueSettings()); \/\/ of gebruik _settings\n                await smtp.SendMailAsync(mail);\n            });\n    }\n}\n<\/pre><\/div>\n\n\n\n<p>Simple Json serializer<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"csharp\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">using System.Text.Json;\n\npublic class JsonSerializerAdapter&lt;T> : ISerializer&lt;T>\n{\n    public string Serialize(T item) => JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true });\n    public T Deserialize(string serialized) => JsonSerializer.Deserialize&lt;T>(serialized);\n}\n<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Example mail queue Simple Json serializer<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9792","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9792","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=9792"}],"version-history":[{"count":1,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9792\/revisions"}],"predecessor-version":[{"id":9793,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9792\/revisions\/9793"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=9792"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=9792"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=9792"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}