diff --git a/Application/Misc/ServerDataViewer.cs b/Application/Misc/ServerDataViewer.cs index a0043579f..5205cfbe1 100644 --- a/Application/Misc/ServerDataViewer.cs +++ b/Application/Misc/ServerDataViewer.cs @@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.Misc private readonly IDataValueCache> _clientHistoryCache; private readonly TimeSpan? _cacheTimeSpan = - Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1); + Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10); public ServerDataViewer(ILogger logger, IDataValueCache snapshotCache, IDataValueCache serverStatsCache, @@ -36,7 +36,8 @@ namespace IW4MAdmin.Application.Misc _clientHistoryCache = clientHistoryCache; } - public async Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null, + public async Task<(int?, DateTime?)> + MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null, CancellationToken token = default) { _snapshotCache.SetCacheItem(async (snapshots, cancellationToken) => @@ -83,7 +84,7 @@ namespace IW4MAdmin.Application.Misc _logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients); return (maxClients, maxClientsTime); - }, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan); + }, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true); try { @@ -107,7 +108,7 @@ namespace IW4MAdmin.Application.Misc cancellationToken); return (count, recentCount); - }, nameof(_serverStatsCache), _cacheTimeSpan); + }, nameof(_serverStatsCache), _cacheTimeSpan, true); try { diff --git a/Data/Abstractions/IDataValueCache.cs b/Data/Abstractions/IDataValueCache.cs index 145eb1f9a..98dcb44c8 100644 --- a/Data/Abstractions/IDataValueCache.cs +++ b/Data/Abstractions/IDataValueCache.cs @@ -5,9 +5,10 @@ using Microsoft.EntityFrameworkCore; namespace Data.Abstractions { - public interface IDataValueCache where T : class + public interface IDataValueCache where TEntityType : class { - void SetCacheItem(Func, CancellationToken, Task> itemGetter, string keyName, TimeSpan? expirationTime = null); - Task GetCacheItem(string keyName, CancellationToken token = default); + void SetCacheItem(Func, CancellationToken, Task> itemGetter, string keyName, + TimeSpan? expirationTime = null, bool autoRefresh = false); + Task GetCacheItem(string keyName, CancellationToken token = default); } } \ No newline at end of file diff --git a/Data/Data.csproj b/Data/Data.csproj index 1aaaedb4b..dc6d683a0 100644 --- a/Data/Data.csproj +++ b/Data/Data.csproj @@ -8,7 +8,7 @@ RaidMax.IW4MAdmin.Data RaidMax.IW4MAdmin.Data - 1.0.7 + 1.0.8 diff --git a/Data/Helpers/DataValueCache.cs b/Data/Helpers/DataValueCache.cs index 140056a83..bbe48e334 100644 --- a/Data/Helpers/DataValueCache.cs +++ b/Data/Helpers/DataValueCache.cs @@ -1,58 +1,83 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using Data.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Timer = System.Timers.Timer; namespace Data.Helpers { - public class DataValueCache : IDataValueCache where T : class + public class DataValueCache : IDataValueCache + where TEntityType : class { private readonly ILogger _logger; private readonly IDatabaseContextFactory _contextFactory; - private readonly Dictionary _cacheStates = new Dictionary(); + + private readonly ConcurrentDictionary _cacheStates = + new ConcurrentDictionary(); + + private bool _autoRefresh; private const int DefaultExpireMinutes = 15; + private Timer _timer; private class CacheState { public string Key { get; set; } public DateTime LastRetrieval { get; set; } public TimeSpan ExpirationTime { get; set; } - public Func, CancellationToken, Task> Getter { get; set; } - public V Value { get; set; } + public Func, CancellationToken, Task> Getter { get; set; } + public TReturnType Value { get; set; } public bool IsExpired => ExpirationTime != TimeSpan.MaxValue && (DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0; } - public DataValueCache(ILogger> logger, IDatabaseContextFactory contextFactory) + public DataValueCache(ILogger> logger, + IDatabaseContextFactory contextFactory) { _logger = logger; _contextFactory = contextFactory; } - public void SetCacheItem(Func, CancellationToken, Task> getter, string key, - TimeSpan? expirationTime = null) + ~DataValueCache() + { + _timer?.Stop(); + _timer?.Dispose(); + } + + public void SetCacheItem(Func, CancellationToken, Task> getter, string key, + TimeSpan? expirationTime = null, bool autoRefresh = false) { if (_cacheStates.ContainsKey(key)) { - _logger.LogDebug("Cache key {key} is already added", key); + _logger.LogDebug("Cache key {Key} is already added", key); return; } - var state = new CacheState() + var state = new CacheState { Key = key, Getter = getter, ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes) }; - _cacheStates.Add(key, state); + _autoRefresh = autoRefresh; + + _cacheStates.TryAdd(key, state); + + if (!_autoRefresh || expirationTime == TimeSpan.MaxValue) + { + return; + } + + _timer = new Timer(state.ExpirationTime.TotalMilliseconds); + _timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None); + _timer.Start(); } - public async Task GetCacheItem(string keyName, CancellationToken cancellationToken = default) + public async Task GetCacheItem(string keyName, CancellationToken cancellationToken = default) { if (!_cacheStates.ContainsKey(keyName)) { @@ -61,7 +86,9 @@ namespace Data.Helpers var state = _cacheStates[keyName]; - if (state.IsExpired || state.Value == null) + // when auto refresh is off we want to check the expiration and value + // when auto refresh is on, we want to only check the value, because it'll be refreshed automatically + if ((state.IsExpired || state.Value == null) && !_autoRefresh || _autoRefresh && state.Value == null) { await RunCacheUpdate(state, cancellationToken); } @@ -73,15 +100,16 @@ namespace Data.Helpers { try { + _logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state); await using var context = _contextFactory.CreateContext(false); - var set = context.Set(); + var set = context.Set(); var value = await state.Getter(set, token); state.Value = value; state.LastRetrieval = DateTime.Now; } catch (Exception ex) { - _logger.LogError(ex, "Could not get cached value for {key}", state.Key); + _logger.LogError(ex, "Could not get cached value for {Key}", state.Key); } } } diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index e72765a71..29cafb8f1 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj index 67fd1d373..4227042cb 100644 --- a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj +++ b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index 2aba5954f..628ee6aa6 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -23,7 +23,7 @@ - + diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 9a181edf0..13126634f 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -19,7 +19,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 7851baea9..35fda70cc 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 7cfe211b9..2cc2772ab 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -17,7 +17,7 @@ - + diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index 06aed6017..9370e7924 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -20,7 +20,7 @@ - + diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index 08a32062f..270ff9db7 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -123,9 +123,9 @@ namespace SharedLibraryCore.Configuration if (passwords.Length > 1) { var (index, value) = - loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection("Other", null, + loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection(loc["SETUP_RCON_PASSWORD_MANUAL"], null, passwords.Select(pw => - $"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}").ToArray()); + $"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}").ToArray()); if (index > 0) { diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index ab3b23a02..ddff8537f 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -4,7 +4,7 @@ Library netcoreapp3.1 RaidMax.IW4MAdmin.SharedLibraryCore - 2021.8.31.1 + 2021.11.15.1 RaidMax Forever None Debug;Release;Prerelease @@ -19,7 +19,7 @@ true MIT Shared Library for IW4MAdmin - 2021.8.31.1 + 2021.11.15.1 @@ -44,7 +44,7 @@ - +