179 lines
6.8 KiB
C#
179 lines
6.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using SqlKata;
|
|
using Wino.Core.Domain.Entities.Mail;
|
|
using Wino.Core.Domain.Enums;
|
|
using Wino.Core.Domain.Interfaces;
|
|
using Wino.Core.Domain.Models.Folders;
|
|
using Wino.Core.Domain.Models.MailItem;
|
|
using Wino.Services.Extensions;
|
|
|
|
namespace Wino.Services.Threading;
|
|
|
|
public class ImapThreadingStrategy : BaseDatabaseService, IImapThreadingStrategy
|
|
{
|
|
private readonly IFolderService _folderService;
|
|
|
|
public ImapThreadingStrategy(IDatabaseService databaseService, IFolderService folderService) : base(databaseService)
|
|
{
|
|
_folderService = folderService;
|
|
}
|
|
|
|
private Task<MailCopy> GetReplyParentAsync(IMailItem replyItem, Guid accountId, Guid threadingFolderId, Guid sentFolderId, Guid draftFolderId)
|
|
{
|
|
if (string.IsNullOrEmpty(replyItem?.MessageId)) return Task.FromResult<MailCopy>(null);
|
|
|
|
var query = new Query("MailCopy")
|
|
.Distinct()
|
|
.Take(1)
|
|
.Join("MailItemFolder", "MailItemFolder.Id", "MailCopy.FolderId")
|
|
.Where("MailItemFolder.MailAccountId", accountId)
|
|
.WhereIn("MailItemFolder.Id", new List<Guid> { threadingFolderId, sentFolderId, draftFolderId })
|
|
.Where("MailCopy.MessageId", replyItem.InReplyTo)
|
|
.WhereNot("MailCopy.Id", replyItem.Id)
|
|
.Select("MailCopy.*");
|
|
|
|
return Connection.FindWithQueryAsync<MailCopy>(query.GetRawQuery());
|
|
}
|
|
|
|
private Task<MailCopy> GetInReplyToReplyAsync(IMailItem originalItem, Guid accountId, Guid threadingFolderId, Guid sentFolderId, Guid draftFolderId)
|
|
{
|
|
if (string.IsNullOrEmpty(originalItem?.MessageId)) return Task.FromResult<MailCopy>(null);
|
|
|
|
var query = new Query("MailCopy")
|
|
.Distinct()
|
|
.Take(1)
|
|
.Join("MailItemFolder", "MailItemFolder.Id", "MailCopy.FolderId")
|
|
.WhereNot("MailCopy.Id", originalItem.Id)
|
|
.Where("MailItemFolder.MailAccountId", accountId)
|
|
.Where("MailCopy.InReplyTo", originalItem.MessageId)
|
|
.WhereIn("MailItemFolder.Id", new List<Guid> { threadingFolderId, sentFolderId, draftFolderId })
|
|
.Select("MailCopy.*");
|
|
|
|
var raq = query.GetRawQuery();
|
|
|
|
return Connection.FindWithQueryAsync<MailCopy>(query.GetRawQuery());
|
|
}
|
|
|
|
public async Task<List<IMailItem>> ThreadItemsAsync(List<MailCopy> items, IMailItemFolder threadingForFolder)
|
|
{
|
|
var threads = new List<ThreadMailItem>();
|
|
|
|
var account = items.First().AssignedAccount;
|
|
var accountId = account.Id;
|
|
|
|
// Child -> Parent approach.
|
|
|
|
var mailLookupTable = new Dictionary<string, bool>();
|
|
|
|
// Fill up the mail lookup table to prevent double thread creation.
|
|
foreach (var mail in items)
|
|
if (!mailLookupTable.ContainsKey(mail.Id))
|
|
mailLookupTable.Add(mail.Id, false);
|
|
|
|
var sentFolder = await _folderService.GetSpecialFolderByAccountIdAsync(accountId, SpecialFolderType.Sent);
|
|
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(accountId, SpecialFolderType.Draft);
|
|
|
|
// Threading is not possible. Return items as it is.
|
|
|
|
if (sentFolder == null || draftFolder == null) return new List<IMailItem>(items);
|
|
|
|
foreach (var replyItem in items)
|
|
{
|
|
if (mailLookupTable[replyItem.Id])
|
|
continue;
|
|
|
|
mailLookupTable[replyItem.Id] = true;
|
|
|
|
var threadItem = new ThreadMailItem();
|
|
|
|
threadItem.AddThreadItem(replyItem);
|
|
|
|
var replyToChild = await GetReplyParentAsync(replyItem, accountId, replyItem.AssignedFolder.Id, sentFolder.Id, draftFolder.Id);
|
|
|
|
// Build up
|
|
while (replyToChild != null)
|
|
{
|
|
replyToChild.AssignedAccount = account;
|
|
|
|
if (replyToChild.FolderId == draftFolder.Id)
|
|
replyToChild.AssignedFolder = draftFolder;
|
|
|
|
if (replyToChild.FolderId == sentFolder.Id)
|
|
replyToChild.AssignedFolder = sentFolder;
|
|
|
|
if (replyToChild.FolderId == replyItem.AssignedFolder.Id)
|
|
replyToChild.AssignedFolder = replyItem.AssignedFolder;
|
|
|
|
threadItem.AddThreadItem(replyToChild);
|
|
|
|
if (mailLookupTable.ContainsKey(replyToChild.Id))
|
|
mailLookupTable[replyToChild.Id] = true;
|
|
|
|
replyToChild = await GetReplyParentAsync(replyToChild, accountId, replyToChild.AssignedFolder.Id, sentFolder.Id, draftFolder.Id);
|
|
}
|
|
|
|
// Build down
|
|
var replyToParent = await GetInReplyToReplyAsync(replyItem, accountId, replyItem.AssignedFolder.Id, sentFolder.Id, draftFolder.Id);
|
|
|
|
while (replyToParent != null)
|
|
{
|
|
replyToParent.AssignedAccount = account;
|
|
|
|
if (replyToParent.FolderId == draftFolder.Id)
|
|
replyToParent.AssignedFolder = draftFolder;
|
|
|
|
if (replyToParent.FolderId == sentFolder.Id)
|
|
replyToParent.AssignedFolder = sentFolder;
|
|
|
|
if (replyToParent.FolderId == replyItem.AssignedFolder.Id)
|
|
replyToParent.AssignedFolder = replyItem.AssignedFolder;
|
|
|
|
threadItem.AddThreadItem(replyToParent);
|
|
|
|
if (mailLookupTable.ContainsKey(replyToParent.Id))
|
|
mailLookupTable[replyToParent.Id] = true;
|
|
|
|
replyToParent = await GetInReplyToReplyAsync(replyToParent, accountId, replyToParent.AssignedFolder.Id, sentFolder.Id, draftFolder.Id);
|
|
}
|
|
|
|
// It's a thread item.
|
|
|
|
if (threadItem.ThreadItems.Count > 1 && !threads.Exists(a => a.Id == threadItem.Id))
|
|
{
|
|
threads.Add(threadItem);
|
|
}
|
|
else
|
|
{
|
|
// False alert. This is not a thread item.
|
|
mailLookupTable[replyItem.Id] = false;
|
|
|
|
// TODO: Here potentially check other algorithms for threading like References.
|
|
}
|
|
}
|
|
|
|
// At this points all mails in the list belong to single items.
|
|
// Merge with threads.
|
|
// Last sorting will be done later on in MailService.
|
|
|
|
// Remove single mails that are included in thread.
|
|
items.RemoveAll(a => mailLookupTable.ContainsKey(a.Id) && mailLookupTable[a.Id]);
|
|
|
|
var finalList = new List<IMailItem>(items);
|
|
|
|
finalList.AddRange(threads);
|
|
|
|
return finalList;
|
|
}
|
|
|
|
public bool ShouldThreadWithItem(IMailItem originalItem, IMailItem targetItem)
|
|
{
|
|
bool isChild = originalItem.InReplyTo != null && originalItem.InReplyTo == targetItem.MessageId;
|
|
bool isParent = originalItem.MessageId != null && originalItem.MessageId == targetItem.InReplyTo;
|
|
|
|
return isChild || isParent;
|
|
}
|
|
}
|