Removing server init from the app init. Making sure server connection is established before doing a request. Handling Connecting state.

This commit is contained in:
Burak Kaan Köse
2024-08-11 15:25:40 +02:00
parent 6d08368462
commit 983bc21448
7 changed files with 74 additions and 55 deletions

View File

@@ -19,19 +19,13 @@ namespace Wino.Core.Domain.Interfaces
/// <summary> /// <summary>
/// Launches Full Trust process (Wino Server) and awaits connection completion. /// 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 already running, it'll connect to existing one.
/// If the server process is not running, it'll be launched and connection establishment is awaited. /// If the server process is not running, it'll be launched and connection establishment is awaited.
/// </summary> /// </summary>
/// <returns>Whether connection is established or not.</returns> /// <returns>Whether connection is established or not.</returns>
Task<bool> ConnectAsync(); Task<bool> ConnectAsync();
/// <summary>
/// Disconnects from existing connection and disposes the connection.
/// </summary>
/// <returns>Whether disconnection is succesfull or not.</returns>
Task<bool> DisconnectAsync();
/// <summary> /// <summary>
/// Queues a new user request to be processed by Wino Server. /// Queues a new user request to be processed by Wino Server.
/// Healthy connection must present before calling this method. /// Healthy connection must present before calling this method.
@@ -48,6 +42,13 @@ namespace Wino.Core.Domain.Interfaces
/// <param name="clientMessage">Request type.</param> /// <param name="clientMessage">Request type.</param>
/// <returns>Response received from the server for the given TResponse type.</returns> /// <returns>Response received from the server for the given TResponse type.</returns>
Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage; Task<WinoServerResponse<TResponse>> GetResponseAsync<TResponse, TRequestType>(TRequestType clientMessage) where TRequestType : IClientMessage;
/// <summary>
/// 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.
/// </summary>
TaskCompletionSource<bool> ConnectingHandle { get; }
} }
public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync public interface IWinoServerConnectionManager<TAppServiceConnection> : IWinoServerConnectionManager, IInitializeAsync

View File

@@ -13,8 +13,6 @@ namespace Wino.Core.Domain.Models.Server
public string Message { get; set; } public string Message { get; set; }
public T Data { get; set; } public T Data { get; set; }
// protected WinoServerResponse() { }
public static WinoServerResponse<T> CreateSuccessResponse(T data) public static WinoServerResponse<T> CreateSuccessResponse(T data)
{ {
return new WinoServerResponse<T> return new WinoServerResponse<T>

View File

@@ -26,10 +26,11 @@ namespace Wino.Core.UWP.Services
IWinoServerConnectionManager<AppServiceConnection>, IWinoServerConnectionManager<AppServiceConnection>,
IRecipient<WinoServerConnectionEstablished> IRecipient<WinoServerConnectionEstablished>
{ {
private const int ServerConnectionTimeoutMs = 5000; private const int ServerConnectionTimeoutMs = 10000;
public event EventHandler<WinoServerConnectionStatus> StatusChanged; public event EventHandler<WinoServerConnectionStatus> StatusChanged;
private TaskCompletionSource<bool> _connectionTaskCompletionSource;
public TaskCompletionSource<bool> ConnectingHandle { get; private set; }
private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>(); private ILogger Logger => Logger.ForContext<WinoServerConnectionManager>();
@@ -40,6 +41,7 @@ namespace Wino.Core.UWP.Services
get { return status; } get { return status; }
private set private set
{ {
Log.Information("Server connection status changed to {Status}.", value);
status = value; status = value;
StatusChanged?.Invoke(this, value); StatusChanged?.Invoke(this, value);
} }
@@ -85,13 +87,29 @@ namespace Wino.Core.UWP.Services
public async Task<bool> ConnectAsync() public async Task<bool> 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)) if (ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
{ {
try try
{ {
_connectionTaskCompletionSource ??= new TaskCompletionSource<bool>(); ConnectingHandle ??= new TaskCompletionSource<bool>();
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs)); 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 // Once the connection is established, the handler will set the Connection property
// and WinoServerConnectionEstablished will be fired by the messenger. // 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; Status = WinoServerConnectionStatus.Failed;
return false; return false;
} }
return true; return true;
} }
else
{
Log.Information("FullTrustAppContract is not present in the system. Server connection is not possible.");
}
return false; return false;
} }
public async Task<bool> DisconnectAsync()
{
if (Connection == null || Status == WinoServerConnectionStatus.Disconnected) return true;
// TODO: Send disconnect message to the fulltrust process.
return true;
}
public async Task InitializeAsync() public async Task InitializeAsync()
{ {
var isConnectionSuccessfull = await ConnectAsync(); 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) private void ServerMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
@@ -222,7 +246,7 @@ namespace Wino.Core.UWP.Services
private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args) private void ServerDisconnected(AppServiceConnection sender, AppServiceClosedEventArgs args)
{ {
// TODO: Handle server disconnection. Log.Information("Server disconnected.");
} }
public async Task QueueRequestAsync(IRequestBase request, Guid accountId) public async Task QueueRequestAsync(IRequestBase request, Guid accountId)
@@ -242,8 +266,8 @@ namespace Wino.Core.UWP.Services
private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null) private async Task<WinoServerResponse<TResponse>> GetResponseInternalAsync<TResponse, TRequestType>(TRequestType message, Dictionary<string, object> parameters = null)
{ {
if (Connection == null) if (Status != WinoServerConnectionStatus.Connected)
return WinoServerResponse<TResponse>.CreateErrorResponse("Server connection is not established."); await ConnectAsync();
string serializedMessage = string.Empty; string serializedMessage = string.Empty;
@@ -306,11 +330,6 @@ namespace Wino.Core.UWP.Services
} }
public void Receive(WinoServerConnectionEstablished message) public void Receive(WinoServerConnectionEstablished message)
{ => ConnectingHandle?.TrySetResult(true);
if (_connectionTaskCompletionSource != null)
{
_connectionTaskCompletionSource.TrySetResult(true);
}
}
} }
} }

