Refactor LimilabsImapEmailService for clarity and efficiency
Refactored the `LimilabsImapEmailService` class to improve email fetching and processing. Consolidated logic by inlining and removing redundant private helper methods (`FetchEmailUidsAsync` and `FetchEmailByUidAsync`). Introduced structured `#region` blocks for UID fetching and email reading. Enhanced filtering capabilities with support for unseen emails, subject, sender, recipient, body, UID ranges, and date ranges. Improved attachment handling by categorizing inline and regular attachments into `EmailAttachmentContext`. Added conditional attachment inclusion based on `filter.WithAttachments`. Integrated caching (`Cache.GetOrCreateAsync`) to avoid redundant email fetches. Improved error handling and logging for better fault tolerance. Overall, the changes simplify the codebase, improve readability, and enhance functionality.
This commit is contained in:
@@ -36,7 +36,55 @@ public class LimilabsImapEmailService(
|
|||||||
using var imap = await OpenAsync(account, filter.Folder);
|
using var imap = await OpenAsync(account, filter.Folder);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uids = await FetchEmailUidsAsync(imap, filter, cancellationToken);
|
#region Find UIDs
|
||||||
|
List<ICriterion> criterions = [];
|
||||||
|
|
||||||
|
if (filter.UnseenOnly)
|
||||||
|
criterions.Add(Expression.HasFlag(Flag.Unseen));
|
||||||
|
|
||||||
|
if (filter.SubjectContains is not null)
|
||||||
|
criterions.Add(Expression.Subject(filter.SubjectContains));
|
||||||
|
|
||||||
|
if (filter.SenderContains is not null)
|
||||||
|
criterions.Add(Expression.From(filter.SenderContains));
|
||||||
|
|
||||||
|
if (filter.RecipientContains is not null)
|
||||||
|
criterions.Add(Expression.To(filter.RecipientContains));
|
||||||
|
|
||||||
|
if (filter.BodyContains is not null)
|
||||||
|
criterions.Add(Expression.Body(filter.BodyContains));
|
||||||
|
|
||||||
|
if (filter.Uid is UidFilter uidF)
|
||||||
|
{
|
||||||
|
if (uidF.Absolute is long exactUid)
|
||||||
|
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(exactUid, exactUid)));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
long lo = uidF.Min ?? 1L;
|
||||||
|
long? hi = uidF.Max;
|
||||||
|
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(lo, hi)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.Date is DateFilter dateF)
|
||||||
|
{
|
||||||
|
if (dateF.After is DateTime after)
|
||||||
|
criterions.Add(Expression.SentSince(after.Date));
|
||||||
|
|
||||||
|
// IMAP BEFORE is exclusive, so add one day to make the bound inclusive
|
||||||
|
if (dateF.Before is DateTime before)
|
||||||
|
criterions.Add(Expression.SentBefore(before.Date.AddDays(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchExpression = criterions.Count > 0 ? Expression.And([.. criterions]) : Expression.All();
|
||||||
|
List<long> uids = [.. await imap.SearchAsync(searchExpression, cancellationToken)];
|
||||||
|
|
||||||
|
if (filter.SortOrder == MailSortOrder.NewestFirst)
|
||||||
|
uids.Reverse();
|
||||||
|
|
||||||
|
if (filter.MaxCount is int maxCount && maxCount > 0 && uids.Count > maxCount)
|
||||||
|
uids = [.. uids.Take(maxCount)];
|
||||||
|
#endregion
|
||||||
|
|
||||||
if (uids.Count == 0)
|
if (uids.Count == 0)
|
||||||
return [];
|
return [];
|
||||||
@@ -49,10 +97,64 @@ public class LimilabsImapEmailService(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var email = await FetchEmailByUidAsync(imap, uid, filter.WithAttachments, cancellationToken)
|
|
||||||
?? throw new NotFoundException($"Email with UID={uid} not found in folder '{filter.Folder}' in {account.Username}.");
|
|
||||||
|
|
||||||
results.Add(email);
|
#region Read email
|
||||||
|
var email = await Cache.GetOrCreateAsync(
|
||||||
|
CacheKeyPrefix + uid,
|
||||||
|
async entry =>
|
||||||
|
{
|
||||||
|
var eml = await imap.GetMessageByUIDAsync(uid, cancellationToken);
|
||||||
|
var mail = new MailBuilder().CreateFromEml(eml);
|
||||||
|
|
||||||
|
var attachments = new List<EmailAttachmentContext>();
|
||||||
|
|
||||||
|
foreach (var att in mail.Attachments)
|
||||||
|
{
|
||||||
|
attachments.Add(new EmailAttachmentContext
|
||||||
|
{
|
||||||
|
FileName = att.FileName ?? "attachment",
|
||||||
|
Content = att.Data,
|
||||||
|
ContentType = att.ContentType?.ToString() ?? "application/octet-stream",
|
||||||
|
IsInline = false,
|
||||||
|
ContentId = att.ContentId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var vis in mail.Visuals)
|
||||||
|
{
|
||||||
|
attachments.Add(new EmailAttachmentContext
|
||||||
|
{
|
||||||
|
FileName = vis.FileName ?? "inline",
|
||||||
|
Content = vis.Data,
|
||||||
|
ContentType = vis.ContentType?.ToString() ?? "application/octet-stream",
|
||||||
|
IsInline = true,
|
||||||
|
ContentId = vis.ContentId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReceivedEmailContext
|
||||||
|
{
|
||||||
|
Uid = uid,
|
||||||
|
From = mail.From.FirstOrDefault()?.Address ?? string.Empty,
|
||||||
|
To = [.. mail.To.SelectMany(m => m.GetMailboxes()).Select(mb => mb.Address)],
|
||||||
|
Cc = [.. mail.Cc.SelectMany(m => m.GetMailboxes()).Select(mb => mb.Address)],
|
||||||
|
Subject = mail.Subject ?? string.Empty,
|
||||||
|
TextBody = mail.Text ?? string.Empty,
|
||||||
|
HtmlBody = mail.Html ?? string.Empty,
|
||||||
|
Date = mail.Date ?? DateTime.MinValue,
|
||||||
|
Attachments = attachments,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
#endregion Read email
|
||||||
|
|
||||||
|
if(email is not null)
|
||||||
|
{
|
||||||
|
if (filter.WithAttachments)
|
||||||
|
results.Add(email);
|
||||||
|
else
|
||||||
|
results.Add(email with { Attachments = [] });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -105,119 +207,6 @@ public class LimilabsImapEmailService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private helpers
|
|
||||||
private static async Task<List<long>> FetchEmailUidsAsync(
|
|
||||||
Imap imap,
|
|
||||||
MailSearchFilter filter,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
List<ICriterion> criterions = [];
|
|
||||||
|
|
||||||
if (filter.UnseenOnly)
|
|
||||||
criterions.Add(Expression.HasFlag(Flag.Unseen));
|
|
||||||
|
|
||||||
if (filter.SubjectContains is not null)
|
|
||||||
criterions.Add(Expression.Subject(filter.SubjectContains));
|
|
||||||
|
|
||||||
if (filter.SenderContains is not null)
|
|
||||||
criterions.Add(Expression.From(filter.SenderContains));
|
|
||||||
|
|
||||||
if (filter.RecipientContains is not null)
|
|
||||||
criterions.Add(Expression.To(filter.RecipientContains));
|
|
||||||
|
|
||||||
if (filter.BodyContains is not null)
|
|
||||||
criterions.Add(Expression.Body(filter.BodyContains));
|
|
||||||
|
|
||||||
if (filter.Uid is UidFilter uidF)
|
|
||||||
{
|
|
||||||
if (uidF.Absolute is long exactUid)
|
|
||||||
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(exactUid, exactUid)));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
long lo = uidF.Min ?? 1L;
|
|
||||||
long? hi = uidF.Max;
|
|
||||||
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(lo, hi)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.Date is DateFilter dateF)
|
|
||||||
{
|
|
||||||
if (dateF.After is DateTime after)
|
|
||||||
criterions.Add(Expression.SentSince(after.Date));
|
|
||||||
|
|
||||||
// IMAP BEFORE is exclusive, so add one day to make the bound inclusive
|
|
||||||
if (dateF.Before is DateTime before)
|
|
||||||
criterions.Add(Expression.SentBefore(before.Date.AddDays(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchExpression = criterions.Count > 0 ? Expression.And([.. criterions]) : Expression.All();
|
|
||||||
List<long> uids = [.. await imap.SearchAsync(searchExpression, cancellationToken)];
|
|
||||||
|
|
||||||
if (filter.SortOrder == MailSortOrder.NewestFirst)
|
|
||||||
uids.Reverse();
|
|
||||||
|
|
||||||
if (filter.MaxCount is int maxCount && maxCount > 0 && uids.Count > maxCount)
|
|
||||||
uids = [.. uids.Take(maxCount)];
|
|
||||||
|
|
||||||
return uids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ReceivedEmailContext?> FetchEmailByUidAsync(
|
|
||||||
Imap imap,
|
|
||||||
long uid,
|
|
||||||
bool withAttachments = false,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var email = await Cache.GetOrCreateAsync(
|
|
||||||
CacheKeyPrefix + uid,
|
|
||||||
async entry =>
|
|
||||||
{
|
|
||||||
var eml = await imap.GetMessageByUIDAsync(uid, cancellationToken);
|
|
||||||
var mail = new MailBuilder().CreateFromEml(eml);
|
|
||||||
|
|
||||||
var attachments = new List<EmailAttachmentContext>();
|
|
||||||
|
|
||||||
foreach (var att in mail.Attachments)
|
|
||||||
{
|
|
||||||
attachments.Add(new EmailAttachmentContext
|
|
||||||
{
|
|
||||||
FileName = att.FileName ?? "attachment",
|
|
||||||
Content = att.Data,
|
|
||||||
ContentType = att.ContentType?.ToString() ?? "application/octet-stream",
|
|
||||||
IsInline = false,
|
|
||||||
ContentId = att.ContentId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var vis in mail.Visuals)
|
|
||||||
{
|
|
||||||
attachments.Add(new EmailAttachmentContext
|
|
||||||
{
|
|
||||||
FileName = vis.FileName ?? "inline",
|
|
||||||
Content = vis.Data,
|
|
||||||
ContentType = vis.ContentType?.ToString() ?? "application/octet-stream",
|
|
||||||
IsInline = true,
|
|
||||||
ContentId = vis.ContentId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReceivedEmailContext
|
|
||||||
{
|
|
||||||
Uid = uid,
|
|
||||||
From = mail.From.FirstOrDefault()?.Address ?? string.Empty,
|
|
||||||
To = [.. mail.To.SelectMany(m => m.GetMailboxes()).Select(mb => mb.Address)],
|
|
||||||
Cc = [.. mail.Cc.SelectMany(m => m.GetMailboxes()).Select(mb => mb.Address)],
|
|
||||||
Subject = mail.Subject ?? string.Empty,
|
|
||||||
TextBody = mail.Text ?? string.Empty,
|
|
||||||
HtmlBody = mail.Html ?? string.Empty,
|
|
||||||
Date = mail.Date ?? DateTime.MinValue,
|
|
||||||
Attachments = attachments,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return withAttachments || email is null ? email : email with { Attachments = [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Imap> OpenAsync(EmailAccountDto account, string folder)
|
private async Task<Imap> OpenAsync(EmailAccountDto account, string folder)
|
||||||
{
|
{
|
||||||
var imap = new Imap();
|
var imap = new Imap();
|
||||||
|
|||||||
Reference in New Issue
Block a user