diff --git a/Wino.Core.Domain/Enums/InvalidMoveTargetReason.cs b/Wino.Core.Domain/Enums/InvalidMoveTargetReason.cs
new file mode 100644
index 00000000..8b0f4b37
--- /dev/null
+++ b/Wino.Core.Domain/Enums/InvalidMoveTargetReason.cs
@@ -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.
+}
diff --git a/Wino.Core.Domain/Exceptions/InvalidMoveTargetException.cs b/Wino.Core.Domain/Exceptions/InvalidMoveTargetException.cs
index 830a8205..f3e7b1ae 100644
--- a/Wino.Core.Domain/Exceptions/InvalidMoveTargetException.cs
+++ b/Wino.Core.Domain/Exceptions/InvalidMoveTargetException.cs
@@ -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;
+}
diff --git a/Wino.Core.Domain/Interfaces/IMailService.cs b/Wino.Core.Domain/Interfaces/IMailService.cs
index e4617bba..34331e16 100644
--- a/Wino.Core.Domain/Interfaces/IMailService.cs
+++ b/Wino.Core.Domain/Interfaces/IMailService.cs
@@ -141,4 +141,12 @@ public interface IMailService
///
/// Account id.
Task HasAccountAnyDraftAsync(Guid accountId);
+
+ ///
+ /// Compares the ids returned from online search result for Archive folder against the local database.
+ ///
+ /// Archive folder id.
+ /// Retrieved MailCopy ids from search result.
+ /// Result model that contains added and removed mail copy ids.
+ Task GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List onlineArchiveMailIds);
}
diff --git a/Wino.Core.Domain/MenuItems/FolderMenuItem.cs b/Wino.Core.Domain/MenuItems/FolderMenuItem.cs
index ce5d5abc..c5cbbf5d 100644
--- a/Wino.Core.Domain/MenuItems/FolderMenuItem.cs
+++ b/Wino.Core.Domain/MenuItems/FolderMenuItem.cs
@@ -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;
@@ -32,6 +31,8 @@ public partial class FolderMenuItem : MenuItemBase
+/// Comparison result of the Gmail archive.
+///
+/// Mail copy ids to be added to Archive.
+/// Mail copy ids to be removed from Archive.
+public record GmailArchiveComparisonResult(string[] Added, string[] Removed);
diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json
index f6e3fd26..15cbec78 100644
--- a/Wino.Core.Domain/Translations/en_US/resources.json
+++ b/Wino.Core.Domain/Translations/en_US/resources.json
@@ -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",
@@ -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",
diff --git a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
index e7cab535..2b44cdbb 100644
--- a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
+++ b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
@@ -63,6 +63,16 @@ public interface IDefaultChangeProcessor
Task GetMailCopyAsync(string mailCopyId);
Task CreateMailRawAsync(MailAccount account, MailItemFolder mailItemFolder, NewMailItemPackage package);
Task DeleteUserMailCacheAsync(Guid accountId);
+
+ ///
+ /// 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.
+ ///
+ /// Message id
+ /// Folder's local id.
+ /// Whether mail exists in the folder or not.
+ Task IsMailExistsInFolderAsync(string messageId, Guid folderId);
}
public interface IGmailChangeProcessor : IDefaultChangeProcessor
@@ -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 GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List onlineArchiveMailIds);
}
public interface IOutlookChangeProcessor : IDefaultChangeProcessor
{
- ///
- /// 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.
- ///
- /// Message id
- /// Folder's local id.
- /// Whether mail exists in the folder or not.
- Task IsMailExistsInFolderAsync(string messageId, Guid folderId);
-
///
/// Updates Folder's delta synchronization identifier.
/// Only used in Outlook since it does per-folder sync.
@@ -211,4 +213,7 @@ public class DefaultChangeProcessor(IDatabaseService databaseService,
await _mimeFileService.DeleteUserMimeCacheAsync(accountId).ConfigureAwait(false);
await AccountService.DeleteAccountMailCacheAsync(accountId, AccountCacheResetReason.ExpiredCache).ConfigureAwait(false);
}
+
+ public Task IsMailExistsInFolderAsync(string messageId, Guid folderId)
+ => MailService.IsMailExistsAsync(messageId, folderId);
}
diff --git a/Wino.Core/Integration/Processors/GmailChangeProcessor.cs b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs
index 0c1ea2da..c478308d 100644
--- a/Wino.Core/Integration/Processors/GmailChangeProcessor.cs
+++ b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs
@@ -8,6 +8,7 @@ using Wino.Core.Domain.Entities.Calendar;
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;
@@ -310,4 +311,7 @@ public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcesso
public Task HasAccountAnyDraftAsync(Guid accountId)
=> MailService.HasAccountAnyDraftAsync(accountId);
+
+ public Task GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List onlineArchiveMailIds)
+ => MailService.GetGmailArchiveComparisonResultAsync(archiveFolderId, onlineArchiveMailIds);
}
diff --git a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs
index aaecf767..74efaea1 100644
--- a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs
+++ b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs
@@ -21,9 +21,6 @@ public class OutlookChangeProcessor(IDatabaseService databaseService,
, IOutlookChangeProcessor
{
- public Task IsMailExistsInFolderAsync(string messageId, Guid folderId)
- => MailService.IsMailExistsAsync(messageId, folderId);
-
public Task ResetAccountDeltaTokenAsync(Guid accountId)
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, null);
diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs
index 7f7384fa..38003c62 100644
--- a/Wino.Core/Services/WinoRequestDelegator.cs
+++ b/Wino.Core/Services/WinoRequestDelegator.cs
@@ -53,9 +53,19 @@ public class WinoRequestDelegator : IWinoRequestDelegator
_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)
{
diff --git a/Wino.Core/Services/WinoRequestProcessor.cs b/Wino.Core/Services/WinoRequestProcessor.cs
index 9aae4f09..5eead88c 100644
--- a/Wino.Core/Services/WinoRequestProcessor.cs
+++ b/Wino.Core/Services/WinoRequestProcessor.cs
@@ -75,8 +75,13 @@ public class WinoRequestProcessor : IWinoRequestProcessor
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;
@@ -142,7 +147,7 @@ public class WinoRequestProcessor : IWinoRequestProcessor
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;
diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs
index 54965f50..f02778f1 100644
--- a/Wino.Core/Synchronizers/GmailSynchronizer.cs
+++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs
@@ -63,6 +63,9 @@ public class GmailSynchronizer : WinoSynchronizer();
+ // Keeping a reference for quick access to the virtual archive folder.
+ private Guid? archiveFolderId;
+
public GmailSynchronizer(MailAccount account,
IGmailAuthenticator authenticator,
IGmailChangeProcessor gmailChangeProcessor) : base(account)
@@ -120,6 +123,10 @@ public class GmailSynchronizer : WinoSynchronizer a.History != null)
- .SelectMany(a => a.History)
- .Where(a => a.MessagesDeleted != null)
- .SelectMany(a => a.MessagesDeleted);
-
- var deletedMailIdsInHistory = messageDeletedHistoryChanges.Select(a => a.Message.Id);
-
- if (deletedMailIdsInHistory.Any())
- {
- var mailIdsToConsolidate = missingMessageIds.Where(a => deletedMailIdsInHistory.Contains(a)).ToList();
-
- int consolidatedMessageCount = missingMessageIds.RemoveAll(a => deletedMailIdsInHistory.Contains(a));
-
- if (consolidatedMessageCount > 0)
- {
- // TODO: Also delete the history changes that are related to these mails.
- // This will prevent unwanted logs and additional queries to look for them in processing.
-
- _logger.Information($"Purged {consolidatedMessageCount} missing mail downloads. ({string.Join(",", mailIdsToConsolidate)})");
- }
- }
-
// Start downloading missing messages.
await BatchDownloadMessagesAsync(missingMessageIds, cancellationToken).ConfigureAwait(false);
+ // Map archive assignments if there are any changes reported.
+ if (listChanges.Any() || deltaChanges.Any())
+ {
+ await MapArchivedMailsAsync(cancellationToken).ConfigureAwait(false);
+ }
+
// Map remote drafts to local drafts.
await MapDraftIdsAsync(cancellationToken).ConfigureAwait(false);
@@ -484,6 +468,51 @@ public class GmailSynchronizer : WinoSynchronizer a.SpecialFolderType == SpecialFolderType.Archive))
+ {
+ archiveFolderId = Guid.NewGuid();
+
+ var archiveFolder = new MailItemFolder()
+ {
+ FolderName = "Archive", // will be localized. N/A
+ RemoteFolderId = ServiceConstants.ARCHIVE_LABEL_ID,
+ Id = archiveFolderId.Value,
+ MailAccountId = Account.Id,
+ SpecialFolderType = SpecialFolderType.Archive,
+ IsSynchronizationEnabled = true,
+ IsSystemFolder = true,
+ IsSticky = true,
+ IsHidden = false,
+ ShowUnreadCount = true
+ };
+
+ await _gmailChangeProcessor.InsertFolderAsync(archiveFolder).ConfigureAwait(false);
+
+ // Migration-> User might've already have another special folder for Archive.
+ // We must remove that type assignment.
+ // This code can be removed after sometime.
+
+ var otherArchiveFolders = localFolders.Where(a => a.SpecialFolderType == SpecialFolderType.Archive && a.Id != archiveFolderId.Value).ToList();
+
+ foreach (var otherArchiveFolder in otherArchiveFolders)
+ {
+ otherArchiveFolder.SpecialFolderType = SpecialFolderType.Other;
+ await _gmailChangeProcessor.UpdateFolderAsync(otherArchiveFolder).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ archiveFolderId = localFolders.First(a => a.SpecialFolderType == SpecialFolderType.Archive && a.RemoteFolderId == ServiceConstants.ARCHIVE_LABEL_ID).Id;
+ }
+ }
+
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
{
var localFolders = await _gmailChangeProcessor.GetLocalFoldersAsync(Account.Id).ConfigureAwait(false);
@@ -502,12 +531,14 @@ public class GmailSynchronizer : WinoSynchronizer deletedFolders = new();
// 1. Handle deleted labels.
-
foreach (var localFolder in localFolders)
{
// Category folder is virtual folder for Wino. Skip it.
if (localFolder.SpecialFolderType == SpecialFolderType.Category) continue;
+ // Gmail's Archive folder is virtual older for Wino. Skip it.
+ if (localFolder.SpecialFolderType == SpecialFolderType.Archive) continue;
+
var remoteFolder = labelsResponse.Labels.FirstOrDefault(a => a.Id == localFolder.RemoteFolderId);
if (remoteFolder == null)
@@ -558,7 +589,6 @@ public class GmailSynchronizer : WinoSynchronizer Insert, Update. Deleted ones are already processed.
-
foreach (var folder in insertedFolders)
{
await _gmailChangeProcessor.InsertFolderAsync(folder).ConfigureAwait(false);
@@ -731,6 +761,29 @@ public class GmailSynchronizer : WinoSynchronizer
+ /// Gmail Archive is a special folder that is not visible in the Gmail web interface.
+ /// We need to handle it separately.
+ ///
+ /// Cancellation token.
+ private async Task MapArchivedMailsAsync(CancellationToken cancellationToken)
+ {
+ var request = _gmailService.Users.Messages.List("me");
+ request.Q = "in:archive";
+ request.MaxResults = InitialMessageDownloadCountPerFolder;
+
+ string pageToken = null;
+
+ var archivedMessageIds = new List();
+
+ do
+ {
+ if (!string.IsNullOrEmpty(pageToken)) request.PageToken = pageToken;
+
+ var response = await request.ExecuteAsync(cancellationToken);
+ if (response.Messages == null) break;
+
+ foreach (var message in response.Messages)
+ {
+ if (archivedMessageIds.Contains(message.Id)) continue;
+
+ archivedMessageIds.Add(message.Id);
+ }
+
+ pageToken = response.NextPageToken;
+ } while (!string.IsNullOrEmpty(pageToken));
+
+ var result = await _gmailChangeProcessor.GetGmailArchiveComparisonResultAsync(archiveFolderId.Value, archivedMessageIds).ConfigureAwait(false);
+
+ foreach (var archiveAddedItem in result.Added)
+ {
+ await HandleArchiveAssignmentAsync(archiveAddedItem);
+ }
+
+ foreach (var unAarchivedRemovedItem in result.Removed)
+ {
+ await HandleUnarchiveAssignmentAsync(unAarchivedRemovedItem);
+ }
+ }
+
///
/// Maps existing Gmail Draft resources to local mail copies.
/// This uses indexed search, therefore it's quite fast.
diff --git a/Wino.Mail.ViewModels/AppShellViewModel.cs b/Wino.Mail.ViewModels/AppShellViewModel.cs
index 057a3b71..4b23fe77 100644
--- a/Wino.Mail.ViewModels/AppShellViewModel.cs
+++ b/Wino.Mail.ViewModels/AppShellViewModel.cs
@@ -976,7 +976,12 @@ public partial class AppShellViewModel : MailBaseViewModel,
{
await RecreateMenuItemsAsync();
- if (MenuItems.FirstOrDefault(a => a is IAccountMenuItem) is IAccountMenuItem firstAccount)
+ // Try to restore latest selected account.
+ if (latestSelectedAccountMenuItem != null)
+ {
+ await ChangeLoadedAccountAsync(latestSelectedAccountMenuItem, navigateInbox: true);
+ }
+ else if (MenuItems.FirstOrDefault(a => a is IAccountMenuItem) is IAccountMenuItem firstAccount)
{
await ChangeLoadedAccountAsync(firstAccount, message.AutomaticallyNavigateFirstItem);
}
diff --git a/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs b/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs
index 85856bb7..dfa4c0ee 100644
--- a/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs
+++ b/Wino.Mail.ViewModels/Collections/WinoMailCollection.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Collections;
+using Serilog;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
@@ -363,53 +364,62 @@ public class WinoMailCollection
public MailItemViewModel GetNextItem(MailCopy mailCopy)
{
- var groupCount = _mailItemSource.Count;
-
- for (int i = 0; i < groupCount; i++)
+ try
{
- var group = _mailItemSource[i];
+ var groupCount = _mailItemSource.Count;
- for (int k = 0; k < group.Count; k++)
+ for (int i = 0; i < groupCount; i++)
{
- var item = group[k];
+ var group = _mailItemSource[i];
- if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == mailCopy.UniqueId)
+ for (int k = 0; k < group.Count; k++)
{
- if (k + 1 < group.Count)
- {
- return group[k + 1] as MailItemViewModel;
- }
- else if (i + 1 < groupCount)
- {
- return _mailItemSource[i + 1][0] as MailItemViewModel;
- }
- else
- {
- return null;
- }
- }
- else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailCopy.UniqueId))
- {
- var singleItemViewModel = threadMailItemViewModel.GetItemById(mailCopy.UniqueId) as MailItemViewModel;
+ var item = group[k];
- if (singleItemViewModel == null) return null;
-
- var singleItemIndex = threadMailItemViewModel.ThreadItems.IndexOf(singleItemViewModel);
-
- if (singleItemIndex + 1 < threadMailItemViewModel.ThreadItems.Count)
+ if (item is MailItemViewModel singleMailItemViewModel && singleMailItemViewModel.UniqueId == mailCopy.UniqueId)
{
- return threadMailItemViewModel.ThreadItems[singleItemIndex + 1] as MailItemViewModel;
+ if (k + 1 < group.Count)
+ {
+ return group[k + 1] as MailItemViewModel;
+ }
+ else if (i + 1 < groupCount)
+ {
+ return _mailItemSource[i + 1][0] as MailItemViewModel;
+ }
+ else
+ {
+ return null;
+ }
}
- else if (i + 1 < groupCount)
+ else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(mailCopy.UniqueId))
{
- return _mailItemSource[i + 1][0] as MailItemViewModel;
- }
- else
- {
- return null;
+ var singleItemViewModel = threadMailItemViewModel.GetItemById(mailCopy.UniqueId) as MailItemViewModel;
+
+ if (singleItemViewModel == null) return null;
+
+ var singleItemIndex = threadMailItemViewModel.ThreadItems.IndexOf(singleItemViewModel);
+
+ if (singleItemIndex + 1 < threadMailItemViewModel.ThreadItems.Count)
+ {
+ return threadMailItemViewModel.ThreadItems[singleItemIndex + 1] as MailItemViewModel;
+ }
+ else if (i + 1 < groupCount)
+ {
+ return _mailItemSource[i + 1][0] as MailItemViewModel;
+ }
+ else
+ {
+ return null;
+ }
}
}
}
+
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Log.Warning(ex, "Failed to find the next item to select.");
}
return null;
diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs
index baf7e40d..056e493f 100644
--- a/Wino.Mail.ViewModels/MailListPageViewModel.cs
+++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs
@@ -711,7 +711,10 @@ public partial class MailListPageViewModel : MailBaseViewModel,
if (isDeletedMailSelected && PreferencesService.AutoSelectNextItem)
{
- nextItem = MailCollection.GetNextItem(removedMail);
+ await ExecuteUIThread(() =>
+ {
+ nextItem = MailCollection.GetNextItem(removedMail);
+ });
}
// Remove the deleted item from the list.
diff --git a/Wino.Server/ServerContext.cs b/Wino.Server/ServerContext.cs
index 79b2454e..82bb7092 100644
--- a/Wino.Server/ServerContext.cs
+++ b/Wino.Server/ServerContext.cs
@@ -86,6 +86,7 @@ public class ServerContext :
private async void SynchronizationTimerTriggered(object sender, System.Timers.ElapsedEventArgs e)
{
+#if !DEBUG
// Send sync request for all accounts.
var accounts = await _accountService.GetAccountsAsync();
@@ -102,6 +103,7 @@ public class ServerContext :
await ExecuteServerMessageSafeAsync(null, request);
}
+#endif
}
#region Message Handlers
diff --git a/Wino.Services/MailService.cs b/Wino.Services/MailService.cs
index 054a781d..c498fc27 100644
--- a/Wino.Services/MailService.cs
+++ b/Wino.Services/MailService.cs
@@ -1059,4 +1059,16 @@ public class MailService : BaseDatabaseService, IMailService
public Task IsMailExistsAsync(string mailCopyId, Guid folderId)
=> Connection.ExecuteScalarAsync("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ? AND FolderId = ?)", mailCopyId, folderId);
+
+ public async Task GetGmailArchiveComparisonResultAsync(Guid archiveFolderId, List onlineArchiveMailIds)
+ {
+ var localArchiveMails = await Connection.Table()
+ .Where(a => a.FolderId == archiveFolderId)
+ .ToListAsync().ConfigureAwait(false);
+
+ var removedMails = localArchiveMails.Where(a => !onlineArchiveMailIds.Contains(a.Id)).Select(a => a.Id).Distinct().ToArray();
+ var addedMails = onlineArchiveMailIds.Where(a => !localArchiveMails.Select(b => b.Id).Contains(a)).Distinct().ToArray();
+
+ return new GmailArchiveComparisonResult(addedMails, removedMails);
+ }
}
diff --git a/Wino.Services/ServiceConstants.cs b/Wino.Services/ServiceConstants.cs
index 005ff31f..331506fe 100644
--- a/Wino.Services/ServiceConstants.cs
+++ b/Wino.Services/ServiceConstants.cs
@@ -16,6 +16,7 @@ public static class ServiceConstants
public const string SPAM_LABEL_ID = "SPAM";
public const string CHAT_LABEL_ID = "CHAT";
public const string TRASH_LABEL_ID = "TRASH";
+ public const string ARCHIVE_LABEL_ID = "ARCHIVE";
// Category labels.
public const string FORUMS_LABEL_ID = "FORUMS";