Files
Wino-Mail/Wino.Services/SentMailReceiptService.cs
2026-04-11 21:02:51 +02:00

162 lines
6.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Extensions;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging.UI;
namespace Wino.Services;
public class SentMailReceiptService(
IDatabaseService databaseService,
IFolderService folderService,
IAccountService accountService) : BaseDatabaseService(databaseService), ISentMailReceiptService
{
public async Task PopulateReceiptStateAsync(MailCopy mailCopy)
{
if (mailCopy == null)
return;
var state = await Connection.FindAsync<SentMailReceiptState>(mailCopy.UniqueId).ConfigureAwait(false);
ApplyState(mailCopy, state);
}
public async Task PopulateReceiptStatesAsync(IReadOnlyCollection<MailCopy> mailCopies)
{
if (mailCopies == null || mailCopies.Count == 0)
return;
var uniqueIds = mailCopies
.Select(m => m.UniqueId)
.Where(id => id != Guid.Empty)
.Distinct()
.ToList();
if (uniqueIds.Count == 0)
return;
var placeholders = string.Join(",", uniqueIds.Select(_ => "?"));
var states = await Connection.QueryAsync<SentMailReceiptState>(
$"SELECT * FROM {nameof(SentMailReceiptState)} WHERE {nameof(SentMailReceiptState.MailUniqueId)} IN ({placeholders})",
uniqueIds.Cast<object>().ToArray()).ConfigureAwait(false);
var stateLookup = states.ToDictionary(s => s.MailUniqueId);
foreach (var mailCopy in mailCopies)
{
stateLookup.TryGetValue(mailCopy.UniqueId, out var state);
ApplyState(mailCopy, state);
}
}
public async Task TrackSentMailAsync(MailCopy mailCopy, MimeKit.MimeMessage mimeMessage = null)
{
if (mailCopy?.AssignedFolder == null || mailCopy.AssignedAccount == null)
return;
if (mailCopy.AssignedFolder.SpecialFolderType != SpecialFolderType.Sent)
return;
var isRequested = mailCopy.IsReadReceiptRequested || mimeMessage.HasReadReceiptRequest();
if (!isRequested || string.IsNullOrWhiteSpace(mailCopy.MessageId))
return;
var existing = await Connection.FindAsync<SentMailReceiptState>(mailCopy.UniqueId).ConfigureAwait(false);
if (existing == null)
{
existing = new SentMailReceiptState
{
MailUniqueId = mailCopy.UniqueId,
AccountId = mailCopy.AssignedAccount.Id,
MessageId = MailHeaderExtensions.NormalizeMessageId(mailCopy.MessageId),
IsReceiptRequested = true,
RequestedAtUtc = mailCopy.CreationDate == default ? DateTime.UtcNow : mailCopy.CreationDate,
Status = SentMailReceiptStatus.Requested
};
await Connection.InsertAsync(existing, typeof(SentMailReceiptState)).ConfigureAwait(false);
}
else
{
existing.AccountId = mailCopy.AssignedAccount.Id;
existing.MessageId = MailHeaderExtensions.NormalizeMessageId(mailCopy.MessageId);
existing.IsReceiptRequested = true;
if (existing.Status == SentMailReceiptStatus.None)
existing.Status = SentMailReceiptStatus.Requested;
await Connection.UpdateAsync(existing, typeof(SentMailReceiptState)).ConfigureAwait(false);
}
ApplyState(mailCopy, existing);
ReportUIChange(new MailUpdatedMessage(mailCopy, EntityUpdateSource.Server, MailCopyChangeFlags.ReadReceiptState));
}
public async Task ProcessIncomingReceiptAsync(MailCopy receiptMail, MimeKit.MimeMessage mimeMessage)
{
if (receiptMail?.AssignedAccount == null || mimeMessage == null)
return;
var parsedReceipt = mimeMessage.ParseReadReceipt();
if (!parsedReceipt.IsReadReceipt || string.IsNullOrWhiteSpace(parsedReceipt.OriginalMessageId))
return;
var targetMail = await Connection.FindWithQueryAsync<MailCopy>(
"SELECT MailCopy.* FROM MailCopy " +
"INNER JOIN MailItemFolder ON MailCopy.FolderId = MailItemFolder.Id " +
"WHERE MailItemFolder.MailAccountId = ? AND MailCopy.MessageId = ? AND MailItemFolder.SpecialFolderType = ? " +
"ORDER BY MailCopy.CreationDate DESC LIMIT 1",
receiptMail.AssignedAccount.Id,
parsedReceipt.OriginalMessageId,
SpecialFolderType.Sent).ConfigureAwait(false);
if (targetMail == null)
return;
var state = await Connection.FindAsync<SentMailReceiptState>(targetMail.UniqueId).ConfigureAwait(false)
?? new SentMailReceiptState
{
MailUniqueId = targetMail.UniqueId,
AccountId = receiptMail.AssignedAccount.Id,
MessageId = parsedReceipt.OriginalMessageId,
RequestedAtUtc = targetMail.CreationDate == default ? DateTime.UtcNow : targetMail.CreationDate,
IsReceiptRequested = true,
Status = SentMailReceiptStatus.Requested
};
state.AccountId = receiptMail.AssignedAccount.Id;
state.MessageId = parsedReceipt.OriginalMessageId;
state.IsReceiptRequested = true;
state.Status = SentMailReceiptStatus.Acknowledged;
state.AcknowledgedAtUtc = parsedReceipt.AcknowledgedAtUtc ?? DateTime.UtcNow;
state.ReceiptMessageUniqueId = receiptMail.UniqueId;
if (await Connection.FindAsync<SentMailReceiptState>(state.MailUniqueId).ConfigureAwait(false) == null)
await Connection.InsertAsync(state, typeof(SentMailReceiptState)).ConfigureAwait(false);
else
await Connection.UpdateAsync(state, typeof(SentMailReceiptState)).ConfigureAwait(false);
var folder = await folderService.GetFolderAsync(targetMail.FolderId).ConfigureAwait(false);
if (folder == null)
return;
var account = await accountService.GetAccountAsync(folder.MailAccountId).ConfigureAwait(false);
targetMail.AssignedFolder = folder;
targetMail.AssignedAccount = account;
ApplyState(targetMail, state);
ReportUIChange(new MailUpdatedMessage(targetMail, EntityUpdateSource.Server, MailCopyChangeFlags.ReadReceiptState));
}
private static void ApplyState(MailCopy mailCopy, SentMailReceiptState state)
{
mailCopy.IsReadReceiptRequested = state?.IsReceiptRequested ?? false;
mailCopy.ReadReceiptStatus = state?.Status ?? SentMailReceiptStatus.None;
mailCopy.ReadReceiptAcknowledgedAtUtc = state?.AcknowledgedAtUtc;
mailCopy.ReadReceiptMessageUniqueId = state?.ReceiptMessageUniqueId;
}
}