From 82ae13ba3eddab7009f523683ea091ddf09593ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Kaan=20K=C3=B6se?= Date: Fri, 21 Jun 2024 23:48:03 +0200 Subject: [PATCH] Fixed Archive implementation for Gmail. --- Wino.Core.Domain/Enums/MailOperation.cs | 3 +- Wino.Core/Requests/ArchiveRequest.cs | 66 +++++++++++++++++++ Wino.Core/Services/WinoRequestProcessor.cs | 25 +++++-- Wino.Core/Synchronizers/BaseSynchronizer.cs | 4 ++ Wino.Core/Synchronizers/GmailSynchronizer.cs | 22 +++++++ Wino.Core/Synchronizers/ImapSynchronizer.cs | 3 + .../Synchronizers/OutlookSynchronizer.cs | 3 + 7 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 Wino.Core/Requests/ArchiveRequest.cs diff --git a/Wino.Core.Domain/Enums/MailOperation.cs b/Wino.Core.Domain/Enums/MailOperation.cs index 483b5607..b7a20b6c 100644 --- a/Wino.Core.Domain/Enums/MailOperation.cs +++ b/Wino.Core.Domain/Enums/MailOperation.cs @@ -11,7 +11,8 @@ ChangeFlag, AlwaysMoveTo, MoveToFocused, - RenameFolder + RenameFolder, + Archive } // UI requests diff --git a/Wino.Core/Requests/ArchiveRequest.cs b/Wino.Core/Requests/ArchiveRequest.cs new file mode 100644 index 00000000..f519f4d8 --- /dev/null +++ b/Wino.Core/Requests/ArchiveRequest.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using MoreLinq; +using Wino.Core.Domain.Entities; +using Wino.Core.Domain.Enums; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Domain.Models.Requests; + +namespace Wino.Core.Requests +{ + /// + /// Archive message request. + /// By default, the message will be moved to the Archive folder. + /// For Gmail, 'Archive' label will be removed from the message. + /// + /// Whether are archiving or unarchiving + /// Mail to archive + /// Source folder. + /// Optional Target folder. Required for ImapSynchronizer and OutlookSynchronizer. + public record ArchiveRequest(bool IsArchiving, MailCopy Item, MailItemFolder FromFolder, MailItemFolder ToFolder = null) : RequestBase(Item, MailSynchronizerOperation.Archive), ICustomFolderSynchronizationRequest + { + public List SynchronizationFolderIds + { + get + { + var folderIds = new List { FromFolder.Id }; + + if (ToFolder != null) + { + folderIds.Add(ToFolder.Id); + } + + return folderIds; + } + } + + public override IBatchChangeRequest CreateBatch(IEnumerable matchingItems) + => new BatchArchiveRequest(IsArchiving, matchingItems, FromFolder, ToFolder); + + public override void ApplyUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailRemovedMessage(Item)); + } + + public override void RevertUIChanges() + { + WeakReferenceMessenger.Default.Send(new MailAddedMessage(Item)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public record BatchArchiveRequest(bool IsArchiving, IEnumerable Items, MailItemFolder FromFolder, MailItemFolder ToFolder = null) : BatchRequestBase(Items, MailSynchronizerOperation.Archive) + { + public override void ApplyUIChanges() + { + Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailRemovedMessage(item.Item))); + } + + public override void RevertUIChanges() + { + Items.ForEach(item => WeakReferenceMessenger.Default.Send(new MailAddedMessage(item.Item))); + } + } +} diff --git a/Wino.Core/Services/WinoRequestProcessor.cs b/Wino.Core/Services/WinoRequestProcessor.cs index 93c51693..768fdaa5 100644 --- a/Wino.Core/Services/WinoRequestProcessor.cs +++ b/Wino.Core/Services/WinoRequestProcessor.cs @@ -160,20 +160,37 @@ namespace Wino.Core.Services } else if (action == MailOperation.Archive) { - // Validate archive folder exists. + // For IMAP and Outlook: Validate archive folder exists. + // Gmail doesn't need archive folder existence. - var archiveFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Archive) + MailItemFolder archiveFolder = null; + + bool shouldRequireArchiveFolder = mailItem.AssignedAccount.ProviderType == MailProviderType.Outlook + || mailItem.AssignedAccount.ProviderType == MailProviderType.IMAP4 + || mailItem.AssignedAccount.ProviderType == MailProviderType.Office365; + + if (shouldRequireArchiveFolder) + { + archiveFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Archive) ?? throw new UnavailableSpecialFolderException(SpecialFolderType.Archive, mailItem.AssignedAccount.Id); + } - return new MoveRequest(mailItem, mailItem.AssignedFolder, archiveFolder); + return new ArchiveRequest(true, mailItem, mailItem.AssignedFolder, archiveFolder); } - else if (action == MailOperation.UnArchive || action == MailOperation.MarkAsNotJunk) + else if (action == MailOperation.MarkAsNotJunk) { var inboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Inbox) ?? throw new UnavailableSpecialFolderException(SpecialFolderType.Inbox, mailItem.AssignedAccount.Id); return new MoveRequest(mailItem, mailItem.AssignedFolder, inboxFolder); } + else if (action == MailOperation.UnArchive) + { + var inboxFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Inbox) + ?? throw new UnavailableSpecialFolderException(SpecialFolderType.Inbox, mailItem.AssignedAccount.Id); + + return new ArchiveRequest(false, mailItem, mailItem.AssignedFolder, inboxFolder); + } else if (action == MailOperation.SoftDelete) { var trashFolder = await _folderService.GetSpecialFolderByAccountIdAsync(mailItem.AssignedAccount.Id, SpecialFolderType.Deleted) diff --git a/Wino.Core/Synchronizers/BaseSynchronizer.cs b/Wino.Core/Synchronizers/BaseSynchronizer.cs index be42895a..c1d700a4 100644 --- a/Wino.Core/Synchronizers/BaseSynchronizer.cs +++ b/Wino.Core/Synchronizers/BaseSynchronizer.cs @@ -272,6 +272,9 @@ namespace Wino.Core.Synchronizers case MailSynchronizerOperation.CreateDraft: yield return CreateDraft((BatchCreateDraftRequest)item); break; + case MailSynchronizerOperation.Archive: + yield return Archive((BatchArchiveRequest)item); + break; } } }; @@ -324,6 +327,7 @@ namespace Wino.Core.Synchronizers public virtual IEnumerable> MoveToFocused(BatchMoveToFocusedRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual IEnumerable> CreateDraft(BatchCreateDraftRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); public virtual IEnumerable> SendDraft(BatchSendDraftRequestRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); + public virtual IEnumerable> Archive(BatchArchiveRequest request) => throw new NotSupportedException(string.Format(Translator.Exception_UnsupportedSynchronizerOperation, this.GetType())); /// /// Downloads a single missing message from synchronizer and saves it to given FileId from IMailItem. diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index bad599be..357edcf1 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -635,6 +635,28 @@ namespace Wino.Core.Synchronizers }); } + public override IEnumerable> Archive(BatchArchiveRequest request) + { + return CreateBatchedHttpBundleFromGroup(request, (items) => + { + var batchModifyRequest = new BatchModifyMessagesRequest + { + Ids = items.Select(a => a.Item.Id.ToString()).ToList() + }; + + if (request.IsArchiving) + { + batchModifyRequest.RemoveLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; + } + else + { + batchModifyRequest.AddLabelIds = new[] { GoogleIntegratorExtensions.INBOX_LABEL_ID }; + } + + return _gmailService.Users.Messages.BatchModify(batchModifyRequest, "me"); + }); + } + public override IEnumerable> SendDraft(BatchSendDraftRequestRequest request) { return CreateHttpBundle(request, (item) => diff --git a/Wino.Core/Synchronizers/ImapSynchronizer.cs b/Wino.Core/Synchronizers/ImapSynchronizer.cs index ea59cc16..befdf353 100644 --- a/Wino.Core/Synchronizers/ImapSynchronizer.cs +++ b/Wino.Core/Synchronizers/ImapSynchronizer.cs @@ -272,6 +272,9 @@ namespace Wino.Core.Synchronizers }, request); } + public override IEnumerable> Archive(BatchArchiveRequest request) + => Move(new BatchMoveRequest(request.Items, request.FromFolder, request.ToFolder)); + public override IEnumerable> SendDraft(BatchSendDraftRequestRequest request) { return CreateTaskBundle(async (ImapClient client) => diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 8e609c8e..4392c5ff 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -572,6 +572,9 @@ namespace Wino.Core.Synchronizers return [deleteBundle, sendMailRequest]; } + public override IEnumerable> Archive(BatchArchiveRequest request) + => Move(new BatchMoveRequest(request.Items, request.FromFolder, request.ToFolder)); + public override async Task DownloadMissingMimeMessageAsync(IMailItem mailItem, MailKit.ITransferProgress transferProgress = null, CancellationToken cancellationToken = default)