[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:
parent
ff011be8a6
commit
5529858edd
@ -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,
|
||||
|
21
Application/Factories/DatabaseContextFactory.cs
Normal file
21
Application/Factories/DatabaseContextFactory.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
{
|
||||
|
@ -283,6 +283,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
|
||||
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
|
||||
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
||||
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton(_serviceProvider =>
|
||||
{
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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)))
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
17
SharedLibraryCore/Interfaces/IDatabaseContextFactory.cs
Normal file
17
SharedLibraryCore/Interfaces/IDatabaseContextFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
23
SharedLibraryCore/Interfaces/IPropertyExtender.cs
Normal file
23
SharedLibraryCore/Interfaces/IPropertyExtender.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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'">
|
||||
|
@ -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>())
|
||||
|
@ -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()
|
||||
|
32
Tests/ApplicationTests/Mocks/DatabaseContextFactoryMock.cs
Normal file
32
Tests/ApplicationTests/Mocks/DatabaseContextFactoryMock.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
</span>
|
||||
<span>
|
||||
—
|
||||
<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> —
|
||||
<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 />
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user