#nullable enable
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using WsjtxUtils.WsjtxMessages.Messages;
namespace WsjtxUtils.WsjtxUdpServer
{
///
/// A base WSJT-X UDP server message handler that tracks the client id and remote
/// endpoint of WSJT-X clients that have recently communicated with the server
///
public abstract class WsjtxUdpServerBaseAsyncMessageHandler : IWsjtxUdpMessageHandler
{
///
/// A function to be executed when a WSJT-X client connects for the first time
///
public virtual Func? ClientConnectedCallback { get; set; }
///
/// A function to be executed when a WSJT-X client closes the main window
///
public virtual Func? ClientClosedCallback { get; set; }
///
/// A function to be executed when a WSJT-X client is expired for a lack of communication which
/// exceeds the period
///
public virtual Func? ClientExpiredCallback { get; set; }
///
/// List of connected WSJT-X clients
///
public ConcurrentDictionary ConnectedClients { get; protected set; }
= new ConcurrentDictionary();
///
/// The period in seconds that a connected client will exist in the
/// with no communication
///
public virtual int ConnectedClientExpiryInSeconds { get; set; } = 300; // default 5 mins
#region Message Handlers
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleHeartbeatMessageAsync(WsjtxUdpServer server, Heartbeat message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleStatusMessageAsync(WsjtxUdpServer server, Status message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint, message);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleDecodeMessageAsync(WsjtxUdpServer server, Decode message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleClearMessageAsync(WsjtxUdpServer server, Clear message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleQsoLoggedMessageAsync(WsjtxUdpServer server, QsoLogged message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleClosedMessageAsync(WsjtxUdpServer server, Close message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
// remove the client and fire the closed event
if (ConnectedClients.TryRemove(message.Id, out WsjtxConnectedClient? target))
ClientClosedCallback?.Invoke(target);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleWSPRDecodeMessageAsync(WsjtxUdpServer server, WSPRDecode message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
///
/// Handle WSJT-X messages
///
///
///
///
///
///
public virtual Task HandleLoggedAdifMessageAsync(WsjtxUdpServer server, LoggedAdif message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
#endregion
#region Private Methods
///
/// Adds or updates a client to the list of communicating clients
///
///
///
///
private void AddUpdateOrExpireClient(string clientId, EndPoint endpoint, Status? status = null)
{
// add or update a connected client while updating the last communication time
bool isNewClient = false;
ConnectedClients.AddOrUpdate(clientId,
(id) =>
{
var client = new WsjtxConnectedClient(id, endpoint, status);
client.Status = status ?? client.Status;
isNewClient = true;
return client;
},
(id, client) =>
{
client.Status = status ?? client.Status;
client.LastCommunications = DateTime.UtcNow;
return client;
});
// execute the client connected callback if this is a new client
if (isNewClient)
ClientConnectedCallback?.Invoke(ConnectedClients[clientId]);
// build a list of all clients which have not communicated with
// the server for the window specified in lastHeardWindowSeconds
// and remove those clients from the connected clients list while
// executing the client expired callback on each client found
var expiredClients = ConnectedClients.Values
.Where(target => (DateTime.UtcNow - target.LastCommunications).TotalSeconds > ConnectedClientExpiryInSeconds)
.Select(target => target.ClientId);
foreach (var id in expiredClients)
if (ConnectedClients.TryRemove(id, out WsjtxConnectedClient? target))
ClientExpiredCallback?.Invoke(target);
}
#endregion
}
}