[misc bug fixes]

properly hide broadcast failure messages if ignore connection lost is turned on
fix concurent issue for update stats history that happened with new event processing
make get/set additional property thread safe
add ellipse to truncated chat messages on home
This commit is contained in:
RaidMax 2020-04-25 19:01:26 -05:00
parent ff011be8a6
commit 5529858edd
26 changed files with 258 additions and 74 deletions

View File

@ -480,6 +480,12 @@ namespace IW4MAdmin.Application
var client = await GetClientService().Get(clientId);
if (client == null)
{
_logger.WriteWarning($"No client found with id {clientId} when generating profile meta");
return metaList;
}
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,

View File

@ -0,0 +1,21 @@
using SharedLibraryCore.Database;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Factories
{
/// <summary>
/// implementation of the IDatabaseContextFactory interface
/// </summary>
public class DatabaseContextFactory : IDatabaseContextFactory
{
/// <summary>
/// creates a new database context
/// </summary>
/// <param name="enableTracking">indicates if entity tracking should be enabled</param>
/// <returns></returns>
public DatabaseContext CreateContext(bool? enableTracking = true)
{
return enableTracking.HasValue ? new DatabaseContext(disableTracking: !enableTracking.Value) : new DatabaseContext();
}
}
}

View File

@ -216,8 +216,11 @@ namespace IW4MAdmin
if (lastException != null)
{
Logger.WriteDebug("Last Exception is not null");
throw lastException;
bool notifyDisconnects = !Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost;
if (notifyDisconnects || (!notifyDisconnects && lastException as NetworkException == null))
{
throw lastException;
}
}
}
}
@ -250,7 +253,7 @@ namespace IW4MAdmin
if (E.Type == GameEvent.EventType.ConnectionRestored)
{
if (Throttled)
if (Throttled && !Manager.GetApplicationSettings().Configuration().IgnoreServerConnectionLost)
{
Logger.WriteVerbose(loc["MANAGER_CONNECTION_REST"].FormatExt($"[{IP}:{Port}]"));
}
@ -292,6 +295,12 @@ namespace IW4MAdmin
return false;
}
if (E.Origin.CurrentServer == null)
{
Logger.WriteWarning($"preconnecting client {E.Origin} did not have a current server specified");
E.Origin.CurrentServer = this;
}
var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
// they're already connected
@ -800,7 +809,7 @@ namespace IW4MAdmin
Manager.GetEventHandler().AddEvent(e);
}
if (ConnectionErrors > 0 && notifyDisconnects)
if (ConnectionErrors > 0)
{
var _event = new GameEvent()
{
@ -820,7 +829,7 @@ namespace IW4MAdmin
catch (NetworkException e)
{
ConnectionErrors++;
if (ConnectionErrors == 3 && notifyDisconnects)
if (ConnectionErrors == 3)
{
var _event = new GameEvent()
{

View File

@ -283,6 +283,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider =>
{

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -23,7 +23,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -52,7 +52,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (E.Target != null)
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
int performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Target.ClientId);
string performanceRankingString = performanceRanking == 0 ? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Target)))
@ -72,7 +72,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
else
{
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
int performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId);
string performanceRankingString = performanceRanking == 0 ? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
if (E.Owner.GetClientsAsList().Any(_client => _client.Equals(E.Origin)))

View File

@ -1,4 +1,5 @@
using IW4MAdmin.Plugins.Stats.Cheat;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Models;
using IW4MAdmin.Plugins.Stats.Web.Dtos;
using Microsoft.EntityFrameworkCore;
@ -23,32 +24,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
private const int MAX_CACHED_HITS = 100;
private readonly ConcurrentDictionary<long, ServerStats> _servers;
private readonly ILogger _log;
private readonly IDatabaseContextFactory _contextFactory;
private readonly IConfigurationHandler<StatsConfiguration> _configHandler;
private static List<EFServer> serverModels;
public static string CLIENT_STATS_KEY = "ClientStats";
public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
public StatManager(IManager mgr)
public StatManager(IManager mgr, IDatabaseContextFactory contextFactory, IConfigurationHandler<StatsConfiguration> configHandler)
{
_servers = new ConcurrentDictionary<long, ServerStats>();
_log = mgr.GetLogger(0);
_contextFactory = contextFactory;
_configHandler = configHandler;
}
private void SetupServerIds()
{
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
serverModels = ctx.Set<EFServer>().ToList();
}
}
public static Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
public Expression<Func<EFRating, bool>> GetRankingFunc(long? serverId = null)
{
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
return (r) => r.ServerId == serverId &&
r.When > fifteenDaysAgo &&
r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
r.Newest &&
r.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime;
r.ActivityAmount >= _configHandler.Configuration().TopPlayersMinPlayTime;
}
/// <summary>
@ -56,9 +61,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary>
/// <param name="clientId">client id of the player</param>
/// <returns></returns>
public static async Task<int> GetClientOverallRanking(int clientId)
public async Task<int> GetClientOverallRanking(int clientId)
{
using (var context = new DatabaseContext(true))
using (var context = _contextFactory.CreateContext(enableTracking: false))
{
var clientPerformance = await context.Set<EFRating>()
.Where(r => r.RatingHistory.ClientId == clientId)
@ -83,7 +88,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
{
using (var context = new DatabaseContext(true))
using (var context = _contextFactory.CreateContext(enableTracking: false))
{
// setup the query for the clients within the given rating range
var iqClientRatings = (from rating in context.Set<EFRating>()
@ -192,7 +197,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
long serverId = GetIdForServer(sv);
EFServer server;
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
var serverSet = ctx.Set<EFServer>();
// get the server from the database if it exists, otherwise create and insert a new one
@ -275,7 +280,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
EFClientStatistics clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
var clientStatsSet = ctx.Set<EFClientStatistics>();
clientStats = clientStatsSet
@ -394,9 +399,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
}
private static async Task SaveClientStats(EFClientStatistics clientStats)
private async Task SaveClientStats(EFClientStatistics clientStats)
{
using (var ctx = new DatabaseContext())
using (var ctx = _contextFactory.CreateContext())
{
ctx.Update(clientStats);
await ctx.SaveChangesAsync();
@ -591,7 +596,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task SaveHitCache(long serverId)
{
using (var ctx = new DatabaseContext(true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
var server = _servers[serverId];
ctx.AddRange(server.HitCache.ToList());
@ -659,7 +664,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
EFACSnapshot change;
using (var ctx = new DatabaseContext(true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
while ((change = clientDetection.Tracker.GetNextChange()) != default(EFACSnapshot))
{
@ -731,8 +736,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update their performance
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5)
{
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
await UpdateStatHistory(attacker, attackerStats);
try
{
// kill event is not designated as blocking, so we should be able to enter and exit
// we need to make this thread safe because we can potentially have kills qualify
// for stat history update, but one is already processing that invalidates the original
await attacker.Lock();
await UpdateStatHistory(attacker, attackerStats);
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
}
catch (Exception e)
{
_log.WriteWarning($"Could not update stat history for {attacker}");
_log.WriteDebug(e.GetExceptionInfo());
}
finally
{
attacker.Unlock();
}
}
}
@ -742,7 +765,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// <param name="client">client to update</param>
/// <param name="clientStats">stats of client that is being updated</param>
/// <returns></returns>
private async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats)
public async Task UpdateStatHistory(EFClient client, EFClientStatistics clientStats)
{
int currentSessionTime = (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds;
@ -754,7 +777,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
int currentServerTotalPlaytime = clientStats.TimePlayed + currentSessionTime;
using (var ctx = new DatabaseContext())
using (var ctx = _contextFactory.CreateContext(enableTracking: true))
{
// select the rating history for client
var iqHistoryLink = from history in ctx.Set<EFClientRatingHistory>()
@ -842,7 +865,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var clientStatsList = await iqClientStats.ToListAsync();
// add the current server's so we don't have to pull it frmo the database
// add the current server's so we don't have to pull it from the database
clientStatsList.Add(new
{
clientStats.Performance,
@ -1067,7 +1090,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
EFServerStatistics serverStats;
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId);
@ -1119,7 +1142,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return;
}
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{
ctx.Set<EFClientMessage>().Add(new EFClientMessage()
{
@ -1142,7 +1165,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
await waiter.WaitAsync();
using (var ctx = new DatabaseContext())
using (var ctx = _contextFactory.CreateContext())
{
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(_servers[serverId].ServerStatistics);

View File

@ -31,10 +31,12 @@ namespace IW4MAdmin.Plugins.Stats
int scriptDamageCount;
int scriptKillCount;
#endif
private readonly IDatabaseContextFactory _databaseContextFactory;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory)
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory)
{
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
_databaseContextFactory = databaseContextFactory;
}
public async Task OnEventAsync(GameEvent E, Server S)
@ -209,7 +211,7 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + (await StatManager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Value = "#" + (await Manager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 0,
Type = ProfileMeta.MetaType.Information
@ -495,7 +497,7 @@ namespace IW4MAdmin.Plugins.Stats
manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", mostPlayed));
ServerManager = manager;
Manager = new StatManager(manager);
Manager = new StatManager(manager, _databaseContextFactory, Config);
}
public Task OnTickAsync(Server S)

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -14,7 +14,7 @@
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.8" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.7" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.10" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -86,7 +86,10 @@ namespace SharedLibraryCore.Database
var connectionString = connectionStringBuilder.ToString();
var connection = new SqliteConnection(connectionString);
optionsBuilder.UseSqlite(connection);
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite(connection);
}
}
else

View File

@ -1,15 +1,41 @@
using System;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models
{
public class SharedEntity
public class SharedEntity : IPropertyExtender
{
private readonly ConcurrentDictionary<string, object> _additionalProperties;
/// <summary>
/// indicates if the entity is active
/// </summary>
public bool Active { get; set; } = true;
public SharedEntity()
{
_additionalProperties = new ConcurrentDictionary<string, object>();
}
public T GetAdditionalProperty<T>(string name)
{
return _additionalProperties.ContainsKey(name) ? (T)_additionalProperties[name] : default;
}
public void SetAdditionalProperty(string name, object value)
{
if (_additionalProperties.ContainsKey(name))
{
_additionalProperties[name] = value;
}
else
{
_additionalProperties.TryAdd(name, value);
}
}
///// <summary>
///// Specifies when the entity was created
///// </summary>

View File

@ -0,0 +1,17 @@
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// describes the capabilities of the database context factory
/// </summary>
public interface IDatabaseContextFactory
{
/// <summary>
/// create or retrieves an existing database context instance
/// </summary>
/// <param name="enableTracking">indicated if entity tracking should be enabled</param>
/// <returns>database context instance</returns>
DatabaseContext CreateContext(bool? enableTracking = true);
}
}

View File

@ -0,0 +1,23 @@
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// describes the capability of extending properties by name
/// </summary>
interface IPropertyExtender
{
/// <summary>
/// adds or updates property by name
/// </summary>
/// <param name="name">unique name of the property</param>
/// <param name="value">value of the property</param>
void SetAdditionalProperty(string name, object value);
/// <summary>
/// retreives a property by name
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name">name of the property</param>
/// <returns>property value if exists, otherwise default T</returns>
T GetAdditionalProperty<T>(string name);
}
}

View File

@ -86,10 +86,7 @@ namespace SharedLibraryCore.Database.Models
{
ConnectionTime = DateTime.UtcNow;
ClientNumber = -1;
_additionalProperties = new Dictionary<string, object>
{
{ "_reportCount", 0 }
};
SetAdditionalProperty("_reportCount", 0);
ReceivedPenalties = new List<EFPenalty>();
_processingEvent = new SemaphoreSlim(1, 1);
}
@ -101,7 +98,7 @@ namespace SharedLibraryCore.Database.Models
public override string ToString()
{
return $"{CurrentAlias?.Name ?? "--"}::{NetworkId}";
return $"[Name={CurrentAlias?.Name ?? "--"}, NetworkId={NetworkId.ToString("X")}, IP={(string.IsNullOrEmpty(IPAddressString) ? "--" : IPAddressString)}, ClientSlot={ClientNumber}]";
}
[NotMapped]
@ -643,26 +640,6 @@ namespace SharedLibraryCore.Database.Models
return true;
}
[NotMapped]
readonly Dictionary<string, object> _additionalProperties;
public T GetAdditionalProperty<T>(string name)
{
return _additionalProperties.ContainsKey(name) ? (T)_additionalProperties[name] : default(T);
}
public void SetAdditionalProperty(string name, object value)
{
if (_additionalProperties.ContainsKey(name))
{
_additionalProperties[name] = value;
}
else
{
_additionalProperties.Add(name, value);
}
}
[NotMapped]
public int ClientNumber { get; set; }
[NotMapped]

View File

@ -6,7 +6,7 @@
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.2.8</Version>
<Version>2.2.10</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
@ -20,8 +20,8 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<AssemblyVersion>2.2.8.0</AssemblyVersion>
<FileVersion>2.2.8.0</FileVersion>
<AssemblyVersion>2.2.10.0</AssemblyVersion>
<FileVersion>2.2.10.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">

View File

@ -1,7 +1,9 @@
using ApplicationTests.Fixtures;
using ApplicationTests.Mocks;
using FakeItEasy;
using IW4MAdmin;
using Microsoft.Extensions.DependencyInjection;
using SharedLibraryCore.Database;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
@ -13,11 +15,13 @@ namespace ApplicationTests
{
var manager = A.Fake<IManager>();
var logger = A.Fake<ILogger>();
A.CallTo(() => manager.GetLogger(A<long>.Ignored))
.Returns(logger);
serviceCollection.AddSingleton(logger)
.AddSingleton(manager)
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactoryMock>()
.AddSingleton(A.Fake<IRConConnectionFactory>())
.AddSingleton(A.Fake<IRConConnection>())
.AddSingleton(A.Fake<ITranslationLookup>())

View File

@ -29,7 +29,7 @@ namespace ApplicationTests.Fixtures
Level = EFClient.Permission.User,
Connections = 1,
FirstConnection = DateTime.UtcNow.AddDays(-1),
LastConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow.AddMinutes(-5),
NetworkId = 1,
TotalConnectionTime = 100,
CurrentAlias = new EFAlias()

View File

@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Interfaces;
using System;
namespace ApplicationTests.Mocks
{
class DatabaseContextFactoryMock : IDatabaseContextFactory
{
private DatabaseContext ctx;
private readonly IServiceProvider _serviceProvider;
public DatabaseContextFactoryMock(IServiceProvider sp)
{
_serviceProvider = sp;
}
public DatabaseContext CreateContext(bool? enableTracking)
{
if (ctx == null)
{
var contextOptions = new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase(databaseName: "database")
.Options;
ctx = new DatabaseContext(contextOptions);
}
return ctx;
}
}
}

View File

@ -10,6 +10,10 @@ using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Plugins.Stats.Config;
using System.Collections.Generic;
using SharedLibraryCore.Database.Models;
using Microsoft.Extensions.DependencyInjection;
using IW4MAdmin.Plugins.Stats.Helpers;
using ApplicationTests.Fixtures;
using System.Threading.Tasks;
namespace ApplicationTests
{
@ -17,12 +21,17 @@ namespace ApplicationTests
public class StatsTests
{
ILogger logger;
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
logger = A.Fake<ILogger>();
serviceProvider = new ServiceCollection()
.BuildBase()
.BuildServiceProvider();
void testLog(string msg) => Console.WriteLine(msg);
A.CallTo(() => logger.WriteError(A<string>.Ignored)).Invokes((string msg) => testLog(msg));
@ -37,7 +46,7 @@ namespace ApplicationTests
var mgr = A.Fake<IManager>();
var handlerFactory = A.Fake<IConfigurationHandlerFactory>();
var config = A.Fake<IConfigurationHandler<StatsConfiguration>>();
var plugin = new IW4MAdmin.Plugins.Stats.Plugin(handlerFactory);
var plugin = new IW4MAdmin.Plugins.Stats.Plugin(handlerFactory, null);
A.CallTo(() => config.Configuration())
.Returns(new StatsConfiguration()
@ -113,5 +122,36 @@ namespace ApplicationTests
public string BasePath => @"X:\IW4MAdmin\BUILD\Plugins";
}
[Test]
public async Task Test_ConcurrentCallsToUpdateStatHistoryDoesNotCauseException()
{
var server = serviceProvider.GetRequiredService<IW4MServer>();
var configHandler = A.Fake<IConfigurationHandler<StatsConfiguration>>();
var mgr = new StatManager(serviceProvider.GetRequiredService<IManager>(), serviceProvider.GetRequiredService<IDatabaseContextFactory>(), configHandler);
var target = ClientGenerators.CreateDatabaseClient();
target.CurrentServer = server;
A.CallTo(() => configHandler.Configuration())
.Returns(new StatsConfiguration()
{
TopPlayersMinPlayTime = 0
});
var dbFactory = serviceProvider.GetRequiredService<IDatabaseContextFactory>();
var db = dbFactory.CreateContext(true);
db.Set<EFServer>().Add(new EFServer()
{
EndPoint = server.EndPoint.ToString()
});
db.Clients.Add(target);
db.SaveChanges();
mgr.AddServer(server);
await mgr.AddPlayer(target);
var stats = target.GetAdditionalProperty<EFClientStatistics>("ClientStats");
await mgr.UpdateStatHistory(target, stats);
}
}
}

View File

@ -36,7 +36,7 @@
</span>
<span>
&mdash;
<color-code value="@Model.ChatHistory[i].Message.Substring(0, Math.Min(40, Model.ChatHistory[i].Message.Length))" allow="@ViewBag.EnableColorCodes"></color-code>
<color-code value="@Model.ChatHistory[i].Message.CapClientName(48)" allow="@ViewBag.EnableColorCodes"></color-code>
</span><br />
}
}
@ -113,7 +113,7 @@
<color-code value="@Model.ChatHistory[i].Name" allow="@ViewBag.EnableColorCodes"></color-code>
</span>
<span> &mdash;
<color-code value="@Model.ChatHistory[i].Message.Substring(0, Math.Min(40, Model.ChatHistory[i].Message.Length))" allow="@ViewBag.EnableColorCodes"></color-code>
<color-code value="@Model.ChatHistory[i].Message.CapClientName(48)" allow="@ViewBag.EnableColorCodes"></color-code>
</span><br />
}
}