From cf9f308b7f8fa21fff7132c5ea9b2171a4051383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Fri, 16 Aug 2024 01:29:31 +0200 Subject: [PATCH] Updating aliases during profile sync for Gmail. --- Wino.Core.Domain/Entities/MailAccountAlias.cs | 26 ++++++++++ .../Extensions/EntityExtensions.cs | 34 ++++++++++++++ .../Interfaces/IAccountService.cs | 8 ++++ .../Extensions/GoogleIntegratorExtensions.cs | 47 +++++-------------- .../Processors/DefaultChangeProcessor.cs | 4 ++ Wino.Core/Services/AccountService.cs | 28 +++++++---- Wino.Core/Synchronizers/GmailSynchronizer.cs | 18 +++++++ 7 files changed, 121 insertions(+), 44 deletions(-) create mode 100644 Wino.Core.Domain/Extensions/EntityExtensions.cs diff --git a/Wino.Core.Domain/Entities/MailAccountAlias.cs b/Wino.Core.Domain/Entities/MailAccountAlias.cs index a97efeb7..f1f0b623 100644 --- a/Wino.Core.Domain/Entities/MailAccountAlias.cs +++ b/Wino.Core.Domain/Entities/MailAccountAlias.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SQLite; namespace Wino.Core.Domain.Entities @@ -38,5 +39,30 @@ namespace Wino.Core.Domain.Entities /// Non-verified alias messages might be rejected by SMTP server. /// public bool IsVerified { get; set; } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + return false; + + var other = (MailAccountAlias)obj; + return other != null && + AccountId == other.AccountId && + AliasAddress == other.AliasAddress && + ReplyToAddress == other.ReplyToAddress && + IsPrimary == other.IsPrimary && + IsVerified == other.IsVerified; + } + + public override int GetHashCode() + { + int hashCode = -753829106; + hashCode = hashCode * -1521134295 + AccountId.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AliasAddress); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ReplyToAddress); + hashCode = hashCode * -1521134295 + IsPrimary.GetHashCode(); + hashCode = hashCode * -1521134295 + IsVerified.GetHashCode(); + return hashCode; + } } } diff --git a/Wino.Core.Domain/Extensions/EntityExtensions.cs b/Wino.Core.Domain/Extensions/EntityExtensions.cs new file mode 100644 index 00000000..b078ad99 --- /dev/null +++ b/Wino.Core.Domain/Extensions/EntityExtensions.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using Wino.Core.Domain.Entities; + +namespace Wino.Core.Domain.Extensions +{ + public static class EntityExtensions + { + public static List GetFinalAliasList(List localAliases, List networkAliases) + { + var finalAliases = new List(); + + var networkAliasDict = networkAliases.ToDictionary(a => a, a => a); + + // Handle updating and retaining existing aliases + foreach (var localAlias in localAliases) + { + if (networkAliasDict.TryGetValue(localAlias, out var networkAlias)) + { + // If alias exists in both lists, update it with the network alias (preserving Id from local) + networkAlias.Id = localAlias.Id; // Preserve the local Id + finalAliases.Add(networkAlias); + networkAliasDict.Remove(localAlias); // Remove from dictionary to track what's been handled + } + // If the alias isn't in the network list, it's considered deleted and not added to finalAliases + } + + // Add new aliases that were not in the local list + finalAliases.AddRange(networkAliasDict.Values); + + return finalAliases; + } + } +} diff --git a/Wino.Core.Domain/Interfaces/IAccountService.cs b/Wino.Core.Domain/Interfaces/IAccountService.cs index 78682cda..926a65d6 100644 --- a/Wino.Core.Domain/Interfaces/IAccountService.cs +++ b/Wino.Core.Domain/Interfaces/IAccountService.cs @@ -100,5 +100,13 @@ namespace Wino.Core.Domain.Interfaces /// /// AccountId-OrderNumber pair for all accounts. Task UpdateAccountOrdersAsync(Dictionary accountIdOrderPair); + + /// + /// Updated account's aliases. + /// + /// Account id to update aliases for. + /// Full list of updated aliases. + /// + Task UpdateAccountAliases(Guid accountId, List aliases); } } diff --git a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs index d03877b9..263669ab 100644 --- a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs +++ b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Web; using Google.Apis.Gmail.v1.Data; using MimeKit; using Wino.Core.Domain.Entities; using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Extensions; namespace Wino.Core.Extensions { @@ -205,44 +205,21 @@ namespace Wino.Core.Extensions }; } - public static Tuple> GetMailDetails(this Message message) + public static List GetMailAliases(this ListSendAsResponse response, MailAccount currentAccount) { - MimeMessage mimeMessage = message.GetGmailMimeMessage(); + if (response == null || response.SendAs == null) return currentAccount.Aliases; - if (mimeMessage == null) + var remoteAliases = response.SendAs.Select(a => new MailAccountAlias() { - // This should never happen. - Debugger.Break(); + AccountId = currentAccount.Id, + AliasAddress = a.SendAsEmail, + IsPrimary = a.IsPrimary.GetValueOrDefault(), + ReplyToAddress = string.IsNullOrEmpty(a.ReplyToAddress) ? currentAccount.Address : a.ReplyToAddress, + IsVerified = string.IsNullOrEmpty(a.VerificationStatus) ? true : a.VerificationStatus == "accepted", + Id = Guid.NewGuid() + }).ToList(); - return default; - } - - bool isUnread = message.GetIsUnread(); - bool isFocused = message.GetIsFocused(); - bool isFlagged = message.GetIsFlagged(); - bool isDraft = message.GetIsDraft(); - - var mailCopy = new MailCopy() - { - CreationDate = mimeMessage.Date.UtcDateTime, - Subject = HttpUtility.HtmlDecode(mimeMessage.Subject), - FromName = MailkitClientExtensions.GetActualSenderName(mimeMessage), - FromAddress = MailkitClientExtensions.GetActualSenderAddress(mimeMessage), - PreviewText = HttpUtility.HtmlDecode(message.Snippet), - ThreadId = message.ThreadId, - Importance = (MailImportance)mimeMessage.Importance, - Id = message.Id, - IsDraft = isDraft, - HasAttachments = mimeMessage.Attachments.Any(), - IsRead = !isUnread, - IsFlagged = isFlagged, - IsFocused = isFocused, - InReplyTo = mimeMessage.InReplyTo, - MessageId = mimeMessage.MessageId, - References = mimeMessage.References.GetReferences() - }; - - return new Tuple>(mailCopy, mimeMessage, message.LabelIds); + return EntityExtensions.GetFinalAliasList(currentAccount.Aliases, remoteAliases); } } } diff --git a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs index 4028dc9c..6ab9ef2d 100644 --- a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs @@ -46,6 +46,7 @@ namespace Wino.Core.Integration.Processors Task UpdateFolderLastSyncDateAsync(Guid folderId); Task> GetExistingFoldersAsync(Guid accountId); + Task UpdateAccountAliasesAsync(Guid accountId, List aliases); } public interface IGmailChangeProcessor : IDefaultChangeProcessor @@ -176,5 +177,8 @@ namespace Wino.Core.Integration.Processors public Task UpdateAccountAsync(MailAccount account) => AccountService.UpdateAccountAsync(account); + + public Task UpdateAccountAliasesAsync(Guid accountId, List aliases) + => AccountService.UpdateAccountAliases(accountId, aliases); } } diff --git a/Wino.Core/Services/AccountService.cs b/Wino.Core/Services/AccountService.cs index d7c9ab67..ab665f30 100644 --- a/Wino.Core/Services/AccountService.cs +++ b/Wino.Core/Services/AccountService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Diagnostics; @@ -241,7 +240,11 @@ namespace Wino.Core.Services // By default all accounts must have at least 1 primary alias to create drafts for. // If there's no alias, create one from the existing account address. Migration doesn't exists to create one for older messages. - var aliases = await Connection.Table().ToListAsync().ConfigureAwait(false); + var aliases = await Connection + .Table() + .Where(a => a.AccountId == accountId) + .ToListAsync() + .ConfigureAwait(false); if (!aliases.Any()) { @@ -350,17 +353,24 @@ namespace Wino.Core.Services public async Task UpdateAccountAsync(MailAccount account) { - if (account.Preferences == null) - { - Debugger.Break(); - } - - await Connection.UpdateAsync(account.Preferences); - await Connection.UpdateAsync(account); + await Connection.UpdateAsync(account.Preferences).ConfigureAwait(false); + await Connection.UpdateAsync(account).ConfigureAwait(false); ReportUIChange(new AccountUpdatedMessage(account)); } + public async Task UpdateAccountAliases(Guid accountId, List aliases) + { + // Delete existing ones. + await Connection.Table().DeleteAsync(a => a.AccountId == accountId).ConfigureAwait(false); + + // Insert new ones. + foreach (var alias in aliases) + { + await Connection.InsertAsync(alias).ConfigureAwait(false); + } + } + public async Task CreateAccountAsync(MailAccount account, TokenInformation tokenInformation, CustomServerInformation customServerInformation) { Guard.IsNotNull(account); diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index 5a7a6838..57da147d 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -103,6 +103,24 @@ namespace Wino.Core.Synchronizers Account.ProfilePictureBase64 = base64ProfilePicture; } + // Sync aliases + + var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me"); + var sendAsListResponse = await sendAsListRequest.ExecuteAsync(); + + var updatedAliases = sendAsListResponse.GetMailAliases(Account); + + bool shouldUpdateAliases = + Account.Aliases.Any(a => updatedAliases.Any(b => a.Id == b.Id) == false) || + updatedAliases.Any(a => Account.Aliases.Any(b => a.Id == b.Id) == false); + + if (shouldUpdateAliases) + { + Account.Aliases = updatedAliases; + + await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases); + } + if (shouldUpdateAccountProfile) { await _gmailChangeProcessor.UpdateAccountAsync(Account).ConfigureAwait(false);