View File

@@ -80,7 +80,7 @@ namespace Wino.Core.Services
await QueueRequestAsync(accountRequest, accountId.Key); await QueueRequestAsync(accountRequest, accountId.Key);
} }
QueueSynchronization(accountId.Key); await QueueSynchronizationAsync(accountId.Key);
} }
} }
@@ -108,7 +108,7 @@ namespace Wino.Core.Services
if (request == null) return; if (request == null) return;
await QueueRequestAsync(request, accountId); await QueueRequestAsync(request, accountId);
QueueSynchronization(accountId); await QueueSynchronizationAsync(accountId);
} }
public async Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest) public async Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest)
@@ -116,7 +116,7 @@ namespace Wino.Core.Services
var request = new CreateDraftRequest(draftPreperationRequest); var request = new CreateDraftRequest(draftPreperationRequest);
await QueueRequestAsync(request, draftPreperationRequest.Account.Id); await QueueRequestAsync(request, draftPreperationRequest.Account.Id);
QueueSynchronization(draftPreperationRequest.Account.Id); await QueueSynchronizationAsync(draftPreperationRequest.Account.Id);
} }
public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest) public async Task ExecuteAsync(SendDraftPreparationRequest sendDraftPreperationRequest)
@@ -124,23 +124,26 @@ namespace Wino.Core.Services
var request = new SendDraftRequest(sendDraftPreperationRequest); var request = new SendDraftRequest(sendDraftPreperationRequest);
await QueueRequestAsync(request, sendDraftPreperationRequest.MailItem.AssignedAccount.Id); 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) private async Task QueueRequestAsync(IRequestBase request, Guid accountId)
{ {
try try
{ {
await EnsureServerConnectedAsync();
await _winoServerConnectionManager.QueueRequestAsync(request, accountId); await _winoServerConnectionManager.QueueRequestAsync(request, accountId);
} }
catch (WinoServerException serverException) 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() var options = new SynchronizationOptions()
{ {
AccountId = accountId, AccountId = accountId,
@@ -149,5 +152,12 @@ namespace Wino.Core.Services
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client)); WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Client));
} }
private async Task EnsureServerConnectedAsync()
{
if (_winoServerConnectionManager.Status == WinoServerConnectionStatus.Connected) return;
await _winoServerConnectionManager.ConnectAsync();
}
} }
} }

View File

@@ -845,9 +845,6 @@ namespace Wino.Mail.ViewModels
trackingSynchronizationId = null; trackingSynchronizationId = null;
completedTrackingSynchronizationCount = 0; 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. // Notify change for archive-unarchive app bar button.
OnPropertyChanged(nameof(IsArchiveSpecialFolder)); OnPropertyChanged(nameof(IsArchiveSpecialFolder));
@@ -865,6 +862,9 @@ namespace Wino.Mail.ViewModels
await Task.Delay(100); 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. // Let awaiters know about the completion of mail init.
message.FolderInitLoadAwaitTask?.TrySetResult(true); message.FolderInitLoadAwaitTask?.TrySetResult(true);

View File

@@ -67,7 +67,6 @@ namespace Wino
private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>() private List<IInitializeAsync> initializeServices => new List<IInitializeAsync>()
{ {
_databaseService, _databaseService,
_appServiceConnectionManager,
_translationService, _translationService,
_themeService, _themeService,
}; };
@@ -77,8 +76,6 @@ namespace Wino
InitializeComponent(); InitializeComponent();
UnhandledException += OnAppUnhandledException; UnhandledException += OnAppUnhandledException;
EnteredBackground += OnEnteredBackground;
LeavingBackground += OnLeavingBackground;
Resuming += OnResuming; Resuming += OnResuming;
Suspending += OnSuspending; Suspending += OnSuspending;
@@ -126,8 +123,6 @@ namespace Wino
} }
private void LogActivation(string log) => Log.Information($"{WinoLaunchLogPrefix}{log}"); 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() private IServiceProvider ConfigureServices()
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
@@ -417,13 +412,11 @@ namespace Wino
yield return Services.GetService<FileActivationHandler>(); yield return Services.GetService<FileActivationHandler>();
} }
public async void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) public void OnConnectionBackgroundTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{ {
sender.Canceled -= OnConnectionBackgroundTaskCanceled; sender.Canceled -= OnConnectionBackgroundTaskCanceled;
Log.Information($"Server connection background task '{sender.Task.Name}' was canceled. Reason: {reason}"); Log.Information($"Server connection background task was canceled. Reason: {reason}");
await _appServiceConnectionManager.DisconnectAsync();
connectionBackgroundTaskDeferral?.Complete(); connectionBackgroundTaskDeferral?.Complete();
connectionBackgroundTaskDeferral = null; connectionBackgroundTaskDeferral = null;

View File

@@ -173,7 +173,7 @@ namespace Wino.Server
if (status != AppServiceConnectionStatus.Success) if (status != AppServiceConnectionStatus.Success)
{ {
// TODO: Handle connection error Log.Error("Opening server connection failed. Status: {status}", status);
DisposeConnection(); DisposeConnection();
} }
@@ -224,8 +224,6 @@ namespace Wino.Server
private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args) private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
{ {
// TODO: Handle connection closed.
// UWP app might've been terminated or suspended. // UWP app might've been terminated or suspended.
// At this point, we must keep active synchronizations going, but connection is lost. // 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. // As long as this process is alive, database will be kept updated, but no messages will be sent.