Skip to content

Commit 4147111

Browse files
committed
Issue3: Added a socket that will report when the ladder has changed and can be used to provide realtime notifications on the website.
1 parent 041d378 commit 4147111

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

Beef/BeefApi/ApiServer.cs

+95
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,38 @@
22
using System;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Net;
6+
using System.Net.Sockets;
57
using System.Text;
68
using System.Threading.Tasks;
79

810

911
namespace Beef.BeefApi {
12+
/// <summary>
13+
/// The ApiServer hosts a REST endpoint at /beef-ladder that returns a json representation
14+
/// of the current ladder rankings. It also provides a socket that can be connected to at
15+
/// the configured port in order to be notified realtime when the ladder has been changed.
16+
/// </summary>
1017
public class ApiServer {
1118
private String _configFilePath;
1219
private SynchronizationContext _mainContext;
1320
private BeefUserConfigManager _beefUserManager;
1421
private PresentationManager _ladderManager;
1522
private WebApplication _application;
1623
private Thread _thread;
24+
private SynchronizationContext _threadContext;
25+
private int _eventSocket; // set from the config file
26+
private List<Socket> _eventClients = new List<Socket>();
1727
private object _lock = new object();
1828

1929
public ApiServer(String configFilePath, SynchronizationContext mainContext, PresentationManager ladderManager, BeefUserConfigManager beefUserManager) {
2030
_configFilePath = configFilePath;
2131
_mainContext = mainContext;
2232
_ladderManager = ladderManager;
2333
_beefUserManager = beefUserManager;
34+
35+
// Subscribe to the ladder changed event
36+
_ladderManager.LadderChanged += OnLadderChanged;
2437
}
2538

2639
public void Start() {
@@ -51,9 +64,13 @@ public void Stop() {
5164
}
5265

5366
private void ThreadStart() {
67+
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
68+
_threadContext = SynchronizationContext.Current;
69+
5470
lock (_lock) {
5571
var builder = WebApplication.CreateBuilder();
5672
builder.Configuration.AddJsonFile(_configFilePath);
73+
_eventSocket = builder.Configuration.GetValue<int>("LadderChangedEventPort", 5002);
5774

5875
_application = builder.Build();
5976
Monitor.PulseAll(_lock);
@@ -117,7 +134,85 @@ await Task.Run(() => {
117134
await context.Response.WriteAsJsonAsync(response);
118135
});
119136

137+
_threadContext.Post(async (object? state) => {
138+
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, _eventSocket);
139+
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
140+
listener.Bind(localEndPoint);
141+
listener.Listen();
142+
143+
while (true) {
144+
Socket client = await listener.AcceptAsync();
145+
146+
lock (_eventClients) {
147+
_eventClients.Add(client);
148+
}
149+
}
150+
}, null);
151+
120152
_application.Run();
121153
}
154+
155+
private void NotifyLadderChanged() {
156+
List<Socket> clientsToRemove = new List<Socket>();
157+
lock (_eventClients) {
158+
foreach (Socket client in _eventClients) {
159+
if (!client.Connected) {
160+
clientsToRemove.Add(client);
161+
continue;
162+
}
163+
164+
try {
165+
String eventMessage = "{ \"Message\": \"OnLadderChanged\" }";
166+
int messageLength = eventMessage.Length;
167+
byte[] lengthBytes = GetBytesInNetworkOrder(messageLength);
168+
byte[] messageBytes = Encoding.UTF8.GetBytes(eventMessage);
169+
170+
SendBytesOrDie(lengthBytes, client);
171+
SendBytesOrDie(messageBytes, client);
172+
} catch (Exception) {
173+
// Any exception should just close the connection and keep going
174+
clientsToRemove.Add(client);
175+
}
176+
}
177+
178+
// Cleanup anyone that has disconnected
179+
foreach (Socket client in clientsToRemove) {
180+
_eventClients.Remove(client);
181+
}
182+
}
183+
}
184+
185+
private byte[] GetBytesInNetworkOrder(int number) {
186+
byte[] numberBytes = BitConverter.GetBytes(number);
187+
if (BitConverter.IsLittleEndian)
188+
Array.Reverse(numberBytes);
189+
return numberBytes;
190+
}
191+
192+
private void OnLadderChanged(List<BeefEntry> entries) {
193+
_threadContext.Post((object? state) => {
194+
NotifyLadderChanged();
195+
}, null);
196+
}
197+
198+
/// <summary>
199+
/// Sends the given bytes to the given socket. If all the bytes aren't sent, an
200+
/// exception is thrown. Note that in the event the buffer was sent in stages and
201+
/// an error occurs inbetween the stages, some bytes could have been sent prior to the exception.
202+
/// </summary>
203+
/// <param name="bytes">The bytes to send to the socket.</param>
204+
/// <param name="socket">The socket to write to. It is assumed it's already connected.</param>
205+
private void SendBytesOrDie(byte[] bytes, Socket socket) {
206+
int bytesSent = 0;
207+
int index = 0;
208+
while (bytesSent < bytes.Length) {
209+
int result = socket.Send(bytes, index, bytes.Length - index, SocketFlags.None);
210+
if (result <= 0) {
211+
throw new SocketException(result);
212+
} else {
213+
bytesSent += result;
214+
}
215+
}
216+
}
122217
}
123218
}

Beef/BeefApiConfig.json.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"Url": "https://localhost:5001"
99
}
1010
}
11-
}
11+
},
12+
"LadderChangedEventPort": 5002
1213
}

Beef/PresentationManager.cs

+7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public class PresentationManager {
3434
private Dictionary<String, Tuple<ProfileInfo, LadderInfo>> _mmrDictionary;
3535
private Dictionary<int, String> _rankToObjectId; // A map of rank to the ID of the object in the slide representing that rank
3636

37+
public delegate void OnLadderChanged(List<BeefEntry> entries);
38+
public event OnLadderChanged LadderChanged;
39+
3740
/// <summary>
3841
/// Creates a PresentationManager with the given information.
3942
/// </summary>
@@ -576,6 +579,10 @@ public ErrorCode SubmitRequests(bool backup) {
576579
}
577580

578581
_requestList.Clear();
582+
583+
// Notify listeners
584+
LadderChanged.Invoke(_entries);
585+
579586
return result;
580587
}
581588

0 commit comments

Comments
 (0)