diff --git a/Wino.Core.Domain/Enums/AccountCreationDialogState.cs b/Wino.Core.Domain/Enums/AccountCreationDialogState.cs index 947449d4..e7eb47d8 100644 --- a/Wino.Core.Domain/Enums/AccountCreationDialogState.cs +++ b/Wino.Core.Domain/Enums/AccountCreationDialogState.cs @@ -10,6 +10,7 @@ TestingConnection, AutoDiscoverySetup, AutoDiscoveryInProgress, - FetchingProfileInformation + FetchingProfileInformation, + Canceled } } diff --git a/Wino.Core.Domain/Interfaces/IAccountCreationDialog.cs b/Wino.Core.Domain/Interfaces/IAccountCreationDialog.cs index c74a8413..e534a042 100644 --- a/Wino.Core.Domain/Interfaces/IAccountCreationDialog.cs +++ b/Wino.Core.Domain/Interfaces/IAccountCreationDialog.cs @@ -1,11 +1,12 @@ -using Wino.Core.Domain.Enums; +using System.Threading; +using Wino.Core.Domain.Enums; namespace Wino.Core.Domain.Interfaces { public interface IAccountCreationDialog { - void ShowDialog(); - void Complete(); + void ShowDialog(CancellationTokenSource cancellationTokenSource); + void Complete(bool cancel); AccountCreationDialogState State { get; set; } } } diff --git a/Wino.Core.Domain/Interfaces/INativeAppService.cs b/Wino.Core.Domain/Interfaces/INativeAppService.cs index 7b1aeeaf..ff933592 100644 --- a/Wino.Core.Domain/Interfaces/INativeAppService.cs +++ b/Wino.Core.Domain/Interfaces/INativeAppService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Wino.Core.Domain.Models.Authorization; @@ -10,14 +11,14 @@ namespace Wino.Core.Domain.Interfaces Task GetMimeMessageStoragePath(); Task GetEditorBundlePathAsync(); Task LaunchFileAsync(string filePath); - Task LaunchUriAsync(Uri uri); + Task LaunchUriAsync(Uri uri); /// /// Launches the default browser with the specified uri and waits for protocol activation to finish. /// /// /// Response callback from the browser. - Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri); + Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri, CancellationToken cancellationToken = default); /// /// Finalizes GetAuthorizationResponseUriAsync for current IAuthenticator. diff --git a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs b/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs index eff74b95..4ad58446 100644 --- a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs +++ b/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Models.Server; @@ -41,7 +42,7 @@ namespace Wino.Core.Domain.Interfaces /// Request type. /// Request type. /// Response received from the server for the given TResponse type. - Task> GetResponseAsync(TRequestType clientMessage) where TRequestType : IClientMessage; + Task> GetResponseAsync(TRequestType clientMessage, CancellationToken cancellationToken = default) where TRequestType : IClientMessage; /// /// Handle for connecting to the server. diff --git a/Wino.Core.UWP/Services/NativeAppService.cs b/Wino.Core.UWP/Services/NativeAppService.cs index a39d4bdc..2d8089ca 100644 --- a/Wino.Core.UWP/Services/NativeAppService.cs +++ b/Wino.Core.UWP/Services/NativeAppService.cs @@ -1,5 +1,7 @@ using System; +using System.Threading; using System.Threading.Tasks; +using Nito.AsyncEx; using Windows.ApplicationModel; using Windows.Foundation.Metadata; using Windows.Security.Authentication.Web; @@ -9,10 +11,10 @@ using Windows.Storage; using Windows.Storage.Streams; using Windows.System; using Windows.UI.Shell; +using Wino.Core.Domain; +using Wino.Core.Domain.Exceptions; using Wino.Core.Domain.Interfaces; using Wino.Core.Domain.Models.Authorization; -using Wino.Core.Domain.Exceptions; -using Wino.Core.Domain; @@ -126,7 +128,7 @@ namespace Wino.Services await Launcher.LaunchFileAsync(file); } - public Task LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask(); + public Task LaunchUriAsync(Uri uri) => Launcher.LaunchUriAsync(uri).AsTask(); public string GetFullAppVersion() { @@ -154,7 +156,7 @@ namespace Wino.Services await taskbarManager.RequestPinCurrentAppAsync(); } - public async Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri) + public async Task GetAuthorizationResponseUriAsync(IAuthenticator authenticator, string authorizationUri, CancellationToken cancellationToken = default) { if (authorizationCompletedTaskSource != null) { @@ -164,9 +166,12 @@ namespace Wino.Services authorizationCompletedTaskSource = new TaskCompletionSource(); - await LaunchUriAsync(new Uri(authorizationUri)); + bool isLaunched = await Launcher.LaunchUriAsync(new Uri(authorizationUri)).AsTask(cancellationToken); - return await authorizationCompletedTaskSource.Task; + if (!isLaunched) + throw new WinoServerException("Failed to launch Google Authentication dialog."); + + return await authorizationCompletedTaskSource.Task.WaitAsync(cancellationToken); } public void ContinueAuthorization(Uri authorizationResponseUri) diff --git a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs index 8b42d642..6a36e33a 100644 --- a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs +++ b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs @@ -275,10 +275,12 @@ namespace Wino.Core.UWP.Services queueResponse.ThrowIfFailed(); } - public Task> GetResponseAsync(TRequestType message) where TRequestType : IClientMessage - => GetResponseInternalAsync(message); + public Task> GetResponseAsync(TRequestType message, CancellationToken cancellationToken = default) where TRequestType : IClientMessage + => GetResponseInternalAsync(message, cancellationToken: cancellationToken); - private async Task> GetResponseInternalAsync(TRequestType message, Dictionary parameters = null) + private async Task> GetResponseInternalAsync(TRequestType message, + Dictionary parameters = null, + CancellationToken cancellationToken = default) { if (Status != WinoServerConnectionStatus.Connected) await ConnectAsync(); @@ -315,7 +317,11 @@ namespace Wino.Core.UWP.Services } } - response = await Connection.SendMessageAsync(valueSet); + response = await Connection.SendMessageAsync(valueSet).AsTask(cancellationToken); + } + catch (OperationCanceledException) + { + return WinoServerResponse.CreateErrorResponse($"Request is canceled by client."); } catch (Exception serverSendException) { diff --git a/Wino.Core/Authenticators/GmailAuthenticator.cs b/Wino.Core/Authenticators/GmailAuthenticator.cs index efb646f8..faee8d43 100644 --- a/Wino.Core/Authenticators/GmailAuthenticator.cs +++ b/Wino.Core/Authenticators/GmailAuthenticator.cs @@ -123,7 +123,6 @@ namespace Wino.Core.Authenticators try { - //await _authorizationCompletionSource.Task.WaitAsync(_authorizationCancellationTokenSource.Token); responseRedirectUri = await _nativeAppService.GetAuthorizationResponseUriAsync(this, authorizationUri); } catch (Exception) diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs index c305bd18..aee52bdb 100644 --- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs +++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs @@ -319,8 +319,33 @@ namespace Wino.Core.Synchronizers return true; } + /// + /// Somehow, Graph API returns Message type item for items like TodoTask, EventMessage and Contact. + /// Basically deleted item retention items are stored as Message object in Deleted Items folder. + /// Suprisingly, odatatype will also be the same as Message. + /// In order to differentiate them from regular messages, we need to check the addresses in the message. + /// + /// Retrieved message. + /// Whether the item is non-Message type or not. + private bool IsNotRealMessageType(Message item) + => item is EventMessage || item.From?.EmailAddress == null; + private async Task HandleItemRetrievedAsync(Message item, MailItemFolder folder, IList downloadedMessageIds, CancellationToken cancellationToken = default) { + if (IsNotRealMessageType(item)) + { + if (item is EventMessage eventMessage) + { + Log.Warning("Recieved event message. This is not supported yet. {Id}", eventMessage.Id); + } + else + { + Log.Warning("Recieved either contact or todo item as message This is not supported yet. {Id}", item.Id); + } + + return true; + } + if (IsResourceDeleted(item.AdditionalData)) { // Deleting item with this override instead of the other one that deletes all mail copies. diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs index 95a7445c..78ee7f71 100644 --- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs +++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -150,6 +151,8 @@ namespace Wino.Mail.ViewModels // Select provider. var accountCreationDialogResult = await _dialogService.ShowNewAccountMailProviderDialogAsync(providers); + var accountCreationCancellationTokenSource = new CancellationTokenSource(); + if (accountCreationDialogResult != null) { creationDialog = _dialogService.GetAccountCreationDialog(accountCreationDialogResult.ProviderType); @@ -164,7 +167,7 @@ namespace Wino.Mail.ViewModels Id = Guid.NewGuid() }; - creationDialog.ShowDialog(); + creationDialog.ShowDialog(accountCreationCancellationTokenSource); creationDialog.State = AccountCreationDialogState.SigningIn; TokenInformation tokenInformation = null; @@ -191,12 +194,15 @@ namespace Wino.Mail.ViewModels { // For OAuth authentications, we just generate token and assign it to the MailAccount. - var tokenInformationResponse = await _winoServerConnectionManager.GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType, createdAccount)); + var tokenInformationResponse = await _winoServerConnectionManager + .GetResponseAsync(new AuthorizationRequested(accountCreationDialogResult.ProviderType, + createdAccount), accountCreationCancellationTokenSource.Token); + + if (creationDialog.State == AccountCreationDialogState.Canceled) + throw new AccountSetupCanceledException(); tokenInformationResponse.ThrowIfFailed(); - // ?? throw new AuthenticationException(Translator.Exception_TokenInfoRetrivalFailed); - tokenInformation = tokenInformationResponse.Data; createdAccount.Address = tokenInformation.Address; tokenInformation.AccountId = createdAccount.Id; @@ -294,6 +300,10 @@ namespace Wino.Mail.ViewModels { // Ignore } + catch (Exception ex) when (ex.Message.Contains(nameof(AccountSetupCanceledException))) + { + // Ignore + } catch (Exception ex) { Log.Error(ex, WinoErrors.AccountCreation); @@ -309,7 +319,7 @@ namespace Wino.Mail.ViewModels } finally { - creationDialog?.Complete(); + creationDialog?.Complete(false); } } diff --git a/Wino.Mail/Dialogs/AccountCreationDialog.xaml b/Wino.Mail/Dialogs/AccountCreationDialog.xaml index a8bb69d2..cb0220e7 100644 --- a/Wino.Mail/Dialogs/AccountCreationDialog.xaml +++ b/Wino.Mail/Dialogs/AccountCreationDialog.xaml @@ -1,12 +1,12 @@  @@ -15,10 +15,11 @@ + - + @@ -30,32 +31,44 @@ Text="{x:Bind domain:Translator.AccountCreationDialog_Initializing}" TextWrapping="Wrap" /> +