* Fixing an issue where scrollviewer overrides a part of template in mail list. Adjusted zoomed out header grid's corner radius. * IDLE implementation, imap synchronization strategies basics and condstore synchronization. * Adding iCloud and Yahoo as special IMAP handling scenario. * iCloud special imap handling. * Support for killing synchronizers. * Update privacy policy url. * Batching condstore downloads into 50, using SORT extension for searches if supported. * Bumping some nugets. More on the imap synchronizers. * Delegating idle synchronizations to server to post-sync operations. * Update mailkit to resolve qresync bug with iCloud. * Fixing remote highest mode seq checks for qresync and condstore synchronizers. * Yahoo custom settings. * Bump google sdk package. * Fixing the build issue.... * NRE on canceled token accounts during setup. * Server crash handlers. * Remove ARM32. Upgrade server to .NET 9. * Fix icons for yahoo and apple. * Fixed an issue where disabled folders causing an exception on forced sync. * Remove smtp encoding constraint. * Remove commented code. * Fixing merge conflict * Addressing double registrations for mailkit remote folder events in synchronizers. * Making sure idle canceled result is not reported. * Fixing custom imap server dialog opening. * Fixing the issue with account creation making the previously selected account as selected as well. * Fixing app close behavior and logging app close.
194 lines
7.4 KiB
C#
194 lines
7.4 KiB
C#
using System;
|
|
using System.Linq;
|
|
using MailKit;
|
|
using MimeKit;
|
|
using Wino.Core.Domain;
|
|
using Wino.Core.Domain.Entities.Mail;
|
|
using Wino.Core.Domain.Enums;
|
|
|
|
namespace Wino.Services.Extensions
|
|
{
|
|
public static class MailkitClientExtensions
|
|
{
|
|
public static char MailCopyUidSeparator = '_';
|
|
|
|
public static uint ResolveUid(string mailCopyId)
|
|
{
|
|
var splitted = mailCopyId.Split(MailCopyUidSeparator);
|
|
|
|
if (splitted.Length > 1 && uint.TryParse(splitted[1], out uint parsedUint)) return parsedUint;
|
|
|
|
throw new ArgumentOutOfRangeException(nameof(mailCopyId), mailCopyId, "Invalid mailCopyId format.");
|
|
}
|
|
|
|
public static UniqueId ResolveUidStruct(string mailCopyId)
|
|
=> new UniqueId(ResolveUid(mailCopyId));
|
|
|
|
public static string CreateUid(Guid folderId, uint messageUid)
|
|
=> $"{folderId}{MailCopyUidSeparator}{messageUid}";
|
|
|
|
public static MailImportance GetImportance(this MimeMessage messageSummary)
|
|
{
|
|
if (messageSummary.Headers != null && messageSummary.Headers.Contains(HeaderId.Importance))
|
|
{
|
|
var rawImportance = messageSummary.Headers[HeaderId.Importance];
|
|
|
|
return rawImportance switch
|
|
{
|
|
"Low" => MailImportance.Low,
|
|
"High" => MailImportance.High,
|
|
_ => MailImportance.Normal,
|
|
};
|
|
}
|
|
|
|
return MailImportance.Normal;
|
|
}
|
|
|
|
public static bool GetIsRead(this MessageFlags? flags)
|
|
=> flags.GetValueOrDefault().HasFlag(MessageFlags.Seen);
|
|
|
|
public static bool GetIsFlagged(this MessageFlags? flags)
|
|
=> flags.GetValueOrDefault().HasFlag(MessageFlags.Flagged);
|
|
|
|
public static string GetThreadId(this IMessageSummary messageSummary)
|
|
{
|
|
// First check whether we have the default values.
|
|
|
|
if (!string.IsNullOrEmpty(messageSummary.ThreadId))
|
|
return messageSummary.ThreadId;
|
|
|
|
if (messageSummary.GMailThreadId != null)
|
|
return messageSummary.GMailThreadId.ToString();
|
|
|
|
return default;
|
|
}
|
|
|
|
public static string GetMessageId(this MimeMessage mimeMessage)
|
|
=> mimeMessage.MessageId;
|
|
|
|
public static string GetReferences(this MessageIdList messageIdList)
|
|
=> string.Join(";", messageIdList);
|
|
|
|
public static string GetInReplyTo(this MimeMessage mimeMessage)
|
|
{
|
|
if (mimeMessage.Headers.Contains(HeaderId.InReplyTo))
|
|
{
|
|
// Normalize if <> brackets are there.
|
|
var inReplyTo = mimeMessage.Headers[HeaderId.InReplyTo];
|
|
|
|
if (inReplyTo.StartsWith("<") && inReplyTo.EndsWith(">"))
|
|
return inReplyTo.Substring(1, inReplyTo.Length - 2);
|
|
|
|
return inReplyTo;
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string GetPreviewText(this MimeMessage message)
|
|
{
|
|
if (string.IsNullOrEmpty(message.HtmlBody))
|
|
return message.TextBody;
|
|
else
|
|
return HtmlAgilityPackExtensions.GetPreviewText(message.HtmlBody);
|
|
}
|
|
|
|
public static MailCopy GetMailDetails(this IMessageSummary messageSummary, MailItemFolder folder, MimeMessage mime)
|
|
{
|
|
// MessageSummary will only have UniqueId, Flags, ThreadId.
|
|
// Other properties are extracted directly from the MimeMessage.
|
|
|
|
// IMAP doesn't have unique id for mails.
|
|
// All mails are mapped to specific folders with incremental Id.
|
|
// Uid 1 may belong to different messages in different folders, but can never be
|
|
// same for different messages in same folders.
|
|
// Here we create arbitrary Id that maps the Id of the message with Folder UniqueId.
|
|
// When folder becomes invalid, we'll clear out these MailCopies as well.
|
|
|
|
var messageUid = CreateUid(folder.Id, messageSummary.UniqueId.Id);
|
|
var previewText = mime.GetPreviewText();
|
|
|
|
var copy = new MailCopy()
|
|
{
|
|
Id = messageUid,
|
|
CreationDate = mime.Date.UtcDateTime,
|
|
ThreadId = messageSummary.GetThreadId(),
|
|
MessageId = mime.GetMessageId(),
|
|
Subject = mime.Subject,
|
|
IsRead = messageSummary.Flags.GetIsRead(),
|
|
IsFlagged = messageSummary.Flags.GetIsFlagged(),
|
|
PreviewText = previewText,
|
|
FromAddress = GetActualSenderAddress(mime),
|
|
FromName = GetActualSenderName(mime),
|
|
IsFocused = false,
|
|
Importance = mime.GetImportance(),
|
|
References = mime.References?.GetReferences(),
|
|
InReplyTo = mime.GetInReplyTo(),
|
|
HasAttachments = mime.Attachments.Any(),
|
|
FileId = Guid.NewGuid()
|
|
};
|
|
|
|
return copy;
|
|
}
|
|
|
|
// TODO: Name and Address parsing should be handled better.
|
|
// At some point Wino needs better contact management.
|
|
|
|
public static string GetActualSenderName(MimeMessage message)
|
|
{
|
|
if (message == null)
|
|
return string.Empty;
|
|
|
|
return message.From.Mailboxes.FirstOrDefault()?.Name ?? message.Sender?.Name ?? Translator.UnknownSender;
|
|
|
|
// From MimeKit
|
|
|
|
// The "From" header specifies the author(s) of the message.
|
|
// If more than one MimeKit.MailboxAddress is added to the list of "From" addresses,
|
|
// the MimeKit.MimeMessage.Sender should be set to the single MimeKit.MailboxAddress
|
|
// of the personal actually sending the message.
|
|
|
|
// Also handle: https://stackoverflow.com/questions/46474030/mailkit-from-address
|
|
|
|
//if (message.Sender != null)
|
|
// return string.IsNullOrEmpty(message.Sender.Name) ? message.Sender.Address : message.Sender.Name;
|
|
//else if (message.From?.Mailboxes != null)
|
|
//{
|
|
// var firstAvailableName = message.From.Mailboxes.FirstOrDefault(a => !string.IsNullOrEmpty(a.Name))?.Name;
|
|
|
|
// if (string.IsNullOrEmpty(firstAvailableName))
|
|
// {
|
|
// var firstAvailableAddress = message.From.Mailboxes.FirstOrDefault(a => !string.IsNullOrEmpty(a.Address))?.Address;
|
|
|
|
// if (!string.IsNullOrEmpty(firstAvailableAddress))
|
|
// {
|
|
// return firstAvailableAddress;
|
|
// }
|
|
// }
|
|
|
|
// return firstAvailableName;
|
|
//}
|
|
|
|
//// No sender, no from, I don't know what to do.
|
|
//return Translator.UnknownSender;
|
|
}
|
|
|
|
// TODO: This is wrong.
|
|
public static string GetActualSenderAddress(MimeMessage message)
|
|
{
|
|
return message.From.Mailboxes.FirstOrDefault()?.Address ?? message.Sender?.Address ?? Translator.UnknownSender;
|
|
//if (mime == null)
|
|
// return string.Empty;
|
|
|
|
//bool hasSingleFromMailbox = mime.From.Mailboxes.Count() == 1;
|
|
|
|
//if (hasSingleFromMailbox)
|
|
// return mime.From.Mailboxes.First().GetAddress(idnEncode: true);
|
|
//else if (mime.Sender != null)
|
|
// return mime.Sender.GetAddress(idnEncode: true);
|
|
//else
|
|
// return Translator.UnknownSender;
|
|
}
|
|
}
|
|
}
|