110 lines
4.7 KiB
C#
110 lines
4.7 KiB
C#
using DigitalData.MessagingService.Application.Common.Interfaces;
|
|
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
|
|
using DigitalData.MessagingService.Application.Common.Options;
|
|
using DigitalData.MessagingService.Domain.Entities;
|
|
using DigitalData.MessagingService.Domain.Enums;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace DigitalData.MessagingService.Infrastructure.Services.Background;
|
|
|
|
public class EmailSyncWorker(IOptions<EmailAccountsOptions> Options, IServiceProvider Provider, ILogger<EmailSyncWorker> Logger) : BackgroundService
|
|
{
|
|
private readonly string DefaultFolder = "INBOX";
|
|
|
|
/// <summary>
|
|
/// Signals the current <see cref="Task.Delay"/> to complete immediately,
|
|
/// causing the sync loop to start the next cycle without waiting.
|
|
/// A new TCS is created at the start of each delay so repeated triggers work correctly.
|
|
/// </summary>
|
|
private volatile TaskCompletionSource<bool> _syncTrigger = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
/// <summary>
|
|
/// Triggers an immediate sync cycle by completing the current delay early.
|
|
/// Safe to call from any thread or HTTP request at any time.
|
|
/// If a sync is already running, the trigger is ignored — the next cycle starts normally.
|
|
/// </summary>
|
|
public void TriggerSync() => _syncTrigger.TrySetResult(true);
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
await UpsertSeedEmailAccount(stoppingToken);
|
|
|
|
var interval = TimeSpan.FromSeconds(Options.Value.SyncIntervalSeconds);
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
await using var scope = Provider.CreateAsyncScope();
|
|
|
|
var emailAccountRepo = scope.ServiceProvider.GetRequiredService<IRepository<EmailAccount>>();
|
|
var imapService = scope.ServiceProvider.GetRequiredService<IImapEmailService>();
|
|
var pop3Service = scope.ServiceProvider.GetRequiredService<IPop3EmailService>();
|
|
|
|
foreach (var account in await emailAccountRepo.GetAllAsync(stoppingToken))
|
|
{
|
|
await SyncAccountAsync(account, imapService, pop3Service, stoppingToken);
|
|
}
|
|
|
|
// Reset trigger before waiting so any TriggerSync() call during the delay is caught
|
|
_syncTrigger = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
var delay = Task.Delay(interval, stoppingToken);
|
|
var triggered = _syncTrigger.Task;
|
|
await Task.WhenAny(delay, triggered).ConfigureAwait(false);
|
|
|
|
// Propagate cancellation if the host is stopping
|
|
stoppingToken.ThrowIfCancellationRequested();
|
|
}
|
|
}
|
|
|
|
private async Task SyncAccountAsync(
|
|
EmailAccount account,
|
|
IImapEmailService imapService,
|
|
IPop3EmailService pop3Service,
|
|
CancellationToken stoppingToken)
|
|
{
|
|
if (account.IncomingProtocol == IncomingProtocol.None)
|
|
return;
|
|
|
|
Logger.LogDebug(
|
|
"Email sync started. Account={Username}, Protocol={Protocol}.",
|
|
account.Username, account.IncomingProtocol);
|
|
|
|
try
|
|
{
|
|
var result = account.IncomingProtocol switch
|
|
{
|
|
IncomingProtocol.Imap or IncomingProtocol.ImapOAuth2
|
|
=> await imapService.SyncEmailsAsync(account, DefaultFolder, stoppingToken),
|
|
|
|
IncomingProtocol.Pop3 or IncomingProtocol.Pop3OAuth2
|
|
=> await pop3Service.SyncEmailsAsync(account, stoppingToken),
|
|
|
|
_ => throw new NotSupportedException(
|
|
$"IncomingProtocol '{account.IncomingProtocol}' is not supported by the sync worker.")
|
|
};
|
|
|
|
Logger.LogDebug(
|
|
"Email sync completed. Account={Username}, Protocol={Protocol}, Processed={Processed}, Failed={Failed}.",
|
|
account.Username, account.IncomingProtocol, result.ProcessedCount, result.FailedCount);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex,
|
|
"Email sync failed. Account={Username}, Protocol={Protocol}.",
|
|
account.Username, account.IncomingProtocol);
|
|
}
|
|
}
|
|
|
|
public async Task UpsertSeedEmailAccount(CancellationToken stoppingToken)
|
|
{
|
|
await using var scope = Provider.CreateAsyncScope();
|
|
|
|
var emailAccountRepo = scope.ServiceProvider.GetRequiredService<IRepository<EmailAccount>>();
|
|
|
|
foreach (var account in Options.Value.Accounts)
|
|
await emailAccountRepo.UpsertAsync(a => a.Username == account.Username, account, stoppingToken);
|
|
}
|
|
} |