Full trust Wino Server implementation. (#295)
* Separation of messages. Introducing Wino.Messages library. * Wino.Server and Wino.Packaging projects. Enabling full trust for UWP and app service connection manager basics. * Remove debug code. * Enable generating assembly info to deal with unsupported os platform warnings. * Fix server-client connection. * UIMessage communication. Single instancing for server and re-connection mechanism on suspension. * Removed IWinoSynchronizerFactory from UWP project. * Removal of background task service from core. * Delegating changes to UI and triggering new background synchronization. * Fix build error. * Moved core lib messages to Messaging project. * Better client-server communication. Handling of requests in the server. New synchronizer factory in the server. * WAM broker and MSAL token caching for OutlookAuthenticator. Handling account creation for Outlook. * WinoServerResponse basics. * Delegating protocol activation for Gmail authenticator. * Adding margin to searchbox to match action bar width. * Move libraries into lib folder. * Storing base64 encoded mime on draft creation instead of MimeMessage object. Fixes serialization/deserialization issue with S.T.Json * Scrollbar adjustments * WınoExpander for thread expander layout ıssue. * Handling synchronizer state changes. * Double init on background activation. * FIxing packaging issues and new Wino Mail launcher protocol for activation from full thrust process. * Remove debug deserialization. * Remove debug code. * Making sure the server connection is established when the app is launched. * Thrust -> Trust string replacement... * Rename package to Wino Mail * Enable translated values in the server. * Fixed an issue where toast activation can't find the clicked mail after the folder is initialized. * Revert debug code. * Change server background sync to every 3 minute and Inbox only synchronization. * Revert google auth changes. * App preferences page. * Changing tray icon visibility on preference change. * Start the server with invisible tray icon if set to invisible. * Reconnect button on the title bar. * Handling of toast actions. * Enable x86 build for server during packaging. * Get rid of old background tasks and v180 migration. * Terminate client when Exit clicked in server. * Introducing SynchronizationSource to prevent notifying UI after server tick synchronization. * Remove confirmAppClose restricted capability and unused debug code in manifest. * Closing the reconnect info popup when reconnect is clicked. * Custom RetryHandler for OutlookSynchronizer and separating client/server logs. * Running server on Windows startup. * Fix startup exe. * Fix for expander list view item paddings. * Force full sync on app launch instead of Inbox. * Fix draft creation. * Fix an issue with custom folder sync logic. * Reporting back account sync progress from server. * Fix sending drafts and missing notifications for imap. * Changing imap folder sync requirements. * Retain file count is set to 3. * Disabled swipe gestures temporarily due to native crash with SwipeControl * Save all attachments implementation. * Localization for save all attachments button. * Fix logging dates for logs. * Fixing ARM64 build. * Add ARM64 build config to packaging project. * Comment out OutOfProcPDB for ARM64. * Hnadling GONE response for Outlook folder synchronization.
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Services;
|
||||
|
||||
namespace Wino.Core.Authenticators
|
||||
{
|
||||
public class CustomAuthenticator : BaseAuthenticator, IAuthenticator
|
||||
{
|
||||
public CustomAuthenticator(ITokenService tokenService) : base(tokenService) { }
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.IMAP4;
|
||||
|
||||
public string ClientId => throw new NotImplementedException(); // Not needed.
|
||||
|
||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
||||
|
||||
public void CancelAuthorization() { }
|
||||
|
||||
public void ContinueAuthorization(Uri authorizationResponseUri) { }
|
||||
|
||||
public Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Nito.AsyncEx;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -13,11 +11,10 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Authentication;
|
||||
using Wino.Core.Domain.Models.Authorization;
|
||||
using Wino.Core.Services;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace Wino.Core.Authenticators
|
||||
{
|
||||
public class GmailAuthenticator : BaseAuthenticator, IAuthenticator
|
||||
public class GmailAuthenticator : BaseAuthenticator, IGmailAuthenticator
|
||||
{
|
||||
public string ClientId { get; } = "973025879644-s7b4ur9p3rlgop6a22u7iuptdc0brnrn.apps.googleusercontent.com";
|
||||
|
||||
@@ -27,9 +24,6 @@ namespace Wino.Core.Authenticators
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Gmail;
|
||||
|
||||
private TaskCompletionSource<Uri> _authorizationCompletionSource = null;
|
||||
private CancellationTokenSource _authorizationCancellationTokenSource = null;
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
|
||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
||||
@@ -99,8 +93,6 @@ namespace Wino.Core.Authenticators
|
||||
};
|
||||
}
|
||||
|
||||
public void ContinueAuthorization(Uri authorizationResponseUri) => _authorizationCompletionSource?.TrySetResult(authorizationResponseUri);
|
||||
|
||||
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||
{
|
||||
var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
|
||||
@@ -127,29 +119,19 @@ namespace Wino.Core.Authenticators
|
||||
{
|
||||
var authRequest = _nativeAppService.GetGoogleAuthorizationRequest();
|
||||
|
||||
_authorizationCompletionSource = new TaskCompletionSource<Uri>();
|
||||
_authorizationCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var authorizationUri = authRequest.BuildRequest(ClientId);
|
||||
|
||||
await Browser.OpenAsync(authorizationUri, BrowserLaunchMode.SystemPreferred);
|
||||
|
||||
Uri responseRedirectUri = null;
|
||||
|
||||
try
|
||||
{
|
||||
responseRedirectUri = await _authorizationCompletionSource.Task.WaitAsync(_authorizationCancellationTokenSource.Token);
|
||||
//await _authorizationCompletionSource.Task.WaitAsync(_authorizationCancellationTokenSource.Token);
|
||||
responseRedirectUri = await _nativeAppService.GetAuthorizationResponseUriAsync(this, authorizationUri);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
catch (Exception)
|
||||
{
|
||||
throw new AuthenticationException(Translator.Exception_AuthenticationCanceled);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_authorizationCancellationTokenSource.Dispose();
|
||||
_authorizationCancellationTokenSource = null;
|
||||
_authorizationCompletionSource = null;
|
||||
}
|
||||
|
||||
authRequest.ValidateAuthorizationCode(responseRedirectUri);
|
||||
|
||||
@@ -213,7 +195,5 @@ namespace Wino.Core.Authenticators
|
||||
RefreshToken = activeRefreshToken
|
||||
};
|
||||
}
|
||||
|
||||
public void CancelAuthorization() => _authorizationCancellationTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Wino.Core.Authenticators
|
||||
{
|
||||
public class Office365Authenticator : OutlookAuthenticator
|
||||
{
|
||||
public Office365Authenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService, nativeAppService) { }
|
||||
public Office365Authenticator(ITokenService tokenService, INativeAppService nativeAppService, IApplicationConfiguration applicationConfiguration) : base(tokenService, nativeAppService, applicationConfiguration) { }
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Office365;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Identity.Client;
|
||||
using Microsoft.Identity.Client.Broker;
|
||||
using Microsoft.Identity.Client.Extensions.Msal;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
@@ -12,76 +14,82 @@ using Wino.Core.Services;
|
||||
|
||||
namespace Wino.Core.Authenticators
|
||||
{
|
||||
public class OutlookAuthenticator : BaseAuthenticator, IAuthenticator
|
||||
/// <summary>
|
||||
/// Authenticator for Outlook provider.
|
||||
/// Token cache is managed by MSAL, not by Wino.
|
||||
/// </summary>
|
||||
public class OutlookAuthenticator : BaseAuthenticator, IOutlookAuthenticator
|
||||
{
|
||||
private const string TokenCacheFileName = "OutlookCache.bin";
|
||||
private bool isTokenCacheAttached = false;
|
||||
|
||||
// Outlook
|
||||
private const string Authority = "https://login.microsoftonline.com/common";
|
||||
|
||||
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||
|
||||
private readonly string[] MailScope = new string[] { "email", "mail.readwrite", "offline_access", "mail.send" };
|
||||
private readonly string[] MailScope = ["email", "mail.readwrite", "offline_access", "mail.send"];
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||
|
||||
private readonly IPublicClientApplication _publicClientApplication;
|
||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||
|
||||
public OutlookAuthenticator(ITokenService tokenService, INativeAppService nativeAppService) : base(tokenService)
|
||||
public OutlookAuthenticator(ITokenService tokenService,
|
||||
INativeAppService nativeAppService,
|
||||
IApplicationConfiguration applicationConfiguration) : base(tokenService)
|
||||
{
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
|
||||
var authenticationRedirectUri = nativeAppService.GetWebAuthenticationBrokerUri();
|
||||
|
||||
_publicClientApplication = PublicClientApplicationBuilder.Create(ClientId)
|
||||
.WithAuthority(Authority)
|
||||
.WithRedirectUri(authenticationRedirectUri)
|
||||
.Build();
|
||||
var options = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
|
||||
{
|
||||
Title = "Wino Mail",
|
||||
ListOperatingSystemAccounts = true,
|
||||
};
|
||||
|
||||
var outlookAppBuilder = PublicClientApplicationBuilder.Create(ClientId)
|
||||
.WithParentActivityOrWindow(nativeAppService.GetCoreWindowHwnd)
|
||||
.WithBroker(options)
|
||||
.WithDefaultRedirectUri()
|
||||
.WithAuthority(Authority);
|
||||
|
||||
_publicClientApplication = outlookAppBuilder.Build();
|
||||
}
|
||||
|
||||
#pragma warning disable S1133 // Deprecated code should be removed
|
||||
[Obsolete("Not used for OutlookAuthenticator.")]
|
||||
#pragma warning restore S1133 // Deprecated code should be removed
|
||||
public void ContinueAuthorization(Uri authorizationResponseUri) { }
|
||||
|
||||
#pragma warning disable S1133 // Deprecated code should be removed
|
||||
[Obsolete("Not used for OutlookAuthenticator.")]
|
||||
#pragma warning restore S1133 // Deprecated code should be removed
|
||||
public void CancelAuthorization() { }
|
||||
|
||||
public async Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||
{
|
||||
var cachedToken = await TokenService.GetTokenInformationAsync(account.Id)
|
||||
?? throw new AuthenticationAttentionException(account);
|
||||
|
||||
// We have token but it's expired.
|
||||
// Silently refresh the token and save new token.
|
||||
|
||||
if (cachedToken.IsExpired)
|
||||
if (!isTokenCacheAttached)
|
||||
{
|
||||
var cachedOutlookAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
||||
var storageProperties = new StorageCreationPropertiesBuilder(TokenCacheFileName, _applicationConfiguration.PublisherSharedFolderPath).Build();
|
||||
var msalcachehelper = await MsalCacheHelper.CreateAsync(storageProperties);
|
||||
msalcachehelper.RegisterCache(_publicClientApplication.UserTokenCache);
|
||||
|
||||
// Again, not expected at all...
|
||||
// Force interactive login at this point.
|
||||
|
||||
if (cachedOutlookAccount == null)
|
||||
{
|
||||
// What if interactive login info is for different account?
|
||||
|
||||
return await GenerateTokenAsync(account, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Silently refresh token from cache.
|
||||
|
||||
AuthenticationResult authResult = await _publicClientApplication.AcquireTokenSilent(MailScope, cachedOutlookAccount).ExecuteAsync();
|
||||
|
||||
// Save refreshed token and return
|
||||
var refreshedTokenInformation = authResult.CreateTokenInformation();
|
||||
|
||||
await TokenService.SaveTokenInformationAsync(account.Id, refreshedTokenInformation);
|
||||
|
||||
return refreshedTokenInformation;
|
||||
}
|
||||
isTokenCacheAttached = true;
|
||||
}
|
||||
|
||||
var storedAccount = (await _publicClientApplication.GetAccountsAsync()).FirstOrDefault(a => a.Username == account.Address);
|
||||
|
||||
// TODO: Handle it from the server.
|
||||
if (storedAccount == null) throw new AuthenticationAttentionException(account);
|
||||
|
||||
try
|
||||
{
|
||||
var authResult = await _publicClientApplication.AcquireTokenSilent(MailScope, storedAccount).ExecuteAsync();
|
||||
|
||||
return authResult.CreateTokenInformation() ?? throw new Exception("Failed to get Outlook token.");
|
||||
}
|
||||
catch (MsalUiRequiredException)
|
||||
{
|
||||
// Somehow MSAL is not able to refresh the token silently.
|
||||
// Force interactive login.
|
||||
return await GenerateTokenAsync(account, true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
public async Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Services;
|
||||
|
||||
namespace Wino.Core.Authenticators
|
||||
{
|
||||
public class YahooAuthenticator : BaseAuthenticator, IAuthenticator
|
||||
{
|
||||
public YahooAuthenticator(ITokenService tokenService) : base(tokenService) { }
|
||||
|
||||
public override MailProviderType ProviderType => MailProviderType.Yahoo;
|
||||
|
||||
public string ClientId => throw new NotImplementedException();
|
||||
|
||||
public event EventHandler<string> InteractiveAuthenticationRequired;
|
||||
|
||||
public void CancelAuthorization()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ContinueAuthorization(Uri authorizationResponseUri)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<TokenInformation> GenerateTokenAsync(MailAccount account, bool saveToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<TokenInformation> GetTokenAsync(MailAccount account)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog.Core;
|
||||
using Wino.Core.Authenticators;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Integration.Processors;
|
||||
using Wino.Core.Integration.Threading;
|
||||
@@ -16,9 +17,9 @@ namespace Wino.Core
|
||||
services.AddSingleton(loggerLevelSwitcher);
|
||||
services.AddSingleton<ILogInitializer, LogInitializer>();
|
||||
|
||||
services.AddSingleton<IApplicationConfiguration, ApplicationConfiguration>();
|
||||
services.AddSingleton<ITranslationService, TranslationService>();
|
||||
services.AddSingleton<IDatabaseService, DatabaseService>();
|
||||
services.AddSingleton<IWinoSynchronizerFactory, WinoSynchronizerFactory>();
|
||||
services.AddSingleton<IThreadingStrategyProvider, ThreadingStrategyProvider>();
|
||||
services.AddSingleton<IMimeFileService, MimeFileService>();
|
||||
|
||||
@@ -42,9 +43,14 @@ namespace Wino.Core
|
||||
services.AddTransient<IFontService, FontService>();
|
||||
services.AddTransient<IUnsubscriptionService, UnsubscriptionService>();
|
||||
|
||||
services.AddTransient<IOutlookAuthenticator, OutlookAuthenticator>();
|
||||
services.AddTransient<IGmailAuthenticator, GmailAuthenticator>();
|
||||
|
||||
services.AddTransient<OutlookThreadingStrategy>();
|
||||
services.AddTransient<GmailThreadingStrategy>();
|
||||
services.AddTransient<ImapThreadStrategy>();
|
||||
|
||||
services.AddSingleton<ISynchronizerFactory, SynchronizerFactory>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,15 @@ namespace Wino.Core.Extensions
|
||||
{
|
||||
public static TokenInformation CreateTokenInformation(this AuthenticationResult clientBuilderResult)
|
||||
{
|
||||
var expirationDate = clientBuilderResult.ExpiresOn.UtcDateTime;
|
||||
var accesToken = clientBuilderResult.AccessToken;
|
||||
var userName = clientBuilderResult.Account.Username;
|
||||
|
||||
// MSAL does not expose refresh token for security reasons.
|
||||
// This token info will be created without refresh token.
|
||||
// but OutlookIntegrator will ask for publicApplication to refresh it
|
||||
// in case of expiration.
|
||||
// Plain access token info is not stored for Outlook in Wino's database.
|
||||
// Here we store UniqueId and Access Token in memory only to compare the UniqueId returned from MSAL auth result.
|
||||
|
||||
var tokenInfo = new TokenInformation()
|
||||
{
|
||||
ExpiresAt = expirationDate,
|
||||
AccessToken = accesToken,
|
||||
Address = userName,
|
||||
Address = clientBuilderResult.Account.Username,
|
||||
Id = Guid.NewGuid(),
|
||||
UniqueId = clientBuilderResult.UniqueId,
|
||||
AccessToken = clientBuilderResult.AccessToken
|
||||
};
|
||||
|
||||
return tokenInfo;
|
||||
|
||||
49
Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs
Normal file
49
Wino.Core/Integration/Json/ServerRequestTypeInfoResolver.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Requests;
|
||||
|
||||
namespace Wino.Core.Integration.Json
|
||||
{
|
||||
public class ServerRequestTypeInfoResolver : DefaultJsonTypeInfoResolver
|
||||
{
|
||||
public ServerRequestTypeInfoResolver()
|
||||
{
|
||||
Modifiers.Add(new System.Action<JsonTypeInfo>(t =>
|
||||
{
|
||||
if (t.Type == typeof(IRequestBase))
|
||||
{
|
||||
t.PolymorphismOptions = new()
|
||||
{
|
||||
DerivedTypes =
|
||||
{
|
||||
new JsonDerivedType(typeof(AlwaysMoveToRequest), nameof(AlwaysMoveToRequest)),
|
||||
new JsonDerivedType(typeof(ArchiveRequest), nameof(ArchiveRequest)),
|
||||
new JsonDerivedType(typeof(ChangeFlagRequest), nameof(ChangeFlagRequest)),
|
||||
new JsonDerivedType(typeof(CreateDraftRequest), nameof(CreateDraftRequest)),
|
||||
new JsonDerivedType(typeof(DeleteRequest), nameof(DeleteRequest)),
|
||||
new JsonDerivedType(typeof(EmptyFolderRequest), nameof(EmptyFolderRequest)),
|
||||
new JsonDerivedType(typeof(MarkFolderAsReadRequest), nameof(MarkFolderAsReadRequest)),
|
||||
new JsonDerivedType(typeof(MarkReadRequest), nameof(MarkReadRequest)),
|
||||
new JsonDerivedType(typeof(MoveRequest), nameof(MoveRequest)),
|
||||
new JsonDerivedType(typeof(MoveToFocusedRequest), nameof(MoveToFocusedRequest)),
|
||||
new JsonDerivedType(typeof(RenameFolderRequest), nameof(RenameFolderRequest)),
|
||||
new JsonDerivedType(typeof(SendDraftRequest), nameof(SendDraftRequest)),
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (t.Type == typeof(IMailItem))
|
||||
{
|
||||
t.PolymorphismOptions = new JsonPolymorphismOptions()
|
||||
{
|
||||
DerivedTypes =
|
||||
{
|
||||
new JsonDerivedType(typeof(MailCopy), nameof(MailCopy)),
|
||||
}
|
||||
};
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,15 @@ namespace Wino.Core.Integration.Processors
|
||||
/// <returns>Whether the mime has b</returns>
|
||||
Task<bool> IsMailExistsAsync(string messageId);
|
||||
|
||||
/// <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.
|
||||
@@ -70,6 +79,24 @@ namespace Wino.Core.Integration.Processors
|
||||
/// <param name="synchronizationIdentifier">New synchronization identifier.</param>
|
||||
/// <returns>New identifier if success.</returns>
|
||||
Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string deltaSynchronizationIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Outlook may expire folder's delta token after a while.
|
||||
/// Recommended action for this scenario is to reset token and do full sync.
|
||||
/// This method resets the token for the given folder.
|
||||
/// </summary>
|
||||
/// <param name="folderId">Local folder id to reset token for.</param>
|
||||
/// <returns>Empty string to assign folder delta sync for.</returns>
|
||||
Task<string> ResetFolderDeltaTokenAsync(Guid folderId);
|
||||
|
||||
/// <summary>
|
||||
/// Outlook may expire account's delta token after a while.
|
||||
/// This will result returning 410 GONE response from the API for synchronizing folders.
|
||||
/// This method resets the token for the given account for re-syncing folders.
|
||||
/// </summary>
|
||||
/// <param name="accountId">Account identifier to reset delta token for.</param>
|
||||
/// <returns>Empty string to assign account delta sync for.</returns>
|
||||
Task<string> ResetAccountDeltaTokenAsync(Guid accountId);
|
||||
}
|
||||
|
||||
public interface IImapChangeProcessor : IDefaultChangeProcessor
|
||||
@@ -90,11 +117,11 @@ namespace Wino.Core.Integration.Processors
|
||||
protected IMailService MailService = mailService;
|
||||
|
||||
protected IFolderService FolderService = folderService;
|
||||
private readonly IAccountService _accountService = accountService;
|
||||
protected IAccountService AccountService = accountService;
|
||||
private readonly IMimeFileService _mimeFileService = mimeFileService;
|
||||
|
||||
public Task<string> UpdateAccountDeltaSynchronizationIdentifierAsync(Guid accountId, string synchronizationDeltaIdentifier)
|
||||
=> _accountService.UpdateSynchronizationIdentifierAsync(accountId, synchronizationDeltaIdentifier);
|
||||
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, synchronizationDeltaIdentifier);
|
||||
|
||||
public Task ChangeFlagStatusAsync(string mailCopyId, bool isFlagged)
|
||||
=> MailService.ChangeFlagStatusAsync(mailCopyId, isFlagged);
|
||||
|
||||
@@ -9,11 +9,29 @@ namespace Wino.Core.Integration.Processors
|
||||
IFolderService folderService,
|
||||
IMailService mailService,
|
||||
IAccountService accountService,
|
||||
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, accountService, mimeFileService), IOutlookChangeProcessor
|
||||
IMimeFileService mimeFileService) : DefaultChangeProcessor(databaseService, folderService, mailService, accountService, mimeFileService)
|
||||
, IOutlookChangeProcessor
|
||||
{
|
||||
public Task<bool> IsMailExistsAsync(string messageId)
|
||||
=> MailService.IsMailExistsAsync(messageId);
|
||||
|
||||
public Task<bool> IsMailExistsInFolderAsync(string messageId, Guid folderId)
|
||||
=> MailService.IsMailExistsAsync(messageId, folderId);
|
||||
|
||||
public Task<string> ResetAccountDeltaTokenAsync(Guid accountId)
|
||||
=> AccountService.UpdateSynchronizationIdentifierAsync(accountId, null);
|
||||
|
||||
public async Task<string> ResetFolderDeltaTokenAsync(Guid folderId)
|
||||
{
|
||||
var folder = await FolderService.GetFolderAsync(folderId);
|
||||
|
||||
folder.DeltaToken = null;
|
||||
|
||||
await FolderService.UpdateFolderAsync(folder);
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public Task UpdateFolderDeltaSynchronizationIdentifierAsync(Guid folderId, string synchronizationIdentifier)
|
||||
=> Connection.ExecuteAsync("UPDATE MailItemFolder SET DeltaToken = ? WHERE Id = ?", synchronizationIdentifier, folderId);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
namespace Wino.Core.Messages.Accounts
|
||||
{
|
||||
/// <summary>
|
||||
/// When menu item for the account is requested to be extended.
|
||||
/// Additional properties are also supported to navigate to correct IMailItem.
|
||||
/// </summary>
|
||||
/// <param name="AutoSelectAccount">Account to extend menu item for.</param>
|
||||
/// <param name="FolderId">Folder to select after expansion.</param>
|
||||
/// <param name="NavigateMailItem">Mail item to select if possible in the expanded folder.</param>
|
||||
public record AccountMenuItemExtended(Guid FolderId, IMailItem NavigateMailItem);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Wino.Core.Messages.Accounts
|
||||
{
|
||||
/// <summary>
|
||||
/// Emitted when account menu items are reordered.
|
||||
/// </summary>
|
||||
/// <param name="newOrderDictionary">New order info.</param>
|
||||
public record AccountMenuItemsReordered(Dictionary<Guid, int> newOrderDictionary);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Accounts
|
||||
{
|
||||
/// <summary>
|
||||
/// When a full menu refresh for accounts menu is requested.
|
||||
/// </summary>
|
||||
public record AccountsMenuRefreshRequested(bool AutomaticallyNavigateFirstItem = true);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Messages.Authorization
|
||||
{
|
||||
/// <summary>
|
||||
/// When Google authentication makes a callback to the app via protocol activation to the app.
|
||||
/// </summary>
|
||||
/// <param name="AuthorizationResponseUri">Callback Uri that Google returned.</param>
|
||||
public record ProtocolAuthorizationCallbackReceived(Uri AuthorizationResponseUri);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When rendered html is requested to cancel.
|
||||
/// </summary>
|
||||
public record CancelRenderingContentRequested;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When reset all mail selections requested.
|
||||
/// </summary>
|
||||
public record ClearMailSelectionsRequested;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Wino.Core.Domain.Models.Reader;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When a new composing requested.
|
||||
/// </summary>
|
||||
/// <param name="RenderModel"></param>
|
||||
public record CreateNewComposeMailRequested(MailRenderModel RenderModel);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When rendering frame should be disposed.
|
||||
/// </summary>
|
||||
public class DisposeRenderingFrameRequested { }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When existing a new html is requested to be rendered due to mail selection or signature.
|
||||
/// </summary>
|
||||
/// <param name="HtmlBody">HTML to render in WebView2.</param>
|
||||
public record HtmlRenderingRequested(string HtmlBody);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When IMAP setup dialog requestes back breadcrumb navigation.
|
||||
/// Not providing PageType will go back to previous page by doing back navigation.
|
||||
/// </summary>
|
||||
/// <param name="PageType">Type to go back.</param>
|
||||
/// <param name="Parameter">Back parameters.</param>
|
||||
public record ImapSetupBackNavigationRequested(Type PageType = null, object Parameter = null);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Wino.Core.Domain.Entities;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When user asked to dismiss IMAP setup dialog.
|
||||
/// </summary>
|
||||
/// <param name="CompletedServerInformation"> Validated server information that is ready to be saved to database. </param>
|
||||
public record ImapSetupDismissRequested(CustomServerInformation CompletedServerInformation = null);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When IMAP setup dialog breadcrumb navigation requested.
|
||||
/// </summary>
|
||||
/// <param name="PageType">Page type to navigate.</param>
|
||||
/// <param name="Parameter">Navigation parameters.</param>
|
||||
public record ImapSetupNavigationRequested(Type PageType, object Parameter);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When a IMailItem needs to be navigated (or selected)
|
||||
/// </summary>
|
||||
/// <param name="UniqueMailId">UniqueId of the mail to navigate.</param>
|
||||
/// <param name="ScrollToItem">Whether navigated item should be scrolled to or not..</param>
|
||||
public record MailItemNavigationRequested(Guid UniqueMailId, bool ScrollToItem = false);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// Selects the given FolderMenuItem in the shell folders list.
|
||||
/// </summary>
|
||||
public class NavigateMailFolderEvent : NavigateMailFolderEventArgs
|
||||
{
|
||||
public NavigateMailFolderEvent(IBaseFolderMenuItem baseFolderMenuItem, TaskCompletionSource<bool> folderInitLoadAwaitTask = null)
|
||||
: base(baseFolderMenuItem, folderInitLoadAwaitTask)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
public record RefreshUnreadCountsMessage(Guid AccountId);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When mail save as PDF requested.
|
||||
/// </summary>
|
||||
public record SaveAsPDFRequested(string FileSavePath);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Wino.Core.Messages.Mails
|
||||
{
|
||||
/// <summary>
|
||||
/// When selected mail count is changed.
|
||||
/// </summary>
|
||||
/// <param name="SelectedItemCount">New selected mail count.</param>
|
||||
public record SelectedMailItemsChanged(int SelectedItemCount);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// When back navigation is requested for breadcrumb pages.
|
||||
/// </summary>
|
||||
public record BackBreadcrumNavigationRequested { }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Messages.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// When Breadcrumb control navigation requested.
|
||||
/// </summary>
|
||||
/// <param name="PageTitle">Title to display for the page.</param>
|
||||
/// <param name="PageType">Enum equilavent of the page to navigate.</param>
|
||||
/// <param name="Parameter">Additional parameters to the page.</param>
|
||||
public record BreadcrumbNavigationRequested(string PageTitle, WinoPage PageType, object Parameter = null);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Navigates to settings page.
|
||||
/// </summary>
|
||||
public record NavigateSettingsRequested;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// When the application theme changed.
|
||||
/// </summary>
|
||||
/// <param name="IsUnderlyingThemeDark"></param>
|
||||
public record ApplicationThemeChanged(bool IsUnderlyingThemeDark);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Wino.Core.Domain.Entities;
|
||||
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// When
|
||||
/// - There is no selection of any folder for any account
|
||||
/// - Multiple accounts exists
|
||||
/// - User clicked 'Create New Mail'
|
||||
///
|
||||
/// flyout must be presented to pick correct account.
|
||||
/// This message will be picked up by UWP Shell.
|
||||
/// </summary>
|
||||
public record CreateNewMailWithMultipleAccountsRequested(IEnumerable<MailAccount> AllAccounts);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// For displaying right sliding notification message in shell.
|
||||
/// </summary>
|
||||
/// <param name="Severity">Severity of notification.</param>
|
||||
/// <param name="Title">Title of the message.</param>
|
||||
/// <param name="Message">Message content.</param>
|
||||
public record InfoBarMessageRequested(InfoBarMessageType Severity,
|
||||
string Title,
|
||||
string Message,
|
||||
string ActionButtonTitle = "",
|
||||
Action Action = null);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// When application language is updated.
|
||||
/// </summary>
|
||||
public record LanguageChanged;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
public class MailtoProtocolMessageRequested { }
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// When navigation pane mode is changed.
|
||||
/// </summary>
|
||||
/// <param name="NewMode">New navigation mode.</param>
|
||||
public record NavigationPaneModeChanged(MenuPaneMode NewMode);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Wino.Core.Messages.Shell
|
||||
{
|
||||
/// <summary>
|
||||
/// When reading mail state or reader pane narrowed state is changed.
|
||||
/// </summary>
|
||||
public record ShellStateUpdated;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Enums;
|
||||
|
||||
namespace Wino.Core.Messages.Synchronization
|
||||
{
|
||||
public record AccountSynchronizationCompleted(Guid AccountId, SynchronizationCompletedState Result, Guid? SynchronizationTrackingId);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Synchronizers;
|
||||
|
||||
namespace Wino.Core.Messages.Synchronization
|
||||
{
|
||||
/// <summary>
|
||||
/// Emitted when synchronizer state is updated.
|
||||
/// </summary>
|
||||
/// <param name="synchronizer">Account Synchronizer</param>
|
||||
/// <param name="newState">New state.</param>
|
||||
public record AccountSynchronizerStateChanged(IBaseSynchronizer Synchronizer, AccountSynchronizerState NewState);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
|
||||
namespace Wino.Core.Messages.Synchronization
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers a new synchronization if possible.
|
||||
/// </summary>
|
||||
/// <param name="Options">Options for synchronization.</param>
|
||||
public record NewSynchronizationRequested(SynchronizationOptions Options);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
23
Wino.Core/Requests/Bundles/ServerRequestPackage.cs
Normal file
23
Wino.Core/Requests/Bundles/ServerRequestPackage.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Domain.Models.Requests
|
||||
{
|
||||
///// <summary>
|
||||
///// Encapsulates request to queue and account for synchronizer.
|
||||
///// </summary>
|
||||
///// <param name="AccountId"><inheritdoc/></param>
|
||||
///// <param name="Request"></param>
|
||||
///// <param name="Request">Prepared request for the server.</param>
|
||||
///// <param name="AccountId">Which account to execute this request for.</param>
|
||||
public class ServerRequestBundle(Guid accountId, IRequestBase request) : IClientMessage
|
||||
{
|
||||
public Guid AccountId { get; } = accountId;
|
||||
|
||||
public IRequestBase Request { get; } = request;
|
||||
}
|
||||
|
||||
|
||||
//public record ServerRequestPackage<TUserActionRequestType>(Guid AccountId, TUserActionRequestType Request)
|
||||
// : ServerRequestBundle(AccountId), IClientMessage where TUserActionRequestType : IRequestBase;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
|
||||
@@ -7,11 +7,12 @@ using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
public record SendDraftRequest(SendDraftPreparationRequest Request)
|
||||
: RequestBase<BatchMarkReadRequest>(Request.MailItem, MailSynchronizerOperation.Send),
|
||||
: RequestBase<BatchSendDraftRequestRequest>(Request.MailItem, MailSynchronizerOperation.Send),
|
||||
ICustomFolderSynchronizationRequest
|
||||
{
|
||||
public List<Guid> SynchronizationFolderIds
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
public record MailAddedMessage(MailCopy AddedMail) : IUIMessage;
|
||||
public record MailRemovedMessage(MailCopy RemovedMail) : IUIMessage;
|
||||
public record MailUpdatedMessage(MailCopy UpdatedMail) : IUIMessage;
|
||||
public record MailDownloadedMessage(MailCopy DownloadedMail) : IUIMessage;
|
||||
|
||||
public record AccountCreatedMessage(MailAccount Account) : IUIMessage;
|
||||
public record AccountRemovedMessage(MailAccount Account) : IUIMessage;
|
||||
public record AccountUpdatedMessage(MailAccount Account) : IUIMessage;
|
||||
|
||||
public record DraftCreated(MailCopy DraftMail, MailAccount Account) : IUIMessage;
|
||||
public record DraftFailed(MailCopy DraftMail, MailAccount Account) : IUIMessage;
|
||||
public record DraftMapped(string LocalDraftCopyId, string RemoteDraftCopyId) : IUIMessage;
|
||||
|
||||
public record MergedInboxRenamed(Guid MergedInboxId, string NewName) : IUIMessage;
|
||||
|
||||
public record FolderRenamed(IMailItemFolder MailItemFolder) : IUIMessage;
|
||||
public record FolderSynchronizationEnabled(IMailItemFolder MailItemFolder) : IUIMessage;
|
||||
}
|
||||
@@ -11,8 +11,8 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Core.Messages.Accounts;
|
||||
using Wino.Core.Requests;
|
||||
using Wino.Messaging.Client.Accounts;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
@@ -382,7 +382,10 @@ namespace Wino.Core.Services
|
||||
if (customServerInformation != null)
|
||||
await Connection.InsertAsync(customServerInformation);
|
||||
|
||||
if (tokenInformation != null)
|
||||
// Outlook token cache is managed by MSAL.
|
||||
// Don't save it to database.
|
||||
|
||||
if (tokenInformation != null && account.ProviderType != MailProviderType.Outlook)
|
||||
await Connection.InsertAsync(tokenInformation);
|
||||
}
|
||||
|
||||
|
||||
13
Wino.Core/Services/ApplicationConfiguration.cs
Normal file
13
Wino.Core/Services/ApplicationConfiguration.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
public class ApplicationConfiguration : IApplicationConfiguration
|
||||
{
|
||||
public const string SharedFolderName = "WinoShared";
|
||||
|
||||
public string ApplicationDataFolderPath { get; set; }
|
||||
|
||||
public string PublisherSharedFolderPath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,23 @@ namespace Wino.Core.Services
|
||||
{
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly ITokenService _tokenService;
|
||||
private readonly IApplicationConfiguration _applicationConfiguration;
|
||||
|
||||
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService)
|
||||
public AuthenticationProvider(INativeAppService nativeAppService, ITokenService tokenService, IApplicationConfiguration applicationConfiguration)
|
||||
{
|
||||
_nativeAppService = nativeAppService;
|
||||
_tokenService = tokenService;
|
||||
_applicationConfiguration = applicationConfiguration;
|
||||
}
|
||||
|
||||
public IAuthenticator GetAuthenticator(MailProviderType providerType)
|
||||
{
|
||||
// TODO: Move DI
|
||||
return providerType switch
|
||||
{
|
||||
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService),
|
||||
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService),
|
||||
MailProviderType.Outlook => new OutlookAuthenticator(_tokenService, _nativeAppService, _applicationConfiguration),
|
||||
MailProviderType.Office365 => new Office365Authenticator(_tokenService, _nativeAppService, _applicationConfiguration),
|
||||
MailProviderType.Gmail => new GmailAuthenticator(_tokenService, _nativeAppService),
|
||||
MailProviderType.Yahoo => new YahooAuthenticator(_tokenService),
|
||||
MailProviderType.IMAP4 => new CustomAuthenticator(_tokenService),
|
||||
_ => throw new ArgumentException(Translator.Exception_UnsupportedProvider),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
|
||||
@@ -14,16 +14,16 @@ namespace Wino.Core.Services
|
||||
|
||||
public class DatabaseService : IDatabaseService
|
||||
{
|
||||
private string DatabaseName => "Wino172.db";
|
||||
private const string DatabaseName = "Wino172.db";
|
||||
|
||||
private bool _isInitialized = false;
|
||||
private readonly IAppInitializerService _appInitializerService;
|
||||
private readonly IApplicationConfiguration _folderConfiguration;
|
||||
|
||||
public SQLiteAsyncConnection Connection { get; private set; }
|
||||
|
||||
public DatabaseService(IAppInitializerService appInitializerService)
|
||||
public DatabaseService(IApplicationConfiguration folderConfiguration)
|
||||
{
|
||||
_appInitializerService = appInitializerService;
|
||||
_folderConfiguration = folderConfiguration;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
@@ -31,8 +31,8 @@ namespace Wino.Core.Services
|
||||
if (_isInitialized)
|
||||
return;
|
||||
|
||||
var applicationData = _appInitializerService.GetPublisherSharedFolder();
|
||||
var databaseFileName = Path.Combine(applicationData, DatabaseName);
|
||||
var publisherCacheFolder = _folderConfiguration.PublisherSharedFolderPath;
|
||||
var databaseFileName = Path.Combine(publisherCacheFolder, DatabaseName);
|
||||
|
||||
Connection = new SQLiteAsyncConnection(databaseFileName)
|
||||
{
|
||||
@@ -45,7 +45,6 @@ namespace Wino.Core.Services
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
await CreateTablesAsync();
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
@@ -16,7 +16,7 @@ using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Core.MenuItems;
|
||||
using Wino.Core.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
|
||||
@@ -11,11 +11,11 @@ namespace Wino.Core.Services
|
||||
public const string ProtocolLogFileName = "ImapProtocolLog.log";
|
||||
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
private readonly IAppInitializerService _appInitializerService;
|
||||
private readonly IApplicationConfiguration _appInitializerService;
|
||||
|
||||
private Stream _protocolLogStream;
|
||||
|
||||
public ImapTestService(IPreferencesService preferencesService, IAppInitializerService appInitializerService)
|
||||
public ImapTestService(IPreferencesService preferencesService, IApplicationConfiguration appInitializerService)
|
||||
{
|
||||
_preferencesService = preferencesService;
|
||||
_appInitializerService = appInitializerService;
|
||||
@@ -24,7 +24,7 @@ namespace Wino.Core.Services
|
||||
private void EnsureProtocolLogFileExists()
|
||||
{
|
||||
// Create new file for protocol logger.
|
||||
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder();
|
||||
var localAppFolderPath = _appInitializerService.ApplicationDataFolderPath;
|
||||
|
||||
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.IO;
|
||||
using Serilog;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
@@ -8,8 +7,6 @@ namespace Wino.Core.Services
|
||||
{
|
||||
public class LogInitializer : ILogInitializer
|
||||
{
|
||||
public const string WinoLogFileName = "WinoDiagnostics.log";
|
||||
|
||||
private readonly LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch();
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
@@ -25,13 +22,11 @@ namespace Wino.Core.Services
|
||||
_levelSwitch.MinimumLevel = _preferencesService.IsLoggingEnabled ? Serilog.Events.LogEventLevel.Debug : Serilog.Events.LogEventLevel.Fatal;
|
||||
}
|
||||
|
||||
public void SetupLogger(string logFolderPath)
|
||||
public void SetupLogger(string fullLogFilePath)
|
||||
{
|
||||
string logFilePath = Path.Combine(logFolderPath, WinoLogFileName);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.ControlledBy(_levelSwitch)
|
||||
.WriteTo.File(logFilePath)
|
||||
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
|
||||
.WriteTo.Debug()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithExceptionDetails()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
@@ -10,11 +11,12 @@ using SqlKata;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Comparers;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Extensions;
|
||||
using Wino.Core.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
@@ -52,10 +54,12 @@ namespace Wino.Core.Services
|
||||
}
|
||||
|
||||
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
|
||||
MimeMessage createdDraftMimeMessage,
|
||||
string generatedReplyMimeMessageBase64,
|
||||
MimeMessage replyingMimeMessage = null,
|
||||
IMailItem replyingMailItem = null)
|
||||
{
|
||||
var createdDraftMimeMessage = generatedReplyMimeMessageBase64.GetMimeMessageFromBase64();
|
||||
|
||||
bool isImapAccount = composerAccount.ServerInformation != null;
|
||||
|
||||
string fromName;
|
||||
@@ -625,7 +629,7 @@ namespace Wino.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MimeMessage> CreateDraftMimeMessageAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
|
||||
public async Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions draftCreationOptions)
|
||||
{
|
||||
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
||||
// Same unique id will be used for the local copy as well.
|
||||
@@ -796,7 +800,14 @@ namespace Wino.Core.Services
|
||||
// Update TextBody from existing HtmlBody if exists.
|
||||
}
|
||||
|
||||
return message;
|
||||
using MemoryStream memoryStream = new();
|
||||
message.WriteTo(FormatOptions.Default, memoryStream);
|
||||
byte[] buffer = memoryStream.GetBuffer();
|
||||
int count = (int)memoryStream.Length;
|
||||
|
||||
return Convert.ToBase64String(buffer);
|
||||
|
||||
// return message;
|
||||
|
||||
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
||||
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
||||
@@ -919,5 +930,8 @@ namespace Wino.Core.Services
|
||||
|
||||
public Task<bool> IsMailExistsAsync(string mailCopyId)
|
||||
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ?)", mailCopyId);
|
||||
|
||||
public Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId)
|
||||
=> Connection.ExecuteScalarAsync<bool>("SELECT EXISTS(SELECT 1 FROM MailCopy WHERE Id = ? AND FolderId = ?)", mailCopyId, folderId);
|
||||
}
|
||||
}
|
||||
|
||||
101
Wino.Core/Services/SynchronizerFactory.cs
Normal file
101
Wino.Core/Services/SynchronizerFactory.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Integration.Processors;
|
||||
using Wino.Core.Synchronizers;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
public class SynchronizerFactory : ISynchronizerFactory
|
||||
{
|
||||
private bool isInitialized = false;
|
||||
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IOutlookChangeProcessor _outlookChangeProcessor;
|
||||
private readonly IGmailChangeProcessor _gmailChangeProcessor;
|
||||
private readonly IImapChangeProcessor _imapChangeProcessor;
|
||||
private readonly IOutlookAuthenticator _outlookAuthenticator;
|
||||
private readonly IGmailAuthenticator _gmailAuthenticator;
|
||||
|
||||
private readonly List<IBaseSynchronizer> synchronizerCache = new();
|
||||
|
||||
public SynchronizerFactory(IOutlookChangeProcessor outlookChangeProcessor,
|
||||
IGmailChangeProcessor gmailChangeProcessor,
|
||||
IImapChangeProcessor imapChangeProcessor,
|
||||
IOutlookAuthenticator outlookAuthenticator,
|
||||
IGmailAuthenticator gmailAuthenticator,
|
||||
IAccountService accountService)
|
||||
{
|
||||
_outlookChangeProcessor = outlookChangeProcessor;
|
||||
_gmailChangeProcessor = gmailChangeProcessor;
|
||||
_imapChangeProcessor = imapChangeProcessor;
|
||||
_outlookAuthenticator = outlookAuthenticator;
|
||||
_gmailAuthenticator = gmailAuthenticator;
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
public async Task<IBaseSynchronizer> GetAccountSynchronizerAsync(Guid accountId)
|
||||
{
|
||||
var synchronizer = synchronizerCache.Find(a => a.Account.Id == accountId);
|
||||
|
||||
if (synchronizer == null)
|
||||
{
|
||||
var account = await _accountService.GetAccountAsync(accountId);
|
||||
|
||||
if (account != null)
|
||||
{
|
||||
synchronizer = CreateNewSynchronizer(account);
|
||||
|
||||
return await GetAccountSynchronizerAsync(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
return synchronizer;
|
||||
}
|
||||
|
||||
private IBaseSynchronizer CreateIntegratorWithDefaultProcessor(MailAccount mailAccount)
|
||||
{
|
||||
var providerType = mailAccount.ProviderType;
|
||||
|
||||
switch (providerType)
|
||||
{
|
||||
case Domain.Enums.MailProviderType.Outlook:
|
||||
case Domain.Enums.MailProviderType.Office365:
|
||||
return new OutlookSynchronizer(mailAccount, _outlookAuthenticator, _outlookChangeProcessor);
|
||||
case Domain.Enums.MailProviderType.Gmail:
|
||||
return new GmailSynchronizer(mailAccount, _gmailAuthenticator, _gmailChangeProcessor);
|
||||
case Domain.Enums.MailProviderType.IMAP4:
|
||||
return new ImapSynchronizer(mailAccount, _imapChangeProcessor);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IBaseSynchronizer CreateNewSynchronizer(MailAccount account)
|
||||
{
|
||||
var synchronizer = CreateIntegratorWithDefaultProcessor(account);
|
||||
|
||||
synchronizerCache.Add(synchronizer);
|
||||
|
||||
return synchronizer;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
if (isInitialized) return;
|
||||
|
||||
var accounts = await _accountService.GetAccountsAsync();
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
CreateNewSynchronizer(account);
|
||||
}
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Translations;
|
||||
using Wino.Core.Messages.Shell;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
|
||||
@@ -11,26 +11,26 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Messages.Synchronization;
|
||||
using Wino.Core.Requests;
|
||||
using Wino.Messaging.Server;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
{
|
||||
public class WinoRequestDelegator : IWinoRequestDelegator
|
||||
{
|
||||
private readonly IWinoRequestProcessor _winoRequestProcessor;
|
||||
private readonly IWinoSynchronizerFactory _winoSynchronizerFactory;
|
||||
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||
private readonly IFolderService _folderService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ILogger _logger = Log.ForContext<WinoRequestDelegator>();
|
||||
|
||||
public WinoRequestDelegator(IWinoRequestProcessor winoRequestProcessor,
|
||||
IWinoSynchronizerFactory winoSynchronizerFactory,
|
||||
IWinoServerConnectionManager winoServerConnectionManager,
|
||||
IFolderService folderService,
|
||||
IDialogService dialogService)
|
||||
{
|
||||
_winoRequestProcessor = winoRequestProcessor;
|
||||
_winoSynchronizerFactory = winoSynchronizerFactory;
|
||||
_winoServerConnectionManager = winoServerConnectionManager;
|
||||
_folderService = folderService;
|
||||
_dialogService = dialogService;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Wino.Core.Services
|
||||
{
|
||||
foreach (var accountRequest in accountId)
|
||||
{
|
||||
QueueRequest(accountRequest, accountId.Key);
|
||||
await QueueRequestAsync(accountRequest, accountId.Key);
|
||||
}
|
||||
|
||||
QueueSynchronization(accountId.Key);
|
||||
@@ -107,43 +107,36 @@ namespace Wino.Core.Services
|
||||
|
||||
if (request == null) return;
|
||||
|
||||
QueueRequest(request, accountId);
|
||||
await QueueRequestAsync(request, accountId);
|
||||
QueueSynchronization(accountId);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
||||
public async Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
||||
{
|
||||
var request = new CreateDraftRequest(draftPreperationRequest);
|
||||
|
||||
QueueRequest(request, draftPreperationRequest.Account.Id);
|
||||
await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
|
||||
QueueSynchronization(draftPreperationRequest.Account.Id);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
||||
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
|
||||
{
|
||||
var request = new SendDraftRequest(sendDraftPreperationRequest);
|
||||
|
||||
QueueRequest(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||
QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void QueueRequest(IRequestBase request, Guid accountId)
|
||||
private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
|
||||
{
|
||||
var synchronizer = _winoSynchronizerFactory.GetAccountSynchronizer(accountId);
|
||||
|
||||
if (synchronizer == null)
|
||||
try
|
||||
{
|
||||
_logger.Warning("Synchronizer not found for account {AccountId}.", accountId);
|
||||
_logger.Warning("Skipping queueing request {Operation}.", request.Operation);
|
||||
|
||||
return;
|
||||
await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
|
||||
}
|
||||
catch (WinoServerException serverException)
|
||||
{
|
||||
_dialogService.InfoBarMessage("", serverException.Message, InfoBarMessageType.Error);
|
||||
}
|
||||
|
||||
synchronizer.QueueRequest(request);
|
||||
}
|
||||
|
||||
private void QueueSynchronization(Guid accountId)
|
||||
@@ -154,7 +147,7 @@ namespace Wino.Core.Services
|
||||
Type = SynchronizationType.ExecuteRequests
|
||||
};
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options));
|
||||
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Folders;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Requests;
|
||||
|
||||
namespace Wino.Core.Services
|
||||
@@ -92,6 +91,7 @@ namespace Wino.Core.Services
|
||||
|
||||
var requests = new List<IRequest>();
|
||||
|
||||
// TODO: Fix: Collection was modified; enumeration operation may not execute
|
||||
foreach (var item in preperationRequest.MailItems)
|
||||
{
|
||||
var singleRequest = await GetSingleRequestAsync(item, action, moveTargetStructure, preperationRequest.ToggleExecution);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -14,57 +15,12 @@ using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Integration;
|
||||
using Wino.Core.Messages.Mails;
|
||||
using Wino.Core.Messages.Synchronization;
|
||||
using Wino.Core.Misc;
|
||||
using Wino.Core.Requests;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Synchronizers
|
||||
{
|
||||
public interface IBaseSynchronizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Account that is assigned for this synchronizer.
|
||||
/// </summary>
|
||||
MailAccount Account { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizer state.
|
||||
/// </summary>
|
||||
AccountSynchronizerState State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queues a single request to be executed in the next synchronization.
|
||||
/// </summary>
|
||||
/// <param name="request">Request to queue.</param>
|
||||
void QueueRequest(IRequestBase request);
|
||||
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// </summary>
|
||||
/// <returns>Whether active synchronization is stopped or not.</returns>
|
||||
bool CancelActiveSynchronization();
|
||||
|
||||
/// <summary>
|
||||
/// Performs a full synchronization with the server with given options.
|
||||
/// This will also prepares batch requests for execution.
|
||||
/// Requests are executed in the order they are queued and happens before the synchronization.
|
||||
/// Result of the execution queue is processed during the synchronization.
|
||||
/// </summary>
|
||||
/// <param name="options">Options for synchronization.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Result summary of synchronization.</returns>
|
||||
Task<SynchronizationResult> SynchronizeAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a single MIME message from the server and saves it to disk.
|
||||
/// </summary>
|
||||
/// <param name="mailItem">Mail item to download from server.</param>
|
||||
/// <param name="transferProgress">Optional progress reporting for download operation.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DownloadMissingMimeMessageAsync(IMailItem mailItem, ITransferProgress transferProgress, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public abstract class BaseSynchronizer<TBaseRequest, TMessageType> : BaseMailIntegrator<TBaseRequest>, IBaseSynchronizer
|
||||
{
|
||||
private SemaphoreSlim synchronizationSemaphore = new(1);
|
||||
@@ -88,7 +44,7 @@ namespace Wino.Core.Synchronizers
|
||||
{
|
||||
state = value;
|
||||
|
||||
WeakReferenceMessenger.Default.Send(new AccountSynchronizerStateChanged(this, value));
|
||||
WeakReferenceMessenger.Default.Send(new AccountSynchronizerStateChanged(Account.Id, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,18 +122,21 @@ namespace Wino.Core.Synchronizers
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.Warning("Synchronization cancelled.");
|
||||
Logger.Warning("Synchronization canceled.");
|
||||
|
||||
return SynchronizationResult.Canceled;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Disable maybe?
|
||||
Logger.Error(ex, "Synchronization failed for {Name}", Account.Name);
|
||||
Debugger.Break();
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Reset account progress to hide the progress.
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 0);
|
||||
PublishSynchronizationProgress(0);
|
||||
|
||||
State = AccountSynchronizerState.Idle;
|
||||
synchronizationSemaphore.Release();
|
||||
@@ -191,6 +150,9 @@ namespace Wino.Core.Synchronizers
|
||||
private void PublishUnreadItemChanges()
|
||||
=> WeakReferenceMessenger.Default.Send(new RefreshUnreadCountsMessage(Account.Id));
|
||||
|
||||
public void PublishSynchronizationProgress(double progress)
|
||||
=> WeakReferenceMessenger.Default.Send(new AccountSynchronizationProgressUpdatedMessage(Account.Id, progress));
|
||||
|
||||
/// <summary>
|
||||
/// 1. Group all requests by operation type.
|
||||
/// 2. Group all individual operation type requests with equality check.
|
||||
@@ -302,20 +264,31 @@ namespace Wino.Core.Synchronizers
|
||||
/// <returns>New synchronization options with minimal HTTP effort.</returns>
|
||||
private SynchronizationOptions GetSynchronizationOptionsAfterRequestExecution(IEnumerable<IRequestBase> requests)
|
||||
{
|
||||
bool isAllCustomSynchronizationRequests = requests.All(a => a is ICustomFolderSynchronizationRequest);
|
||||
List<Guid> synchronizationFolderIds = new();
|
||||
|
||||
if (requests.All(a => a is IBatchChangeRequest))
|
||||
{
|
||||
var requestsInsideBatches = requests.Cast<IBatchChangeRequest>().SelectMany(b => b.Items);
|
||||
|
||||
// Gather FolderIds to synchronize.
|
||||
synchronizationFolderIds = requestsInsideBatches
|
||||
.Where(a => a is ICustomFolderSynchronizationRequest)
|
||||
.Cast<ICustomFolderSynchronizationRequest>()
|
||||
.SelectMany(a => a.SynchronizationFolderIds)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var options = new SynchronizationOptions()
|
||||
{
|
||||
AccountId = Account.Id,
|
||||
Type = SynchronizationType.FoldersOnly
|
||||
};
|
||||
|
||||
if (isAllCustomSynchronizationRequests)
|
||||
if (synchronizationFolderIds.Count > 0)
|
||||
{
|
||||
// Gather FolderIds to synchronize.
|
||||
|
||||
options.Type = SynchronizationType.Custom;
|
||||
options.SynchronizationFolderIds = requests.Cast<ICustomFolderSynchronizationRequest>().SelectMany(a => a.SynchronizationFolderIds).ToList();
|
||||
options.SynchronizationFolderIds = synchronizationFolderIds;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Wino.Core.Synchronizers
|
||||
}
|
||||
|
||||
// Start downloading missing messages.
|
||||
await BatchDownloadMessagesAsync(missingMessageIds, options.ProgressListener, cancellationToken).ConfigureAwait(false);
|
||||
await BatchDownloadMessagesAsync(missingMessageIds, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Map remote drafts to local drafts.
|
||||
await MapDraftIdsAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -353,7 +353,7 @@ namespace Wino.Core.Synchronizers
|
||||
/// </summary>
|
||||
/// <param name="messageIds">Gmail message ids to download.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
private async Task BatchDownloadMessagesAsync(IEnumerable<string> messageIds, ISynchronizationProgress progressListener = null, CancellationToken cancellationToken = default)
|
||||
private async Task BatchDownloadMessagesAsync(IEnumerable<string> messageIds, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var totalDownloadCount = messageIds.Count();
|
||||
|
||||
@@ -396,7 +396,7 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
var progressValue = downloadedItemCount * 100 / Math.Max(1, totalDownloadCount);
|
||||
|
||||
progressListener?.AccountProgressUpdated(Account.Id, progressValue);
|
||||
PublishSynchronizationProgress(progressValue);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -407,21 +407,19 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// options.Type = SynchronizationType.FoldersOnly;
|
||||
|
||||
var downloadedMessageIds = new List<string>();
|
||||
|
||||
_logger.Information("Internal synchronization started for {Name}", Account.Name);
|
||||
_logger.Information("Options: {Options}", options);
|
||||
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 1);
|
||||
PublishSynchronizationProgress(1);
|
||||
|
||||
// Only do folder sync for these types.
|
||||
// Opening folder and checking their UidValidity is slow.
|
||||
// Therefore this should be avoided as many times as possible.
|
||||
bool shouldDoFolderSync = options.Type == SynchronizationType.Full || options.Type == SynchronizationType.FoldersOnly;
|
||||
|
||||
// This may create some inconsistencies, but nothing we can do...
|
||||
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (shouldDoFolderSync)
|
||||
{
|
||||
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (options.Type != SynchronizationType.FoldersOnly)
|
||||
{
|
||||
@@ -432,14 +430,14 @@ namespace Wino.Core.Synchronizers
|
||||
var folder = synchronizationFolders[i];
|
||||
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
||||
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, progress);
|
||||
PublishSynchronizationProgress(progress);
|
||||
|
||||
var folderDownloadedMessageIds = await SynchronizeFolderInternalAsync(folder, cancellationToken).ConfigureAwait(false);
|
||||
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
||||
}
|
||||
}
|
||||
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 100);
|
||||
PublishSynchronizationProgress(100);
|
||||
|
||||
// Get all unread new downloaded items and return in the result.
|
||||
// This is primarily used in notifications.
|
||||
@@ -943,7 +941,12 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
foreach (var mailPackage in createdMailPackages)
|
||||
{
|
||||
await _imapChangeProcessor.CreateMailAsync(Account.Id, mailPackage).ConfigureAwait(false);
|
||||
bool isCreated = await _imapChangeProcessor.CreateMailAsync(Account.Id, mailPackage).ConfigureAwait(false);
|
||||
|
||||
if (isCreated)
|
||||
{
|
||||
downloadedMessageIds.Add(mailPackage.Copy.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ using Microsoft.Graph;
|
||||
using Microsoft.Graph.Models;
|
||||
using Microsoft.Kiota.Abstractions;
|
||||
using Microsoft.Kiota.Abstractions.Authentication;
|
||||
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
|
||||
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
|
||||
using MimeKit;
|
||||
using MoreLinq.Extensions;
|
||||
using Serilog;
|
||||
@@ -73,19 +75,59 @@ namespace Wino.Core.Synchronizers
|
||||
{
|
||||
var tokenProvider = new MicrosoftTokenProvider(Account, authenticator);
|
||||
|
||||
// Add immutable id preffered client.
|
||||
// Update request handlers for Graph client.
|
||||
var handlers = GraphClientFactory.CreateDefaultHandlers();
|
||||
handlers.Add(new MicrosoftImmutableIdHandler());
|
||||
|
||||
handlers.Add(GetMicrosoftImmutableIdHandler());
|
||||
|
||||
// Remove existing RetryHandler and add a new one with custom options.
|
||||
var existingRetryHandler = handlers.FirstOrDefault(a => a is RetryHandler);
|
||||
if (existingRetryHandler != null)
|
||||
handlers.Remove(existingRetryHandler);
|
||||
|
||||
// Add custom one.
|
||||
handlers.Add(GetRetryHandler());
|
||||
|
||||
var httpClient = GraphClientFactory.Create(handlers);
|
||||
|
||||
_graphClient = new GraphServiceClient(httpClient, new BaseBearerTokenAuthenticationProvider(tokenProvider));
|
||||
|
||||
_outlookChangeProcessor = outlookChangeProcessor;
|
||||
|
||||
// Specify to use TLS 1.2 as default connection
|
||||
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
}
|
||||
|
||||
#region MS Graph Handlers
|
||||
|
||||
private MicrosoftImmutableIdHandler GetMicrosoftImmutableIdHandler() => new();
|
||||
|
||||
private RetryHandler GetRetryHandler()
|
||||
{
|
||||
var options = new RetryHandlerOption()
|
||||
{
|
||||
ShouldRetry = (delay, attempt, httpResponse) =>
|
||||
{
|
||||
var statusCode = httpResponse.StatusCode;
|
||||
|
||||
return statusCode switch
|
||||
{
|
||||
HttpStatusCode.ServiceUnavailable => true,
|
||||
HttpStatusCode.GatewayTimeout => true,
|
||||
(HttpStatusCode)429 => true,
|
||||
HttpStatusCode.Unauthorized => true,
|
||||
_ => false
|
||||
};
|
||||
},
|
||||
Delay = 3,
|
||||
MaxRetry = 3
|
||||
};
|
||||
|
||||
return new RetryHandler(options);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var downloadedMessageIds = new List<string>();
|
||||
@@ -95,7 +137,7 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
try
|
||||
{
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 1);
|
||||
PublishSynchronizationProgress(1);
|
||||
|
||||
await SynchronizeFoldersAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -111,7 +153,7 @@ namespace Wino.Core.Synchronizers
|
||||
var folder = synchronizationFolders[i];
|
||||
var progress = (int)Math.Round((double)(i + 1) / synchronizationFolders.Count * 100);
|
||||
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, progress);
|
||||
PublishSynchronizationProgress(progress);
|
||||
|
||||
var folderDownloadedMessageIds = await SynchronizeFolderAsync(folder, cancellationToken).ConfigureAwait(false);
|
||||
downloadedMessageIds.AddRange(folderDownloadedMessageIds);
|
||||
@@ -120,13 +162,14 @@ namespace Wino.Core.Synchronizers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Synchronization failed for {Name}", Account.Name);
|
||||
_logger.Error(ex, "Synchronizing folders for {Name}", Account.Name);
|
||||
Debugger.Break();
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
options.ProgressListener?.AccountProgressUpdated(Account.Id, 100);
|
||||
PublishSynchronizationProgress(100);
|
||||
}
|
||||
|
||||
// Get all unred new downloaded items and return in the result.
|
||||
@@ -238,20 +281,12 @@ namespace Wino.Core.Synchronizers
|
||||
private bool IsResourceDeleted(IDictionary<string, object> additionalData)
|
||||
=> additionalData != null && additionalData.ContainsKey("@removed");
|
||||
|
||||
private bool IsResourceUpdated(IDictionary<string, object> additionalData)
|
||||
=> additionalData == null || !additionalData.Any();
|
||||
|
||||
private async Task<bool> HandleFolderRetrievedAsync(MailFolder folder, OutlookSpecialFolderIdInformation outlookSpecialFolderIdInformation, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (IsResourceDeleted(folder.AdditionalData))
|
||||
{
|
||||
await _outlookChangeProcessor.DeleteFolderAsync(Account.Id, folder.Id).ConfigureAwait(false);
|
||||
}
|
||||
else if (IsResourceUpdated(folder.AdditionalData))
|
||||
{
|
||||
// TODO
|
||||
Debugger.Break();
|
||||
}
|
||||
else
|
||||
{
|
||||
// New folder created.
|
||||
@@ -297,38 +332,45 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
await _outlookChangeProcessor.DeleteAssignmentAsync(Account.Id, item.Id, folder.RemoteFolderId).ConfigureAwait(false);
|
||||
}
|
||||
else if (IsResourceUpdated(item.AdditionalData))
|
||||
{
|
||||
// Some of the properties of the item are updated.
|
||||
|
||||
if (item.IsRead != null)
|
||||
{
|
||||
await _outlookChangeProcessor.ChangeMailReadStatusAsync(item.Id, item.IsRead.GetValueOrDefault()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.Flag?.FlagStatus != null)
|
||||
{
|
||||
await _outlookChangeProcessor.ChangeFlagStatusAsync(item.Id, item.Flag.FlagStatus.GetValueOrDefault() == FollowupFlagStatus.Flagged)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Package may return null on some cases mapping the remote draft to existing local draft.
|
||||
// If the item exists in the local database, it means that it's already downloaded. Process as an Update.
|
||||
|
||||
var newMailPackages = await CreateNewMailPackagesAsync(item, folder, cancellationToken);
|
||||
var isMailExists = await _outlookChangeProcessor.IsMailExistsInFolderAsync(item.Id, folder.Id);
|
||||
|
||||
if (newMailPackages != null)
|
||||
if (isMailExists)
|
||||
{
|
||||
foreach (var package in newMailPackages)
|
||||
{
|
||||
// Only add to downloaded message ids if it's inserted successfuly.
|
||||
// Updates should not be added to the list because they are not new.
|
||||
bool isInserted = await _outlookChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||
// Some of the properties of the item are updated.
|
||||
|
||||
if (isInserted)
|
||||
if (item.IsRead != null)
|
||||
{
|
||||
await _outlookChangeProcessor.ChangeMailReadStatusAsync(item.Id, item.IsRead.GetValueOrDefault()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (item.Flag?.FlagStatus != null)
|
||||
{
|
||||
await _outlookChangeProcessor.ChangeFlagStatusAsync(item.Id, item.Flag.FlagStatus.GetValueOrDefault() == FollowupFlagStatus.Flagged)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Package may return null on some cases mapping the remote draft to existing local draft.
|
||||
|
||||
var newMailPackages = await CreateNewMailPackagesAsync(item, folder, cancellationToken);
|
||||
|
||||
if (newMailPackages != null)
|
||||
{
|
||||
foreach (var package in newMailPackages)
|
||||
{
|
||||
downloadedMessageIds.Add(package.Copy.Id);
|
||||
// Only add to downloaded message ids if it's inserted successfuly.
|
||||
// Updates should not be added to the list because they are not new.
|
||||
bool isInserted = await _outlookChangeProcessor.CreateMailAsync(Account.Id, package).ConfigureAwait(false);
|
||||
|
||||
if (isInserted)
|
||||
{
|
||||
downloadedMessageIds.Add(package.Copy.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,11 +381,12 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
private async Task SynchronizeFoldersAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Gather special folders by default.
|
||||
// Others will be other type.
|
||||
// Gather special folders by default.
|
||||
// Others will be other type.
|
||||
|
||||
// Get well known folder ids by batch.
|
||||
// Get well known folder ids by batch.
|
||||
|
||||
retry:
|
||||
var wellKnownFolderIdBatch = new BatchRequestContentCollection(_graphClient);
|
||||
|
||||
var inboxRequest = _graphClient.Me.MailFolders[INBOX_NAME].ToGetRequestInformation((t) => { t.QueryParameters.Select = ["id"]; });
|
||||
@@ -394,9 +437,19 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
deltaRequest.UrlTemplate = deltaRequest.UrlTemplate.Insert(deltaRequest.UrlTemplate.Length - 1, ",%24deltaToken");
|
||||
deltaRequest.QueryParameters.Add("%24deltaToken", currentDeltaLink);
|
||||
graphFolders = await _graphClient.RequestAdapter.SendAsync(deltaRequest,
|
||||
|
||||
try
|
||||
{
|
||||
graphFolders = await _graphClient.RequestAdapter.SendAsync(deltaRequest,
|
||||
Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse.CreateFromDiscriminatorValue,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (ApiException apiException) when (apiException.ResponseStatusCode == 410)
|
||||
{
|
||||
Account.SynchronizationDeltaIdentifier = await _outlookChangeProcessor.ResetAccountDeltaTokenAsync(Account.Id);
|
||||
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
|
||||
var iterator = PageIterator<MailFolder, Microsoft.Graph.Me.MailFolders.Delta.DeltaGetResponse>.CreatePageIterator(_graphClient, graphFolders, (folder) =>
|
||||
@@ -686,6 +739,8 @@ namespace Wino.Core.Synchronizers
|
||||
HttpResponseMessage httpResponseMessage,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (httpResponseMessage == null) return;
|
||||
|
||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
throw new SynchronizerException(string.Format(Translator.Exception_SynchronizerFailureHTTP, httpResponseMessage.StatusCode));
|
||||
|
||||
@@ -5,8 +5,13 @@
|
||||
<RootNamespace>Wino.Core</RootNamespace>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="WinoSynchronizerFactory.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.2.2" />
|
||||
@@ -18,11 +23,13 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MailKit" Version="4.6.0" />
|
||||
<PackageReference Include="MailKit" Version="4.7.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.55.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.47.2" />
|
||||
<PackageReference Include="MimeKit" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.56.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.62.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.62.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.62.0" />
|
||||
<PackageReference Include="MimeKit" Version="4.7.1" />
|
||||
<PackageReference Include="morelinq" Version="4.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
@@ -33,10 +40,10 @@
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.8" />
|
||||
<PackageReference Include="SqlKata" Version="2.4.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
|
||||
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -10,13 +10,6 @@ using Wino.Core.Synchronizers;
|
||||
|
||||
namespace Wino.Core
|
||||
{
|
||||
public interface IWinoSynchronizerFactory : IInitializeAsync
|
||||
{
|
||||
IBaseSynchronizer GetAccountSynchronizer(Guid accountId);
|
||||
IBaseSynchronizer CreateNewSynchronizer(MailAccount account);
|
||||
void DeleteSynchronizer(MailAccount account);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory that keeps track of all integrator with associated mail accounts.
|
||||
/// Synchronizer per-account makes sense because re-generating synchronizers are not ideal.
|
||||
@@ -82,7 +75,6 @@ namespace Wino.Core
|
||||
return new OutlookSynchronizer(mailAccount, outlookAuthenticator, _outlookChangeProcessor);
|
||||
case Domain.Enums.MailProviderType.Gmail:
|
||||
var gmailAuthenticator = new GmailAuthenticator(_tokenService, _nativeAppService);
|
||||
|
||||
return new GmailSynchronizer(mailAccount, gmailAuthenticator, _gmailChangeProcessor);
|
||||
case Domain.Enums.MailProviderType.Office365:
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user