Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gmail - Archive/Unarchive #582

Merged
merged 6 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Wino.Core.Domain/Enums/InvalidMoveTargetReason.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Wino.Core.Domain.Enums;

public enum InvalidMoveTargetReason
{
NonMoveTarget, // This folder does not allow moving mails.
MultipleAccounts // Multiple mails from different accounts cannot be moved.
}
6 changes: 5 additions & 1 deletion Wino.Core.Domain/Exceptions/InvalidMoveTargetException.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using Wino.Core.Domain.Enums;

namespace Wino.Core.Domain.Exceptions;

public class InvalidMoveTargetException : Exception { }
public class InvalidMoveTargetException(InvalidMoveTargetReason reason) : Exception
{
public InvalidMoveTargetReason Reason { get; } = reason;
}
8 changes: 8 additions & 0 deletions Wino.Core.Domain/Interfaces/IMailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,12 @@ public interface IMailService
/// </summary>
/// <param name="accountId">Account id.</param>
Task<bool> HasAccountAnyDraftAsync(Guid accountId);

/// <summary>
/// Compares the ids returned from online search result for Archive folder against the local database.
/// </summary>
/// <param name="archiveFolderId">Archive folder id.</param>
/// <param name="onlineArchiveMailIds">Retrieved MailCopy ids from search result.</param>
/// <returns>Result model that contains added and removed mail copy ids.</returns>
Task<GmailArchiveComparisonResult> GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List<string> onlineArchiveMailIds);
}
3 changes: 2 additions & 1 deletion Wino.Core.Domain/MenuItems/FolderMenuItem.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using Wino.Core.Domain;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
Expand Down Expand Up @@ -32,6 +31,8 @@ public string FolderName
return Translator.MoreFolderNameOverride;
else if (Parameter.SpecialFolderType == SpecialFolderType.Category)
return Translator.CategoriesFolderNameOverride;
else if (Parameter.SpecialFolderType == SpecialFolderType.Archive && ParentAccount.ProviderType == MailProviderType.Gmail)
return Translator.GmailArchiveFolderNameOverride;
else
return Parameter.FolderName;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Wino.Core.Domain.Models.MailItem;

/// <summary>
/// Comparison result of the Gmail archive.
/// </summary>
/// <param name="Added">Mail copy ids to be added to Archive.</param>
/// <param name="Removed">Mail copy ids to be removed from Archive.</param>
public record GmailArchiveComparisonResult(string[] Added, string[] Removed);
2 changes: 2 additions & 0 deletions Wino.Core.Domain/Translations/en_US/resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
"Exception_ImapClientPoolFailed": "IMAP Client Pool failed.",
"Exception_InboxNotAvailable": "Couldn't setup account folders.",
"Exception_InvalidSystemFolderConfiguration": "System folder configuration is not valid. Check configuration and try again.",
"Exception_InvalidMultiAccountMoveTarget": "You can't move multiple items that belong to different accounts in linked account.",
"Exception_MailProcessing": "This mail is still being processed. Please try again after few seconds.",
"Exception_MissingAlias": "Primary alias does not exist for this account. Creating draft failed.",
"Exception_NullAssignedAccount": "Assigned account is null",
Expand Down Expand Up @@ -218,6 +219,7 @@
"GeneralTitle_Warning": "Warning",
"GmailServiceDisabled_Title": "Gmail Error",
"GmailServiceDisabled_Message": "Your Google Workspace account seems to be disabled for Gmail service. Please contact your administrator to enable Gmail service for your account.",
"GmailArchiveFolderNameOverride": "Archive",
"HoverActionOption_Archive": "Archive",
"HoverActionOption_Delete": "Delete",
"HoverActionOption_MoveJunk": "Move to Junk",
Expand Down
23 changes: 14 additions & 9 deletions Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ public interface IDefaultChangeProcessor
Task<MailCopy> GetMailCopyAsync(string mailCopyId);
Task CreateMailRawAsync(MailAccount account, MailItemFolder mailItemFolder, NewMailItemPackage package);
Task DeleteUserMailCacheAsync(Guid accountId);

/// <summary>
/// Checks whether the mail exists in the folder.
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
/// Also duplicate assignments for Gmail's virtual Archive folder is ignored.
/// </summary>
/// <param name="messageId">Message id</param>
/// <param name="folderId">Folder's local id.</param>
/// <returns>Whether mail exists in the folder or not.</returns>
Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId);
}

public interface IGmailChangeProcessor : IDefaultChangeProcessor
Expand All @@ -71,19 +81,11 @@ public interface IGmailChangeProcessor : IDefaultChangeProcessor
Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId);
Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId);
Task ManageCalendarEventAsync(Event calendarEvent, AccountCalendar assignedCalendar, MailAccount organizerAccount);
Task<GmailArchiveComparisonResult> GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List<string> onlineArchiveMailIds);
}

