Refactor IMAP sync caching and improve thread safety

Replaced `IMemoryCache` with a thread-safe `ConcurrentDictionary`
for managing IMAP sync dates in `LimilabsImapEmailService`. Added
`GetLastImapSyncDate` and `SetLastImapSyncDate` methods to handle
cache operations. Updated the `IImapEmailService` interface to
include `GetLastImapSyncDate`. Simplified the `MarkAsSeenAsync`
method signature for better readability. Introduced a private
record type `ImapCacheKey` to encapsulate cache keys.
This commit is contained in:
2026-08-14 08:40:56 +02:00
parent fd234618f7
commit d01a0ceaa6
2 changed files with 30 additions and 9 deletions

View File

@@ -29,5 +29,13 @@ public interface IImapEmailService
long uid,
string folder = "INBOX",
CancellationToken cancellationToken = default);
/// <summary>
/// Gets the last IMAP sync date for the specified account and folder.
/// </summary>
/// <param name="accountId"></param>
/// <param name="folder"></param>
/// <returns></returns>
DateTime? GetLastImapSyncDate(int accountId, string folder = "INBOX");
}
#endif

View File

@@ -5,8 +5,8 @@ using DigitalData.MessagingService.Domain.Entities;
using DigitalData.MessagingService.Infrastructure.Services.Extensions;
using Limilabs.Client.IMAP;
using Limilabs.Mail;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Text;
namespace DigitalData.MessagingService.Infrastructure.Services;
@@ -15,7 +15,7 @@ namespace DigitalData.MessagingService.Infrastructure.Services;
/// IMAP email service using Limilabs Mail.dll.
/// Opens a fresh connection per call — stateless and thread-safe.
/// </summary>
public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger, IRepository<ReceivedEmail> Repository, IMemoryCache Cache) : IImapEmailService
public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger, IRepository<ReceivedEmail> Repository) : IImapEmailService
{
private static readonly string CacheKeyPrefix = Guid.NewGuid().ToString();
@@ -34,7 +34,7 @@ public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger,
// Server-side: only date range; all other filters are applied in-process after cache retrieval
List<ICriterion> criterions = [];
var since = Cache.GetLastImapSyncDate(account.Id, folder);
var since = GetLastImapSyncDate(account.Id, folder);
if (since is not null && since != default)
criterions.Add(Expression.SentSince(since.Value));
@@ -112,7 +112,7 @@ public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger,
emails.Add(email);
Cache.SetLastImapSyncDate(account.Id, folder, operationStartTime);
SetLastImapSyncDate(account.Id, folder, operationStartTime);
}
catch (Exception ex)
{
@@ -136,11 +136,7 @@ public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger,
}
}
public async Task MarkAsSeenAsync(
EmailAccount account,
long uid,
string folder = "INBOX",
CancellationToken cancel = default)
public async Task MarkAsSeenAsync(EmailAccount account, long uid, string folder = "INBOX", CancellationToken cancel = default)
{
using var imap = await OpenAsync(account, folder, cancel);
try
@@ -173,4 +169,21 @@ public class LimilabsImapEmailService(ILogger<LimilabsImapEmailService> Logger,
return imap;
}
#region IMAP Last Sync Date Cache
private readonly ConcurrentDictionary<ImapCacheKey, DateTime> _cache = new();
private record ImapCacheKey(int AccountId, string Folder);
public DateTime? GetLastImapSyncDate(int accountId, string folder = "INBOX")
{
return _cache.GetValueOrDefault(new ImapCacheKey(accountId, folder));
}
private void SetLastImapSyncDate(int accountId, string folder, DateTime date)
{
var key = new ImapCacheKey(accountId, folder);
_cache[key] = date;
}
#endregion
}