UIMessage communication. Single instancing for server and re-connection mechanism on suspension.

This commit is contained in:
Burak Kaan Köse
2024-07-17 22:36:10 +02:00
parent ad1c7e1fd3
commit 329eae3a25
87 changed files with 412 additions and 321 deletions

View File

@@ -1,18 +1,70 @@
using System.Windows;
using System.Threading;
using System.Windows;
using H.NotifyIcon;
namespace Wino.Server
{
/// <summary>
/// Single instance Wino Server.
/// Instancing is done using Mutex.
/// App will not start if another instance is already running.
/// App will let running server know that server execution is triggered, which will
/// led server to start new connection to requesting UWP app.
/// </summary>
public partial class App : Application
{
private const string WinoServerAppName = "Wino.Server";
private const string WinoServerActiatedName = "Wino.Server.Activated";
private TaskbarIcon? notifyIcon;
private static Mutex _mutex = null;
private EventWaitHandle _eventWaitHandle;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
bool isCreatedNew;
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
notifyIcon.ForceCreate(enablesEfficiencyMode: true);
_mutex = new Mutex(true, WinoServerAppName, out isCreatedNew);
_eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, WinoServerActiatedName);
if (isCreatedNew)
{
// Spawn a thread which will be waiting for our event
var thread = new Thread(() =>
{
while (_eventWaitHandle.WaitOne())
{
if (notifyIcon == null) return;
Current.Dispatcher.BeginInvoke(() =>
{
if (notifyIcon.DataContext is TrayIconViewModel trayIconViewModel)
{
trayIconViewModel.Reconnect();
}
});
}
});
// It is important mark it as background otherwise it will prevent app from exiting.
thread.IsBackground = true;
thread.Start();
base.OnStartup(e);
// Create taskbar icon for the new server.
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
notifyIcon.ForceCreate(enablesEfficiencyMode: true);
}
else
{
// Notify other instance so it could bring itself to foreground.
_eventWaitHandle.Set();
// Terminate this instance.
Shutdown();
}
}
protected override void OnExit(ExitEventArgs e)

View File

@@ -1,12 +1,21 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Messaging;
using Wino.Messaging.Enums;
using Wino.Messaging.Server;
namespace Wino.Server
{
public class ServerContext
{
private static object connectionLock = new object();
private AppServiceConnection connection = null;
public ServerContext()
@@ -14,15 +23,33 @@ namespace Wino.Server
InitializeAppServiceConnection();
}
private string GetAppPackagFamilyName()
{
// If running as a standalone app, Package will throw exception.
// Return hardcoded value for debugging purposes.
// Connection will not be available in this case.
try
{
return Package.Current.Id.FamilyName;
}
catch (Exception)
{
return "Debug.Wino.Server.FamilyName";
}
}
/// <summary>
/// Open connection to UWP app service
/// </summary>
public async void InitializeAppServiceConnection()
{
if (connection != null) DisposeConnection();
connection = new AppServiceConnection
{
AppServiceName = "WinoInteropService",
PackageFamilyName = Package.Current.Id.FamilyName
PackageFamilyName = GetAppPackagFamilyName()
};
connection.RequestReceived += OnWinRTMessageReceived;
@@ -33,13 +60,33 @@ namespace Wino.Server
if (status != AppServiceConnectionStatus.Success)
{
// TODO: Handle connection error
DisposeConnection();
}
}
public async void SendMessage()
public Task SendTestMessageAsync()
{
var set = new ValueSet();
set.Add("Hello", "World");
var message = new MailAddedMessage(new MailCopy());
return SendMessageAsync(MessageType.UIMessage, message);
}
private async Task SendMessageAsync(MessageType messageType, object message)
{
if (connection == null) return;
if (message is not IServerMessage serverMessage)
throw new ArgumentException("Server message must be a type of IServerMessage");
string json = JsonSerializer.Serialize(message);
var set = new ValueSet
{
{ MessageConstants.MessageTypeKey, (int)messageType },
{ MessageConstants.MessageDataKey, json },
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
};
await connection.SendMessageAsync(set);
}
@@ -47,12 +94,30 @@ namespace Wino.Server
{
// TODO: Handle connection closed.
// UWP app might've been terminated.
// 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.
DisposeConnection();
}
private void OnWinRTMessageReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
// TODO: Handle incoming messages from UWP/WINUI Application.
}
private void DisposeConnection()
{
lock (connectionLock)
{
if (connection == null) return;
connection.RequestReceived -= OnWinRTMessageReceived;
connection.ServiceClosed -= OnConnectionClosed;
connection.Dispose();
connection = null;
}
}
}
}

View File

@@ -11,7 +11,7 @@ namespace Wino.Server
[RelayCommand]
public void LaunchWino()
{
_context.SendMessage();
_context.SendTestMessageAsync();
}
/// <summary>
@@ -24,5 +24,7 @@ namespace Wino.Server
Application.Current.Shutdown();
}
public void Reconnect() => _context.InitializeAppServiceConnection();
}
}

View File

@@ -25,6 +25,6 @@
<ItemGroup>
<ProjectReference Include="..\Wino.Core.Domain\Wino.Core.Domain.csproj" />
<ProjectReference Include="..\Wino.Core\Wino.Core.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messages.csproj" />
<ProjectReference Include="..\Wino.Messages\Wino.Messaging.csproj" />
</ItemGroup>
</Project>