public interface IOutlookChangeProcessor : IDefaultChangeProcessor
{
/// <summary>
/// Checks whether the mail exists in the folder.
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
/// </summary>
/// <param name="messageId">Message id</param>
/// <param name="folderId">Folder's local id.</param>
/// <returns>Whether mail exists in the folder or not.</returns>
Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId);

/// <summary>
/// Updates Folder's delta synchronization identifier.
/// Only used in Outlook since it does per-folder sync.
Expand Down Expand Up @@ -211,4 +213,7 @@ public async Task DeleteUserMailCacheAsync(Guid accountId)
await _mimeFileService.DeleteUserMimeCacheAsync(accountId).ConfigureAwait(false);
await AccountService.DeleteAccountMailCacheAsync(accountId, AccountCacheResetReason.ExpiredCache).ConfigureAwait(false);
}

public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
=> MailService.IsMailExistsAsync(messageId, folderId);
}
4 changes: 4 additions & 0 deletions Wino.Core/Integration/Processors/GmailChangeProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Extensions;
using Wino.Services;
using CalendarEventAttendee = Wino.Core.Domain.Entities.Calendar.CalendarEventAttendee;
Expand Down Expand Up @@ -310,4 +311,7 @@ private CalendarItemVisibility GetVisibility(string visibility)

public Task<bool> HasAccountAnyDraftAsync(Guid accountId)
=> MailService.HasAccountAnyDraftAsync(accountId);

public Task<GmailArchiveComparisonResult> GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List<string> onlineArchiveMailIds)
=> MailService.GetGmailArchiveComparisonResultAsync(archiveFolderId, onlineArchiveMailIds);
}
3 changes: 0 additions & 3 deletions Wino.Core/Integration/Processors/OutlookChangeProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
, IOutlookChangeProcessor
{

public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
=> MailService.IsMailExistsAsync(messageId, folderId);

public Task<string> ResetAccountDeltaTokenAsync(Guid accountId)
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, null);

Expand Down
14 changes: 12 additions & 2 deletions Wino.Core/Services/WinoRequestDelegator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,19 @@ public async Task ExecuteAsync(MailOperationPreperationRequest request)
_dialogService.HandleSystemFolderConfigurationDialogAsync(unavailableSpecialFolderException.AccountId, _folderService);
});
}
catch (InvalidMoveTargetException)
catch (InvalidMoveTargetException invalidMoveTargetException)
{
_dialogService.InfoBarMessage(Translator.Info_InvalidMoveTargetTitle, Translator.Info_InvalidMoveTargetMessage, InfoBarMessageType.Warning);
switch (invalidMoveTargetException.Reason)
{
case InvalidMoveTargetReason.NonMoveTarget:
_dialogService.InfoBarMessage(Translator.Info_InvalidMoveTargetTitle, Translator.Info_InvalidMoveTargetMessage, InfoBarMessageType.Warning);
break;
case InvalidMoveTargetReason.MultipleAccounts:
_dialogService.InfoBarMessage(Translator.Info_InvalidMoveTargetTitle, Translator.Exception_InvalidMultiAccountMoveTarget, InfoBarMessageType.Warning);
break;
default:
break;
}
}
catch (NotImplementedException)
{
Expand Down
11 changes: 8 additions & 3 deletions Wino.Core/Services/WinoRequestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ public async Task<List<IMailActionRequest>> PrepareRequestsAsync(MailOperationPr

if (action == MailOperation.Move && moveTargetStructure == null)
{
// TODO: Handle multiple accounts for move operation.
// What happens if we move 2 different mails from 2 different accounts?
// Handle the case when user is trying to move multiple mails that belong to different accounts.
// We can't handle this with only 1 picker dialog.

bool isInvalidMoveTarget = preperationRequest.MailItems.Select(a => a.AssignedAccount.Id).Distinct().Count() > 1;

if (isInvalidMoveTarget)
throw new InvalidMoveTargetException(InvalidMoveTargetReason.MultipleAccounts);

var accountId = preperationRequest.MailItems.FirstOrDefault().AssignedAccount.Id;

Expand Down Expand Up @@ -142,7 +147,7 @@ private async Task<IMailActionRequest> GetSingleRequestAsync(MailCopy mailItem,
else if (action == MailOperation.Move)
{
if (moveTargetStructure == null)
throw new InvalidMoveTargetException();
throw new InvalidMoveTargetException(InvalidMoveTargetReason.NonMoveTarget);

// TODO
// Rule: You can't move items to non-move target folders;
Expand Down
Loading