UIMessage communication. Single instancing for server and re-connection mechanism on suspension.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user