diff --git a/Wino.Core.Domain/Interfaces/IFolderService.cs b/Wino.Core.Domain/Interfaces/IFolderService.cs index 9b58ce11..fab3fadc 100644 --- a/Wino.Core.Domain/Interfaces/IFolderService.cs +++ b/Wino.Core.Domain/Interfaces/IFolderService.cs @@ -49,15 +49,6 @@ namespace Wino.Core.Domain.Interfaces /// Folders to update. Task BulkUpdateFolderStructureAsync(Guid accountId, List allFolders); - /// - /// Updates Folder's delta synchronization identifier. - /// Only used in Outlook since it does per-folder sync. - /// - /// Folder id - /// New synchronization identifier. - /// New identifier if success. - Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier); - /// /// Deletes the folder for the given account by remote folder id. /// @@ -87,6 +78,12 @@ namespace Wino.Core.Domain.Interfaces /// True if Inbox exists, False if not. Task IsInboxAvailableForAccountAsync(Guid accountId); + /// + /// Updates folder's LastSynchronizedDate to now. + /// + /// Folder to update. + Task UpdateFolderLastSyncDateAsync(Guid folderId); + Task TestAsync(); } } diff --git a/Wino.Core.Domain/Interfaces/IMailService.cs b/Wino.Core.Domain/Interfaces/IMailService.cs index 30f7083f..6c1be462 100644 --- a/Wino.Core.Domain/Interfaces/IMailService.cs +++ b/Wino.Core.Domain/Interfaces/IMailService.cs @@ -53,7 +53,6 @@ namespace Wino.Core.Domain.Interfaces /// /// /// - /// Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId); Task CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions options); diff --git a/Wino.Core/CoreContainerSetup.cs b/Wino.Core/CoreContainerSetup.cs index c2cd5839..f57b66ee 100644 --- a/Wino.Core/CoreContainerSetup.cs +++ b/Wino.Core/CoreContainerSetup.cs @@ -22,8 +22,10 @@ namespace Wino.Core services.AddSingleton(); services.AddSingleton(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs index 0bbcb6d4..bc5e0595 100644 --- a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs @@ -14,43 +14,31 @@ namespace Wino.Core.Integration.Processors /// Database change processor that handles common operations for all synchronizers. /// When a synchronizer detects a change, it should call the appropriate method in this class to reflect the change in the database. /// Different synchronizers might need additional implementations. - /// and + /// , and /// None of the synchronizers can directly change anything in the database. /// public interface IDefaultChangeProcessor { Task UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string deltaSynchronizationIdentifier); - Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier); - Task CreateAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId); Task DeleteAssignmentAsync(Guid accountId, string mailCopyId, string remoteFolderId); - Task ChangeMailReadStatusAsync(string mailCopyId, bool isRead); Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged); - Task CreateMailAsync(Guid AccountId, NewMailItemPackage package); Task DeleteMailAsync(Guid accountId, string mailId); - - Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId); - Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId); - Task> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable downloadedMailCopyIds); - Task SaveMimeFileAsync(Guid fileId, MimeMessage mimeMessage, Guid accountId); - - // For gmail. Task UpdateFolderStructureAsync(Guid accountId, List allFolders); - Task DeleteFolderAsync(Guid accountId, string remoteFolderId); Task> GetSynchronizationFoldersAsync(SynchronizationOptions options); Task InsertFolderAsync(MailItemFolder folder); - - Task> GetKnownUidsForFolderAsync(Guid folderId); + Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId); + Task UpdateFolderLastSyncDateAsync(Guid folderId); } public interface IGmailChangeProcessor : IDefaultChangeProcessor { - + Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId); } public interface IOutlookChangeProcessor : IDefaultChangeProcessor @@ -62,6 +50,24 @@ namespace Wino.Core.Integration.Processors /// MailCopyId of the message. /// Whether the mime has b Task IsMailExistsAsync(string messageId); + + /// + /// Updates Folder's delta synchronization identifier. + /// Only used in Outlook since it does per-folder sync. + /// + /// Folder id + /// New synchronization identifier. + /// New identifier if success. + Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier); + } + + public interface IImapChangeProcessor : IDefaultChangeProcessor + { + /// + /// Returns all known uids for the given folder. + /// + /// Folder id to retrieve uIds for. + Task> GetKnownUidsForFolderAsync(Guid folderId); } public class DefaultChangeProcessor(IDatabaseService databaseService, @@ -72,7 +78,7 @@ namespace Wino.Core.Integration.Processors { protected IMailService MailService = mailService; - private readonly IFolderService _folderService = folderService; + protected IFolderService FolderService = folderService; private readonly IAccountService _accountService = accountService; private readonly IMimeFileService _mimeFileService = mimeFileService; @@ -99,33 +105,31 @@ namespace Wino.Core.Integration.Processors // Folder methods public Task UpdateFolderStructureAsync(Guid accountId, List allFolders) - => _folderService.BulkUpdateFolderStructureAsync(accountId, allFolders); + => FolderService.BulkUpdateFolderStructureAsync(accountId, allFolders); public Task MapLocalDraftAsync(Guid accountId, Guid localDraftCopyUniqueId, string newMailCopyId, string newDraftId, string newThreadId) => MailService.MapLocalDraftAsync(accountId, localDraftCopyUniqueId, newMailCopyId, newDraftId, newThreadId); - public Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId) - => MailService.MapLocalDraftAsync(mailCopyId, newDraftId, newThreadId); + public Task> GetSynchronizationFoldersAsync(SynchronizationOptions options) - => _folderService.GetSynchronizationFoldersAsync(options); - - public Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier) - => _folderService.UpdateFolderDeltaSynchronizationIdentifierAsync(folderId, deltaSynchronizationIdentifier); + => FolderService.GetSynchronizationFoldersAsync(options); public Task DeleteFolderAsync(Guid accountId, string remoteFolderId) - => _folderService.DeleteFolderAsync(accountId, remoteFolderId); + => FolderService.DeleteFolderAsync(accountId, remoteFolderId); public Task InsertFolderAsync(MailItemFolder folder) - => _folderService.InsertFolderAsync(folder); + => FolderService.InsertFolderAsync(folder); public Task> GetDownloadedUnreadMailsAsync(Guid accountId, IEnumerable downloadedMailCopyIds) => MailService.GetDownloadedUnreadMailsAsync(accountId, downloadedMailCopyIds); - public Task> GetKnownUidsForFolderAsync(Guid folderId) - => _folderService.GetKnownUidsForFolderAsync(folderId); + public Task SaveMimeFileAsync(Guid fileId, MimeMessage mimeMessage, Guid accountId) => _mimeFileService.SaveMimeMessageAsync(fileId, mimeMessage, accountId); + + public Task UpdateFolderLastSyncDateAsync(Guid folderId) + => FolderService.UpdateFolderLastSyncDateAsync(folderId); } } diff --git a/Wino.Core/Integration/Processors/GmailChangeProcessor.cs b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs new file mode 100644 index 00000000..72eb1dee --- /dev/null +++ b/Wino.Core/Integration/Processors/GmailChangeProcessor.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Services; + +namespace Wino.Core.Integration.Processors +{ + public class GmailChangeProcessor : DefaultChangeProcessor, IGmailChangeProcessor + { + public GmailChangeProcessor(IDatabaseService databaseService, IFolderService folderService, IMailService mailService, IAccountService accountService, IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, accountService, mimeFileService) + { + } + + public Task MapLocalDraftAsync(string mailCopyId, string newDraftId, string newThreadId) + => MailService.MapLocalDraftAsync(mailCopyId, newDraftId, newThreadId); + } +} diff --git a/Wino.Core/Integration/Processors/ImapChangeProcessor.cs b/Wino.Core/Integration/Processors/ImapChangeProcessor.cs new file mode 100644 index 00000000..f4bbdee2 --- /dev/null +++ b/Wino.Core/Integration/Processors/ImapChangeProcessor.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Wino.Core.Domain.Interfaces; +using Wino.Core.Services; + +namespace Wino.Core.Integration.Processors +{ + public class ImapChangeProcessor : DefaultChangeProcessor, IImapChangeProcessor + { + public ImapChangeProcessor(IDatabaseService databaseService, + IFolderService folderService, + IMailService mailService, + IAccountService accountService, + IMimeFileService mimeFileService) : base(databaseService, folderService, mailService, accountService, mimeFileService) + { + } + + public Task> GetKnownUidsForFolderAsync(Guid folderId) + => FolderService.GetKnownUidsForFolderAsync(folderId); + } +} diff --git a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs index c36d3991..7c28f494 100644 --- a/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs +++ b/Wino.Core/Integration/Processors/OutlookChangeProcessor.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Wino.Core.Domain.Interfaces; using Wino.Core.Services; @@ -12,5 +13,8 @@ namespace Wino.Core.Integration.Processors { public Task IsMailExistsAsync(string messageId) => MailService.IsMailExistsAsync(messageId); + + public Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier) + => Connection.ExecuteAsync("UPDATE MailItemFolder SET DeltaToken = ? WHERE Id = ?", synchronizationIdentifier, folderId); } } diff --git a/Wino.Core/Services/FolderService.cs b/Wino.Core/Services/FolderService.cs index 07449f1b..32e6a33a 100644 --- a/Wino.Core/Services/FolderService.cs +++ b/Wino.Core/Services/FolderService.cs @@ -513,23 +513,7 @@ namespace Wino.Core.Services } } - public async Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier) - { - var folder = await GetFolderAsync(folderId).ConfigureAwait(false); - if (folder == null) - { - _logger.Warning("Folder with id {FolderId} does not exist.", folderId); - - return string.Empty; - } - - folder.DeltaToken = synchronizationIdentifier; - - await UpdateFolderAsync(folder).ConfigureAwait(false); - - return synchronizationIdentifier; - } public async Task DeleteFolderAsync(Guid accountId, string remoteFolderId) { @@ -588,5 +572,8 @@ namespace Wino.Core.Services => (await Connection.Table() .Where(a => a.SpecialFolderType == SpecialFolderType.Inbox && a.MailAccountId == accountId) .CountAsync()) == 1; + + public Task UpdateFolderLastSyncDateAsync(Guid folderId) + => Connection.ExecuteAsync("UPDATE MailItemFolder SET LastSynchronizedDate = ? WHERE Id = ?", DateTime.UtcNow, folderId); } } diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs index 917e3493..5abc5e00 100644 --- a/Wino.Core/Synchronizers/GmailSynchronizer.cs +++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs @@ -41,12 +41,12 @@ namespace Wino.Core.Synchronizers private readonly ConfigurableHttpClient _gmailHttpClient; private readonly GmailService _gmailService; private readonly IAuthenticator _authenticator; - private readonly IDefaultChangeProcessor _gmailChangeProcessor; + private readonly IGmailChangeProcessor _gmailChangeProcessor; private readonly ILogger _logger = Log.ForContext(); public GmailSynchronizer(MailAccount account, IAuthenticator authenticator, - IDefaultChangeProcessor gmailChangeProcessor) : base(account) + IGmailChangeProcessor gmailChangeProcessor) : base(account) { var messageHandler = new GmailClientMessageHandler(() => _authenticator.GetTokenAsync(Account)); diff --git a/Wino.Core/Synchronizers/ImapSynchronizer.cs b/Wino.Core/Synchronizers/ImapSynchronizer.cs index 05227106..9c357970 100644 --- a/Wino.Core/Synchronizers/ImapSynchronizer.cs +++ b/Wino.Core/Synchronizers/ImapSynchronizer.cs @@ -33,7 +33,7 @@ namespace Wino.Core.Synchronizers private readonly ILogger _logger = Log.ForContext(); private readonly ImapClientPool _clientPool; - private readonly IDefaultChangeProcessor _imapChangeProcessor; + private readonly IImapChangeProcessor _imapChangeProcessor; // Minimum summary items to Fetch for mail synchronization from IMAP. private readonly MessageSummaryItems mailSynchronizationFlags = @@ -61,7 +61,7 @@ namespace Wino.Core.Synchronizers public override uint BatchModificationSize => 1000; public override uint InitialMessageDownloadCountPerFolder => 500; - public ImapSynchronizer(MailAccount account, IDefaultChangeProcessor imapChangeProcessor) : base(account) + public ImapSynchronizer(MailAccount account, IImapChangeProcessor imapChangeProcessor) : base(account) { _clientPool = new ImapClientPool(Account.ServerInformation); @@ -830,10 +830,9 @@ namespace Wino.Core.Synchronizers await _imapChangeProcessor.InsertFolderAsync(folder); } - // Update last synchronization identifier. - // This will update last sync date for the folder. + // Update last synchronization date for the folder.. - await _imapChangeProcessor.UpdateFolderDeltaSynchronizationIdentifierAsync(folder.Id, string.Empty).ConfigureAwait(false); + await _imapChangeProcessor.UpdateFolderLastSyncDateAsync(folder.Id).ConfigureAwait(false); return downloadedMessageIds; } diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index 8947dbb7..073c84b0 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -93,7 +93,6 @@ namespace Wino.Core.Synchronizers try { - options.ProgressListener?.AccountProgressUpdated(Account.Id, 1); await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false); @@ -102,6 +101,9 @@ namespace Wino.Core.Synchronizers { var synchronizationFolders = await _outlookChangeProcessor.GetSynchronizationFoldersAsync(options).ConfigureAwait(false); + _logger.Information("Found {Count} folders to synchronize.", synchronizationFolders.Count); + _logger.Information(string.Format("Folders: {0}", string.Join(",", synchronizationFolders.Select(a => a.FolderName)))); + for (int i = 0; i < synchronizationFolders.Count; i++) { var folder = synchronizationFolders[i]; @@ -149,6 +151,8 @@ namespace Wino.Core.Synchronizers if (isInitialSync) { + _logger.Debug("No sync identifier for Folder {FolderName}. Performing initial sync.", folder.FolderName); + // No delta link. Performing initial sync. messageCollectionPage = await _graphClient.Me.MailFolders[folder.RemoteFolderId].Messages.Delta.GetAsDeltaGetResponseAsync((config) => @@ -162,6 +166,9 @@ namespace Wino.Core.Synchronizers { var currentDeltaToken = folder.DeltaToken; + _logger.Debug("Sync identifier found for Folder {FolderName}. Performing delta sync.", folder.FolderName); + _logger.Debug("Current delta token: {CurrentDeltaToken}", currentDeltaToken); + var requestInformation = _graphClient.Me.MailFolders[folder.RemoteFolderId].Messages.Delta.ToGetRequestInformation((config) => { config.QueryParameters.Top = (int)InitialMessageDownloadCountPerFolder; @@ -186,6 +193,14 @@ namespace Wino.Core.Synchronizers latestDeltaLink = messageIteratorAsync.Deltalink; + if (downloadedMessageIds.Any()) + { + _logger.Debug("Downloaded {Count} messages for folder {FolderName}", downloadedMessageIds.Count, folder.FolderName); + } + + _logger.Debug("Iterator completed for folder {FolderName}", folder.FolderName); + _logger.Debug("Extracted latest delta link is {LatestDeltaLink}", latestDeltaLink); + //Store delta link for tracking new changes. if (!string.IsNullOrEmpty(latestDeltaLink)) { @@ -196,6 +211,8 @@ namespace Wino.Core.Synchronizers await _outlookChangeProcessor.UpdateFolderDeltaSynchronizationIdentifierAsync(folder.Id, deltaToken).ConfigureAwait(false); } + await _outlookChangeProcessor.UpdateFolderLastSyncDateAsync(folder.Id).ConfigureAwait(false); + return downloadedMessageIds; } diff --git a/Wino.Core/WinoSynchronizerFactory.cs b/Wino.Core/WinoSynchronizerFactory.cs index 2ca63a47..d01b7c86 100644 --- a/Wino.Core/WinoSynchronizerFactory.cs +++ b/Wino.Core/WinoSynchronizerFactory.cs @@ -37,8 +37,9 @@ namespace Wino.Core private readonly ISignatureService _signatureService; private readonly IDatabaseService _databaseService; private readonly IMimeFileService _mimeFileService; - private readonly IDefaultChangeProcessor _defaultChangeProcessor; private readonly IOutlookChangeProcessor _outlookChangeProcessor; + private readonly IGmailChangeProcessor _gmailChangeProcessor; + private readonly IImapChangeProcessor _imapChangeProcessor; public WinoSynchronizerFactory(INativeAppService nativeAppService, ITokenService tokenService, @@ -49,8 +50,9 @@ namespace Wino.Core ISignatureService signatureService, IDatabaseService databaseService, IMimeFileService mimeFileService, - IDefaultChangeProcessor defaultChangeProcessor, - IOutlookChangeProcessor outlookChangeProcessor) + IOutlookChangeProcessor outlookChangeProcessor, + IGmailChangeProcessor gmailChangeProcessor, + IImapChangeProcessor imapChangeProcessor) { _contactService = contactService; _notificationBuilder = notificationBuilder; @@ -61,8 +63,9 @@ namespace Wino.Core _signatureService = signatureService; _databaseService = databaseService; _mimeFileService = mimeFileService; - _defaultChangeProcessor = defaultChangeProcessor; _outlookChangeProcessor = outlookChangeProcessor; + _gmailChangeProcessor = gmailChangeProcessor; + _imapChangeProcessor = imapChangeProcessor; } public IBaseSynchronizer GetAccountSynchronizer(Guid accountId) @@ -80,13 +83,13 @@ namespace Wino.Core case Domain.Enums.MailProviderType.Gmail: var gmailAuthenticator = new GmailAuthenticator(_tokenService, _nativeAppService); - return new GmailSynchronizer(mailAccount, gmailAuthenticator, _defaultChangeProcessor); + return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor); case Domain.Enums.MailProviderType.Office365: break; case Domain.Enums.MailProviderType.Yahoo: break; case Domain.Enums.MailProviderType.IMAP4: - return new ImapSynchronizer(mailAccount, _defaultChangeProcessor); + return new ImapSynchronizer(mailAccount, _imapChangeProcessor); default: break; }