diff --git a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs b/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs index 4285f128..eff74b95 100644 --- a/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs +++ b/Wino.Core.Domain/Interfaces/IWinoServerConnectionManager.cs @@ -19,19 +19,13 @@ namespace Wino.Core.Domain.Interfaces /// /// Launches Full Trust process (Wino Server) and awaits connection completion. - /// If connection is not established in 5 seconds, it will return false. + /// If connection is not established in 10 seconds, it will return false. /// If the server process is already running, it'll connect to existing one. /// If the server process is not running, it'll be launched and connection establishment is awaited. /// /// Whether connection is established or not. Task ConnectAsync(); - /// - /// Disconnects from existing connection and disposes the connection. - /// - /// Whether disconnection is succesfull or not. - Task DisconnectAsync(); - /// /// Queues a new user request to be processed by Wino Server. /// Healthy connection must present before calling this method. @@ -48,6 +42,13 @@ namespace Wino.Core.Domain.Interfaces /// Request type. /// Response received from the server for the given TResponse type. Task> GetResponseAsync(TRequestType clientMessage) where TRequestType : IClientMessage; + + /// + /// Handle for connecting to the server. + /// If the server is already running, it'll connect to existing one. + /// Callers can await this handle to wait for connection establishment. + /// + TaskCompletionSource ConnectingHandle { get; } } public interface IWinoServerConnectionManager : IWinoServerConnectionManager, IInitializeAsync diff --git a/Wino.Core.Domain/Models/Server/WinoServerResponse.cs b/Wino.Core.Domain/Models/Server/WinoServerResponse.cs index 45b8bb06..d903aa6e 100644 --- a/Wino.Core.Domain/Models/Server/WinoServerResponse.cs +++ b/Wino.Core.Domain/Models/Server/WinoServerResponse.cs @@ -13,8 +13,6 @@ namespace Wino.Core.Domain.Models.Server public string Message { get; set; } public T Data { get; set; } - // protected WinoServerResponse() { } - public static WinoServerResponse CreateSuccessResponse(T data) { return new WinoServerResponse diff --git a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs index 9aaca3ed..bb5fd965 100644 --- a/Wino.Core.UWP/Services/WinoServerConnectionManager.cs +++ b/Wino.Core.UWP/Services/WinoServerConnectionManager.cs @@ -26,10 +26,11 @@ namespace Wino.Core.UWP.Services IWinoServerConnectionManager, IRecipient { - private const int ServerConnectionTimeoutMs = 5000; + private const int ServerConnectionTimeoutMs = 10000; public event EventHandler StatusChanged; - private TaskCompletionSource _connectionTaskCompletionSource; + + public TaskCompletionSource ConnectingHandle { get; private set; } private ILogger Logger => Logger.ForContext(); @@ -40,6 +41,7 @@ namespace Wino.Core.UWP.Services get { return status; } private set { + Log.Information("Server connection status changed to {Status}.", value); status = value; StatusChanged?.Invoke(this, value); } @@ -85,13 +87,29 @@ namespace Wino.Core.UWP.Services public async Task ConnectAsync() { - if (Status == WinoServerConnectionStatus.Connected) return true; + if (Status == WinoServerConnectionStatus.Connected) + { + Log.Information("Server is already connected."); + return true; + } + + if (Status == WinoServerConnectionStatus.Connecting) + { + // A connection is already being established at the moment. + // No need to run another connection establishment process. + // Await the connecting handler if possible. + + if (ConnectingHandle != null) + { + return await ConnectingHandle.Task; + } + } if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0)) { try { - _connectionTaskCompletionSource ??= new TaskCompletionSource(); + ConnectingHandle ??= new TaskCompletionSource(); var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs)); @@ -103,34 +121,40 @@ namespace Wino.Core.UWP.Services // Once the connection is established, the handler will set the Connection property // and WinoServerConnectionEstablished will be fired by the messenger. - await _connectionTaskCompletionSource.Task.WaitAsync(connectionCancellationToken.Token); + await ConnectingHandle.Task.WaitAsync(connectionCancellationToken.Token); + + Log.Information("Server connection established successfully."); } - catch (Exception) + catch (Exception ex) { + Log.Error(ex, "Failed to connect to the server."); + Status = WinoServerConnectionStatus.Failed; return false; } return true; } + else + { + Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible."); + } return false; } - public async Task DisconnectAsync() - { - if (Connection == null || Status == WinoServerConnectionStatus.Disconnected) return true; - - // TODO: Send disconnect message to the fulltrust process. - - return true; - } - public async Task InitializeAsync() { var isConnectionSuccessfull = await ConnectAsync(); - // TODO: Log connection status + if (isConnectionSuccessfull) + { + Log.Information("ServerConnectionManager initialized successfully."); + } + else + { + Log.Error("ServerConnectionManager initialization failed."); + } } private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) @@ -222,7 +246,7 @@ namespace Wino.Core.UWP.Services private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args) { - // TODO: Handle server disconnection. + Log.Information("Server disconnected."); } public async Task QueueRequestAsync(IRequestBase request, Guid accountId) @@ -242,8 +266,8 @@ namespace Wino.Core.UWP.Services private async Task> GetResponseInternalAsync(TRequestType message, Dictionary parameters = null) { - if (Connection == null) - return WinoServerResponse.CreateErrorResponse("Server connection is not established."); + if (Status != WinoServerConnectionStatus.Connected) + await ConnectAsync(); string serializedMessage = string.Empty; @@ -306,11 +330,6 @@ namespace Wino.Core.UWP.Services } public void Receive(WinoServerConnectionEstablished message) - { - if (_connectionTaskCompletionSource != null) - { - _connectionTaskCompletionSource.TrySetResult(true); - } - } + => ConnectingHandle?.TrySetResult(true); } } diff --git a/Wino.Core/Services/WinoRequestDelegator.cs b/Wino.Core/Services/WinoRequestDelegator.cs index b47196ba..c4e9b60e 100644 --- a/Wino.Core/Services/WinoRequestDelegator.cs +++ b/Wino.Core/Services/WinoRequestDelegator.cs @@ -80,7 +80,7 @@ namespace Wino.Core.Services await QueueRequestAsync(accountRequest, accountId.Key); } - QueueSynchronization(accountId.Key); + await QueueSynchronizationAsync(accountId.Key); } } @@ -108,7 +108,7 @@ namespace Wino.Core.Services if (request == null) return; await QueueRequestAsync(request, accountId); - QueueSynchronization(accountId); + await QueueSynchronizationAsync(accountId); } public async Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest) @@ -116,7 +116,7 @@ namespace Wino.Core.Services var request = new CreateDraftRequest(draftPreperationRequest); await QueueRequestAsync(request, draftPreperationRequest.Account.Id); - QueueSynchronization(draftPreperationRequest.Account.Id); + await QueueSynchronizationAsync(draftPreperationRequest.Account.Id); } public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest) @@ -124,23 +124,26 @@ namespace Wino.Core.Services var request = new SendDraftRequest(sendDraftPreperationRequest); await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id); - QueueSynchronization(sendDraftPreperationRequest.MailItem.AssignedAccount.Id); + await QueueSynchronizationAsync(sendDraftPreperationRequest.MailItem.AssignedAccount.Id); } private async Task QueueRequestAsync(IRequestBase request, Guid accountId) { try { + await EnsureServerConnectedAsync(); await _winoServerConnectionManager.QueueRequestAsync(request, accountId); } catch (WinoServerException serverException) { - _dialogService.InfoBarMessage("", serverException.Message, InfoBarMessageType.Error); + _dialogService.InfoBarMessage("Wino Server Exception", serverException.Message, InfoBarMessageType.Error); } } - private void QueueSynchronization(Guid accountId) + private async Task QueueSynchronizationAsync(Guid accountId) { + await EnsureServerConnectedAsync(); + var options = new SynchronizationOptions() { AccountId = accountId, @@ -149,5 +152,12 @@ namespace Wino.Core.Services WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client)); } + + private async Task EnsureServerConnectedAsync() + { + if (_winoServerConnectionManager.Status == WinoServerConnectionStatus.Connected) return; + + await _winoServerConnectionManager.ConnectAsync(); + } } } diff --git a/Wino.Mail.ViewModels/MailListPageViewModel.cs b/Wino.Mail.ViewModels/MailListPageViewModel.cs index e3f24b1a..2470bec4 100644 --- a/Wino.Mail.ViewModels/MailListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MailListPageViewModel.cs @@ -845,9 +845,6 @@ namespace Wino.Mail.ViewModels trackingSynchronizationId = null; completedTrackingSynchronizationCount = 0; - // Check whether the account synchronizer that this folder belongs to is already in synchronization. - await CheckIfAccountIsSynchronizingAsync(); - // Notify change for archive-unarchive app bar button. OnPropertyChanged(nameof(IsArchiveSpecialFolder)); @@ -865,6 +862,9 @@ namespace Wino.Mail.ViewModels await Task.Delay(100); } + // Check whether the account synchronizer that this folder belongs to is already in synchronization. + await CheckIfAccountIsSynchronizingAsync(); + // Let awaiters know about the completion of mail init. message.FolderInitLoadAwaitTask?.TrySetResult(true); diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs index c4e2d1ad..42d658f9 100644 --- a/Wino.Mail/App.xaml.cs +++ b/Wino.Mail/App.xaml.cs @@ -67,7 +67,6 @@ namespace Wino private List initializeServices => new List() { _databaseService, - _appServiceConnectionManager, _translationService, _themeService, }; @@ -77,8 +76,6 @@ namespace Wino InitializeComponent(); UnhandledException += OnAppUnhandledException; - EnteredBackground += OnEnteredBackground; - LeavingBackground += OnLeavingBackground; Resuming += OnResuming; Suspending += OnSuspending; @@ -126,8 +123,6 @@ namespace Wino } private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); - private void OnLeavingBackground(object sender, LeavingBackgroundEventArgs e) => LogActivation($"Wino went foreground."); - private void OnEnteredBackground(object sender, EnteredBackgroundEventArgs e) => LogActivation($"Wino went background."); private IServiceProvider ConfigureServices() { var services = new ServiceCollection(); @@ -417,13 +412,11 @@ namespace Wino yield return Services.GetService(); } - public async void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) + public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { sender.Canceled -= OnConnectionBackgroundTaskCanceled; - Log.Information($"Server connection background task '{sender.Task.Name}' was canceled. Reason: {reason}"); - - await _appServiceConnectionManager.DisconnectAsync(); + Log.Information($"Server connection background task was canceled. Reason: {reason}"); connectionBackgroundTaskDeferral?.Complete(); connectionBackgroundTaskDeferral = null; diff --git a/Wino.Server/ServerContext.cs b/Wino.Server/ServerContext.cs index 9a60c63d..cf2ce174 100644 --- a/Wino.Server/ServerContext.cs +++ b/Wino.Server/ServerContext.cs @@ -173,7 +173,7 @@ namespace Wino.Server if (status != AppServiceConnectionStatus.Success) { - // TODO: Handle connection error + Log.Error("Opening server connection failed. Status: {status}", status); DisposeConnection(); } @@ -224,8 +224,6 @@ namespace Wino.Server private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) { - // TODO: Handle connection closed. - // UWP app might've been terminated or suspended. // At this point, we must keep active synchronizations going, but connection is lost. // As long as this process is alive, database will be kept updated, but no messages will be sent.