From dd496fb9a46bd1729367369fe2a34ed1d0208d90 Mon Sep 17 00:00:00 2001 From: Bernt Andreas Eide Date: Fri, 8 Mar 2024 17:25:29 +0100 Subject: [PATCH] Added a background service which caches/fetches game server details for some specific games, to ensure that these games always have available servers for display --- GameServerList.App/Helpers/GameList.cs | 1 - .../Helpers/GameServerWorker.cs | 83 +++++++++++++++++++ GameServerList.App/Program.cs | 2 + GameServerList.Common/Model/Game.cs | 3 + .../Services/SteamServerBrowserApiService.cs | 45 +--------- 5 files changed, 91 insertions(+), 43 deletions(-) create mode 100644 GameServerList.App/Helpers/GameServerWorker.cs diff --git a/GameServerList.App/Helpers/GameList.cs b/GameServerList.App/Helpers/GameList.cs index 6c3a866..973c093 100644 --- a/GameServerList.App/Helpers/GameList.cs +++ b/GameServerList.App/Helpers/GameList.cs @@ -1,6 +1,5 @@ using GameServerList.Common.Model; using GameServerList.Common.Utils; -using Newtonsoft.Json; namespace GameServerList.Helpers; diff --git a/GameServerList.App/Helpers/GameServerWorker.cs b/GameServerList.App/Helpers/GameServerWorker.cs new file mode 100644 index 0000000..65fcb1e --- /dev/null +++ b/GameServerList.App/Helpers/GameServerWorker.cs @@ -0,0 +1,83 @@ +using GameServerList.Common.External; +using GameServerList.Common.Model.A2S; +using GameServerList.Common.Model; +using System.Collections.Concurrent; + +namespace GameServerList.Helpers; + +public class GameServerWorker : BackgroundService +{ + public GameServerWorker() + { + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var nextUpdateAt = DateTime.MinValue; + var games = GameList + .Games + .Where(g => (g.UseDefinedServerList.HasValue && g.UseDefinedServerList.Value) || g.MasterServer.HasValue) + .ToList(); + + while (!stoppingToken.IsCancellationRequested) + { + if (DateTime.UtcNow <= nextUpdateAt) + { + Thread.Sleep(100); // put the thread on hold while waiting + continue; + } + + foreach (var game in games) + { + try + { + List servers = null; + + if (game.UseDefinedServerList ?? false) + servers = await QueryServers(game, game.Servers); + else if (game.MasterServer.HasValue) + { + var legacyServers = await A2SQuery.QueryServerList( + A2SQuery.GetMasterServerAddress(game.MasterServer.Value), game + ); + servers = await QueryServers(game, legacyServers.Select(s => s.Address).ToList()); + } + + game.GameServers = servers; + } + catch + { + // skip + } + } + + nextUpdateAt = DateTime.UtcNow.AddMinutes(15); + } + } + + private static bool IsServerValid(Game game, ServerInfo? server) + { + if (server is null || !server.HasValue) + return false; + + if (server.Value.MaxPlayers > 128 || server.Value.MaxPlayers <= 1 || server.Value.Players > server.Value.MaxPlayers) + return false; + + if (game.MasterServer.HasValue && game.MasterServer.Value == MasterServer.GoldSrc && !server.Value.Version.EndsWith("/Stdio")) + return false; + + return true; + } + + private static async Task> QueryServers(Game game, List servers, int timeout = 1500) + { + var items = new ConcurrentBag(); + await Parallel.ForEachAsync(servers, async (address, _) => + { + var obj = await A2SQuery.QueryServerInfo(address, timeout); + if (IsServerValid(game, obj)) + items.Add(obj.Value.MapToGameServerItem(game)); + }); + return [.. items]; + } +} \ No newline at end of file diff --git a/GameServerList.App/Program.cs b/GameServerList.App/Program.cs index 5c8e70e..df99daa 100644 --- a/GameServerList.App/Program.cs +++ b/GameServerList.App/Program.cs @@ -15,6 +15,8 @@ services.AddSingleton(); services.AddSingleton(); +services.AddHostedService(); + services.AddRazorComponents() .AddInteractiveServerComponents(); diff --git a/GameServerList.Common/Model/Game.cs b/GameServerList.Common/Model/Game.cs index 34425a1..cf9b87e 100644 --- a/GameServerList.Common/Model/Game.cs +++ b/GameServerList.Common/Model/Game.cs @@ -15,4 +15,7 @@ public class Game [JsonIgnore] public List? Servers { get; set; } + + [JsonIgnore] + public List? GameServers { get; set; } } \ No newline at end of file diff --git a/GameServerList.Common/Services/SteamServerBrowserApiService.cs b/GameServerList.Common/Services/SteamServerBrowserApiService.cs index c942ea4..af5579d 100644 --- a/GameServerList.Common/Services/SteamServerBrowserApiService.cs +++ b/GameServerList.Common/Services/SteamServerBrowserApiService.cs @@ -1,11 +1,8 @@ -using GameServerList.Common.External; -using GameServerList.Common.Model; -using GameServerList.Common.Model.A2S; +using GameServerList.Common.Model; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System.Collections.Concurrent; using System.Net; namespace GameServerList.Common.Services; @@ -48,18 +45,8 @@ public async Task> FetchServers(Game? game) { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); - if (game.UseDefinedServerList ?? false) - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10); - return await QueryServers(game, game.Servers); - } - else if (game.MasterServer.HasValue) - { - var legacyServers = await A2SQuery.QueryServerList( - A2SQuery.GetMasterServerAddress(game.MasterServer.Value), game - ); - return await QueryServers(game, legacyServers.Select(s => s.Address).ToList()); - } + if ((game.UseDefinedServerList ?? false) || game.MasterServer.HasValue) + return (game.GameServers is null) ? [] : game.GameServers; else { var gamedirFilter = string.IsNullOrEmpty(game.GameDir) ? string.Empty : $"\\gamedir\\{game.GameDir}"; @@ -72,20 +59,6 @@ public async Task> FetchServers(Game? game) }); } - private static bool IsServerValid(Game game, ServerInfo? server) - { - if (server is null || !server.HasValue) - return false; - - if (server.Value.MaxPlayers > 128 || server.Value.MaxPlayers <= 1) - return false; - - if (game.MasterServer.HasValue && game.MasterServer.Value == MasterServer.GoldSrc && !server.Value.Version.EndsWith("/Stdio")) - return false; - - return true; - } - private async Task> Fetch(string url) { try @@ -105,16 +78,4 @@ private async Task> Fetch(string url) return []; } } - - private static async Task> QueryServers(Game game, List servers, int timeout = 1500) - { - var items = new ConcurrentBag(); - await Parallel.ForEachAsync(servers, async (address, _) => - { - var obj = await A2SQuery.QueryServerInfo(address, timeout); - if (IsServerValid(game, obj)) - items.Add(obj.Value.MapToGameServerItem(game)); - }); - return [.. items]; - } } \ No newline at end of file