-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathWsjtxUdpServerBaseAsyncMessageHandler.cs
More file actions
208 lines (189 loc) · 8.68 KB
/
WsjtxUdpServerBaseAsyncMessageHandler.cs
File metadata and controls
208 lines (189 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#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
{
/// <summary>
/// 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
/// </summary>
public abstract class WsjtxUdpServerBaseAsyncMessageHandler : IWsjtxUdpMessageHandler
{
/// <summary>
/// A function to be executed when a WSJT-X client connects for the first time
/// </summary>
public virtual Func<WsjtxConnectedClient, Task>? ClientConnectedCallback { get; set; }
/// <summary>
/// A function to be executed when a WSJT-X client closes the main window
/// </summary>
public virtual Func<WsjtxConnectedClient, Task>? ClientClosedCallback { get; set; }
/// <summary>
/// A function to be executed when a WSJT-X client is expired for a lack of communication which
/// exceeds the <see cref="ConnectedClientExpiryInSeconds"/> period
/// </summary>
public virtual Func<WsjtxConnectedClient, Task>? ClientExpiredCallback { get; set; }
/// <summary>
/// List of connected WSJT-X clients
/// </summary>
public ConcurrentDictionary<string, WsjtxConnectedClient> ConnectedClients { get; protected set; }
= new ConcurrentDictionary<string, WsjtxConnectedClient>();
/// <summary>
/// The period in seconds that a connected client will exist in the
/// <see cref="ConnectedClients"/> with no communication
/// </summary>
public virtual int ConnectedClientExpiryInSeconds { get; set; } = 300; // default 5 mins
#region Message Handlers
/// <summary>
/// Handle WSJT-X <see cref="Heartbeat"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleHeartbeatMessageAsync(WsjtxUdpServer server, Heartbeat message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="Status"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleStatusMessageAsync(WsjtxUdpServer server, Status message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint, message);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="Decode"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleDecodeMessageAsync(WsjtxUdpServer server, Decode message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="Clear"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleClearMessageAsync(WsjtxUdpServer server, Clear message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="QsoLogged"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleQsoLoggedMessageAsync(WsjtxUdpServer server, QsoLogged message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="Close"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Handle WSJT-X <see cref="WSPRDecode"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleWSPRDecodeMessageAsync(WsjtxUdpServer server, WSPRDecode message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
/// <summary>
/// Handle WSJT-X <see cref="LoggedAdif"/> messages
/// </summary>
/// <param name="server"></param>
/// <param name="message"></param>
/// <param name="endPoint"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task HandleLoggedAdifMessageAsync(WsjtxUdpServer server, LoggedAdif message, EndPoint endPoint, CancellationToken cancellationToken = default)
{
AddUpdateOrExpireClient(message.Id, endPoint);
return Task.CompletedTask;
}
#endregion
#region Private Methods
/// <summary>
/// Adds or updates a client to the list of communicating clients
/// </summary>
/// <param name="clientId"></param>
/// <param name="endpoint"></param>
/// <param name="status"></param>
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
}
}