diff --git a/Application/Application.csproj b/Application/Application.csproj index 1ea5f9cf..15acd3da 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -3,9 +3,10 @@ Exe netcoreapp2.1 + 2.1.5 false RaidMax.IW4MAdmin.Application - 2.1.9.4 + 2.1.9.5 RaidMax Forever None IW4MAdmin @@ -30,8 +31,8 @@ true true - 2.1.9.4 - 2.1.9.4 + 2.1.9.5 + 2.1.9.5 @@ -78,7 +79,7 @@ - + diff --git a/Application/Logger.cs b/Application/Logger.cs index bbfc9874..ab8b2c64 100644 --- a/Application/Logger.cs +++ b/Application/Logger.cs @@ -20,11 +20,37 @@ namespace IW4MAdmin.Application readonly string FileName; readonly SemaphoreSlim OnLogWriting; + static readonly short MAX_LOG_FILES = 10; public Logger(string fn) { - FileName = Path.Join("Log", $"{fn}-{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.log"); - OnLogWriting = new SemaphoreSlim(1,1); + FileName = Path.Join("Log", $"{fn}.log"); + OnLogWriting = new SemaphoreSlim(1, 1); + RotateLogs(); + } + + /// + /// rotates logs when log is initialized + /// + private void RotateLogs() + { + string maxLog = FileName + MAX_LOG_FILES; + + if (File.Exists(maxLog)) + { + File.Delete(maxLog); + } + + for (int i = MAX_LOG_FILES - 1; i >= 0; i--) + { + string logToMove = i == 0 ? FileName : FileName + i; + string movedLogName = FileName + (i + 1); + + if (File.Exists(logToMove)) + { + File.Move(logToMove, movedLogName); + } + } } void Write(string msg, LogType type) @@ -41,17 +67,25 @@ namespace IW4MAdmin.Application catch (Exception) { } string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}"; + try + { #if DEBUG - // lets keep it simple and dispose of everything quickly as logging wont be that much (relatively) - - Console.WriteLine(LogLine); - File.AppendAllText(FileName, LogLine + Environment.NewLine); + // lets keep it simple and dispose of everything quickly as logging wont be that much (relatively) + Console.WriteLine(LogLine); + File.AppendAllText(FileName, LogLine + Environment.NewLine); #else if (type == LogType.Error || type == LogType.Verbose) Console.WriteLine(LogLine); //if (type != LogType.Debug) File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}"); #endif + } + + catch (Exception ex) + { + Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else..."); + Console.WriteLine(ex.GetExceptionInfo()); + } OnLogWriting.Release(1); } diff --git a/Application/Main.cs b/Application/Main.cs index 6b945d87..31676419 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -106,7 +106,7 @@ namespace IW4MAdmin.Application var consoleTask = Task.Run(async () => { String userInput; - Player Origin = Utilities.IW4MAdminClient; + Player Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); do { @@ -123,7 +123,6 @@ namespace IW4MAdmin.Application if (userInput?.Length > 0) { - Origin.CurrentServer = ServerManager.Servers[0]; GameEvent E = new GameEvent() { Type = GameEvent.EventType.Command, diff --git a/Application/Server.cs b/Application/Server.cs index 30a3481f..7568a44d 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -205,8 +205,7 @@ namespace IW4MAdmin if (currentBan != null) { Logger.WriteInfo($"Banned client {player} trying to connect..."); - var autoKickClient = Utilities.IW4MAdminClient; - autoKickClient.CurrentServer = this; + var autoKickClient = Utilities.IW4MAdminClient(this); // the player is permanently banned if (currentBan.Type == Penalty.PenaltyType.Ban) @@ -911,7 +910,7 @@ namespace IW4MAdmin { if (Target.Warnings >= 4) { - Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient); + Target.Kick(loc["SERVER_WARNLIMT_REACHED"], Utilities.IW4MAdminClient(this)); return; } diff --git a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj index a69ad40e..8465824c 100644 --- a/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj +++ b/Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 @@ -17,7 +18,7 @@ - + diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 526f623d..5375174f 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 RaidMax.IW4MAdmin.Plugins.Login @@ -21,7 +22,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 041617bd..b0db5bdb 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 RaidMax.IW4MAdmin.Plugins.ProfanityDeterment @@ -19,7 +20,7 @@ - + diff --git a/Plugins/ScriptPlugins/VPNDetection.js b/Plugins/ScriptPlugins/VPNDetection.js index ac291128..32dc4e6c 100644 --- a/Plugins/ScriptPlugins/VPNDetection.js +++ b/Plugins/ScriptPlugins/VPNDetection.js @@ -28,7 +28,6 @@ var plugin = { var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result; var co = re.Content; var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result); - // todo: does this work as expected now? co.Dispose(); re.Dispose(); cl.Dispose(); @@ -39,11 +38,7 @@ var plugin = { if (usingVPN) { this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')'); - var library = importNamespace('SharedLibraryCore'); - var kickOrigin = new library.Objects.Player(); - kickOrigin.ClientId = 1; - kickOrigin.CurrentServer = origin.CurrentServer; - origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin); + origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient); } }, diff --git a/Plugins/Stats/Commands/ResetStats.cs b/Plugins/Stats/Commands/ResetStats.cs index ebcbe88f..b9a68de3 100644 --- a/Plugins/Stats/Commands/ResetStats.cs +++ b/Plugins/Stats/Commands/ResetStats.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using SharedLibraryCore.Database; +using Microsoft.EntityFrameworkCore; namespace IW4MAdmin.Plugins.Stats.Commands { @@ -17,23 +19,30 @@ namespace IW4MAdmin.Plugins.Stats.Commands { if (E.Origin.ClientNumber >= 0) { - var svc = new SharedLibraryCore.Services.GenericRepository(); + int serverId = E.Owner.GetHashCode(); - var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First(); - stats.Deaths = 0; - stats.Kills = 0; - stats.SPM = 0.0; - stats.Skill = 0.0; - stats.TimePlayed = 0; - // todo: make this more dynamic - stats.EloRating = 200.0; + EFClientStatistics clientStats; + using (var ctx = new DatabaseContext(disableTracking: true)) + { + clientStats = await ctx.Set() + .Where(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId) + .FirstAsync(); - // reset the cached version - Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); + clientStats.Deaths = 0; + clientStats.Kills = 0; + clientStats.SPM = 0.0; + clientStats.Skill = 0.0; + clientStats.TimePlayed = 0; + // todo: make this more dynamic + clientStats.EloRating = 200.0; - // fixme: this doesn't work properly when another context exists - await svc.SaveChangesAsync(); + // reset the cached version + Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode()); + + // fixme: this doesn't work properly when another context exists + await ctx.SaveChangesAsync(); + } E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]); } diff --git a/Plugins/Stats/Commands/ViewStats.cs b/Plugins/Stats/Commands/ViewStats.cs index 80a55985..eb71c092 100644 --- a/Plugins/Stats/Commands/ViewStats.cs +++ b/Plugins/Stats/Commands/ViewStats.cs @@ -7,6 +7,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using SharedLibraryCore.Database; +using Microsoft.EntityFrameworkCore; namespace IW4MAdmin.Plugins.Stats.Commands { @@ -39,19 +41,21 @@ namespace IW4MAdmin.Plugins.Stats.Commands } } - var clientStats = new GenericRepository(); int serverId = E.Owner.GetHashCode(); - if (E.Target != null) + using (var ctx = new DatabaseContext(disableTracking: true)) { - pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)).First(); - statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; - } + if (E.Target != null) + { + pStats = (await ctx.Set().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId)); + statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; + } - else - { - pStats = (await clientStats.FindAsync(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)).First(); - statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; + else + { + pStats = (await ctx.Set().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId))); + statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()}"; + } } if (E.Message.IsBroadcastCommand()) diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index db276eba..285a6e98 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -24,20 +24,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public class StatManager { private ConcurrentDictionary Servers; - private ConcurrentDictionary ContextThreads; private ILogger Log; - private IManager Manager; + private readonly IManager Manager; private readonly SemaphoreSlim OnProcessingPenalty; + private readonly SemaphoreSlim OnProcessingSensitive; public StatManager(IManager mgr) { Servers = new ConcurrentDictionary(); - ContextThreads = new ConcurrentDictionary(); Log = mgr.GetLogger(0); Manager = mgr; OnProcessingPenalty = new SemaphoreSlim(1, 1); + OnProcessingSensitive = new SemaphoreSlim(1, 1); } public EFClientStatistics GetClientStats(int clientId, int serverId) => Servers[serverId].PlayerStats[clientId]; @@ -188,36 +188,36 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// public void AddServer(Server sv) { + // insert the server if it does not exist try { int serverId = sv.GetHashCode(); - var statsSvc = new ThreadSafeStatsService(); - ContextThreads.TryAdd(serverId, statsSvc); + EFServer server; - var serverSvc = statsSvc.ServerSvc; - - // get the server from the database if it exists, otherwise create and insert a new one - var server = statsSvc.ServerSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); - - if (server == null) + using (var ctx = new DatabaseContext(disableTracking: true)) { - server = new EFServer() - { - Port = sv.GetPort(), - Active = true, - ServerId = serverId - }; + var serverSet = ctx.Set(); + // get the server from the database if it exists, otherwise create and insert a new one + server = serverSet.FirstOrDefault(c => c.ServerId == serverId); - serverSvc.Insert(server); + if (server == null) + { + server = new EFServer() + { + Port = sv.GetPort(), + Active = true, + ServerId = serverId + }; + + server = serverSet.Add(server).Entity; + // this doesn't need to be async as it's during initialization + ctx.SaveChanges(); + } } - // this doesn't need to be async as it's during initialization - serverSvc.SaveChanges(); // check to see if the stats have ever been initialized - InitializeServerStats(sv); - statsSvc.ServerStatsSvc.SaveChanges(); + var serverStats = InitializeServerStats(sv); - var serverStats = statsSvc.ServerStatsSvc.Find(c => c.ServerId == serverId).FirstOrDefault(); Servers.TryAdd(serverId, new ServerStats(server, serverStats) { IsTeamBased = sv.Gametype != "dm" @@ -237,106 +237,134 @@ namespace IW4MAdmin.Plugins.Stats.Helpers /// EFClientStatistic of specified player public async Task AddPlayer(Player pl) { - int serverId = pl.CurrentServer.GetHashCode(); + await OnProcessingSensitive.WaitAsync(); - if (!Servers.ContainsKey(serverId)) + try { - Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); - return null; - } + int serverId = pl.CurrentServer.GetHashCode(); - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; - var statsSvc = ContextThreads[serverId]; - if (playerStats.ContainsKey(pl.ClientId)) - { - Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); - return playerStats[pl.ClientId]; - } - - // get the client's stats from the database if it exists, otherwise create and attach a new one - // if this fails we want to throw an exception - var clientStatsSvc = statsSvc.ClientStatSvc; - var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault(); - - if (clientStats == null) - { - clientStats = new EFClientStatistics() + if (!Servers.ContainsKey(serverId)) { - Active = true, - ClientId = pl.ClientId, - Deaths = 0, - Kills = 0, - ServerId = serverId, - Skill = 0.0, - SPM = 0.0, - EloRating = 200.0, - HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() - { - Active = true, - HitCount = 0, - Location = hl - }).ToList() - }; - - // insert if they've not been added - clientStats = clientStatsSvc.Insert(clientStats); - - if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) - { - Log.WriteWarning("Adding new client to stats failed"); + Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); + return null; } - await clientStatsSvc.SaveChangesAsync(); - } + var playerStats = Servers[serverId].PlayerStats; + var detectionStats = Servers[serverId].PlayerDetections; - else - { - if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) + if (playerStats.ContainsKey(pl.ClientId)) { - Log.WriteWarning("Adding pre-existing client to stats failed"); + Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); + return playerStats[pl.ClientId]; } - } - // migration for previous existing stats - if (clientStats.HitLocations.Count == 0) - { - clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType() - .Select(hl => new EFHitLocationCount() + // get the client's stats from the database if it exists, otherwise create and attach a new one + // if this fails we want to throw an exception + + EFClientStatistics clientStats; + + using (var ctx = new DatabaseContext(disableTracking: true)) + { + var clientStatsSet = ctx.Set(); + clientStats = clientStatsSet + .Include(cl => cl.HitLocations) + .FirstOrDefault(c => c.ClientId == pl.ClientId && c.ServerId == serverId); + + if (clientStats == null) { - Active = true, - HitCount = 0, - Location = hl - }) - .ToList(); - await statsSvc.ClientStatSvc.SaveChangesAsync(); + clientStats = new EFClientStatistics() + { + Active = true, + ClientId = pl.ClientId, + Deaths = 0, + Kills = 0, + ServerId = serverId, + Skill = 0.0, + SPM = 0.0, + EloRating = 200.0, + HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType().Select(hl => new EFHitLocationCount() + { + Active = true, + HitCount = 0, + Location = hl + }).ToList() + }; + + // insert if they've not been added + clientStats = clientStatsSet.Add(clientStats).Entity; + await ctx.SaveChangesAsync(); + + if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) + { + Log.WriteWarning("Adding new client to stats failed"); + } + + } + + else + { + if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) + { + Log.WriteWarning("Adding pre-existing client to stats failed"); + } + } + + // migration for previous existing stats + if (clientStats.HitLocations.Count == 0) + { + clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType() + .Select(hl => new EFHitLocationCount() + { + Active = true, + HitCount = 0, + Location = hl + }) + .ToList(); + + ctx.Update(clientStats); + await ctx.SaveChangesAsync(); + } + + // for stats before rating + if (clientStats.EloRating == 0.0) + { + clientStats.EloRating = clientStats.Skill; + } + + if (clientStats.RollingWeightedKDR == 0) + { + clientStats.RollingWeightedKDR = clientStats.KDR; + } + + // set these on connecting + clientStats.LastActive = DateTime.UtcNow; + clientStats.LastStatCalculation = DateTime.UtcNow; + clientStats.SessionScore = pl.Score; + clientStats.LastScore = pl.Score; + + if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) + { + Log.WriteWarning("Could not add client to detection"); + } + + Log.WriteInfo($"Adding {pl} to stats"); + } + + return clientStats; } - // for stats before rating - if (clientStats.EloRating == 0.0) + catch (Exception ex) { - clientStats.EloRating = clientStats.Skill; + } - if (clientStats.RollingWeightedKDR == 0) + finally { - clientStats.RollingWeightedKDR = clientStats.KDR; + OnProcessingSensitive.Release(1); } - // set these on connecting - clientStats.LastActive = DateTime.UtcNow; - clientStats.LastStatCalculation = DateTime.UtcNow; - clientStats.SessionScore = pl.Score; - clientStats.LastScore = pl.Score; - - if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) - { - Log.WriteWarning("Could not add client to detection"); - } - - Log.WriteInfo($"Adding {pl} to stats"); - return clientStats; + return null; } /// @@ -352,7 +380,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var playerStats = Servers[serverId].PlayerStats; var detectionStats = Servers[serverId].PlayerDetections; var serverStats = Servers[serverId].ServerStatistics; - var statsSvc = ContextThreads[serverId]; if (!playerStats.ContainsKey(pl.ClientId)) { @@ -371,10 +398,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4); // sync their stats before they leave - var clientStatsSvc = statsSvc.ClientStatSvc; clientStats = UpdateStats(clientStats); - clientStatsSvc.Update(clientStats); - await clientStatsSvc.SaveChangesAsync(); + + using (var ctx = new DatabaseContext(disableTracking: true)) + { + ctx.Update(clientStats); + await ctx.SaveChangesAsync(); + } // increment the total play time serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds; @@ -405,15 +435,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, string fraction, string visibilityPercentage, string snapAngles) { - var statsSvc = ContextThreads[serverId]; - - // incase the add palyer event get delayed - if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) - { - await AddPlayer(attacker); - } - - Vector3 vDeathOrigin = null; Vector3 vKillOrigin = null; Vector3 vViewAngles = null; @@ -490,10 +511,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return; } + // incase the add palyer event get delayed + if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + { + await AddPlayer(attacker); + } + var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; - var clientStatsSvc = statsSvc.ClientStatSvc; - clientStatsSvc.Update(clientStats); + + using (var ctx = new DatabaseContext(disableTracking: true)) + { + ctx.Set().Update(clientStats); + await ctx.SaveChangesAsync(); + } // increment their hit count if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET || @@ -531,17 +562,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Log.WriteDebug(ex.GetExceptionInfo()); } - try - { - await clientStatsSvc.SaveChangesAsync(); - } - - catch (Exception ex) - { - Log.WriteError("Could save save client stats"); - Log.WriteDebug(ex.GetExceptionInfo()); - } - OnProcessingPenalty.Release(1); } } @@ -743,10 +763,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // todo: do we want to save this immediately? - var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; - clientStatsSvc.Update(attackerStats); - clientStatsSvc.Update(victimStats); - await clientStatsSvc.SaveChangesAsync(); + using (var ctx = new DatabaseContext(disableTracking: true)) + { + var clientStatsSet = ctx.Set(); + + clientStatsSet.Update(attackerStats); + clientStatsSet.Update(victimStats); + await ctx.SaveChangesAsync(); + } } /// @@ -1079,39 +1103,43 @@ namespace IW4MAdmin.Plugins.Stats.Helpers return clientStats; } - public void InitializeServerStats(Server sv) + public EFServerStatistics InitializeServerStats(Server sv) { int serverId = sv.GetHashCode(); - var statsSvc = ContextThreads[serverId]; + EFServerStatistics serverStats; - var serverStats = statsSvc.ServerStatsSvc.Find(s => s.ServerId == serverId).FirstOrDefault(); - if (serverStats == null) + using (var ctx = new DatabaseContext(disableTracking: true)) { - Log.WriteDebug($"Initializing server stats for {sv}"); - // server stats have never been generated before - serverStats = new EFServerStatistics() + var serverStatsSet = ctx.Set(); + serverStats = serverStatsSet.FirstOrDefault(s => s.ServerId == serverId); + + if (serverStats == null) { - Active = true, - ServerId = serverId, - TotalKills = 0, - TotalPlayTime = 0, - }; + Log.WriteDebug($"Initializing server stats for {sv}"); + // server stats have never been generated before + serverStats = new EFServerStatistics() + { + ServerId = serverId, + TotalKills = 0, + TotalPlayTime = 0, + }; - var ieClientStats = statsSvc.ClientStatSvc.Find(cs => cs.ServerId == serverId); - - // set these incase we've imported settings - serverStats.TotalKills = ieClientStats.Sum(cs => cs.Kills); - serverStats.TotalPlayTime = Manager.GetClientService().GetTotalPlayTime().Result; - - statsSvc.ServerStatsSvc.Insert(serverStats); + serverStats = serverStatsSet.Add(serverStats).Entity; + ctx.SaveChanges(); + } } + + return serverStats; } public void ResetKillstreaks(int serverId) { var serverStats = Servers[serverId]; + foreach (var stat in serverStats.PlayerStats.Values) + { stat.StartNewSession(); + } } public void ResetStats(int clientId, int serverId) @@ -1131,33 +1159,30 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (clientId < 1) return; - var messageSvc = ContextThreads[serverId].MessageSvc; - messageSvc.Insert(new EFClientMessage() + using (var ctx = new DatabaseContext(disableTracking: true)) { - Active = true, - ClientId = clientId, - Message = message, - ServerId = serverId, - TimeSent = DateTime.UtcNow - }); - await messageSvc.SaveChangesAsync(); + ctx.Set().Add(new EFClientMessage() + { + ClientId = clientId, + Message = message, + ServerId = serverId, + TimeSent = DateTime.UtcNow + }); + + await ctx.SaveChangesAsync(); + } } public async Task Sync(Server sv) { int serverId = sv.GetHashCode(); - var statsSvc = ContextThreads[serverId]; - var serverSvc = statsSvc.ServerSvc; - serverSvc.Update(Servers[serverId].Server); - await serverSvc.SaveChangesAsync(); - - await statsSvc.KillStatsSvc.SaveChangesAsync(); - await statsSvc.ServerSvc.SaveChangesAsync(); - - statsSvc = null; - // this should prevent the gunk from having a long lasting context. - ContextThreads[serverId] = new ThreadSafeStatsService(); + using (var ctx = new DatabaseContext(disableTracking: true)) + { + var serverSet = ctx.Set(); + serverSet.Update(Servers[serverId].Server); + await ctx.SaveChangesAsync(); + } } public void SetTeamBased(int serverId, bool isTeamBased) diff --git a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs b/Plugins/Stats/Helpers/ThreadSafeStatsService.cs deleted file mode 100644 index 64cfb007..00000000 --- a/Plugins/Stats/Helpers/ThreadSafeStatsService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using SharedLibraryCore.Services; -using IW4MAdmin.Plugins.Stats.Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace IW4MAdmin.Plugins.Stats.Helpers -{ - public class ThreadSafeStatsService - { - public GenericRepository ClientStatSvc - { - get - { - return new GenericRepository(false); - } - } - public GenericRepository ServerSvc - { - get - { - return new GenericRepository(false); - } - } - public GenericRepository KillStatsSvc { get; private set; } - public GenericRepository ServerStatsSvc { get; private set; } - public GenericRepository MessageSvc - { - get - { - return new GenericRepository(); - } - } - - public ThreadSafeStatsService() - { - KillStatsSvc = new GenericRepository(); - ServerStatsSvc = new GenericRepository(); - } - } -} diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index 88132ad6..929904e4 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -13,6 +13,8 @@ using SharedLibraryCore.Services; using IW4MAdmin.Plugins.Stats.Config; using IW4MAdmin.Plugins.Stats.Helpers; using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore.Database; +using Microsoft.EntityFrameworkCore; namespace IW4MAdmin.Plugins.Stats { @@ -122,8 +124,11 @@ namespace IW4MAdmin.Plugins.Stats // meta data info async Task> getStats(int clientId) { - var statsSvc = new GenericRepository(); - var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId); + IList clientStats; + using (var ctx = new DatabaseContext(disableTracking: true)) + { + clientStats = await ctx.Set().Where(c => c.ClientId == clientId).ToListAsync(); + } int kills = clientStats.Sum(c => c.Kills); int deaths = clientStats.Sum(c => c.Deaths); @@ -170,8 +175,14 @@ namespace IW4MAdmin.Plugins.Stats async Task> getAnticheatInfo(int clientId) { - var statsSvc = new GenericRepository(); - var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId); + IList clientStats; + using (var ctx = new DatabaseContext(disableTracking: true)) + { + clientStats = await ctx.Set() + .Include(c => c.HitLocations) + .Where(c => c.ClientId == clientId) + .ToListAsync(); + } double headRatio = 0; double chestRatio = 0; @@ -246,19 +257,24 @@ namespace IW4MAdmin.Plugins.Stats async Task> getMessages(int clientId) { - var messageSvc = new GenericRepository(); - var messages = await messageSvc.FindAsync(m => m.ClientId == clientId); - var messageMeta = messages.Select(m => new ProfileMeta() + List messageMeta; + using (var ctx = new DatabaseContext(disableTracking: true)) { - Key = "EventMessage", - Value = m.Message, - When = m.TimeSent, - Extra = m.ServerId.ToString() - }).ToList(); + var messages = ctx.Set().Where(m => m.ClientId == clientId); + + messageMeta = await messages.Select(m => new ProfileMeta() + { + Key = "EventMessage", + Value = m.Message, + When = m.TimeSent, + Extra = m.ServerId.ToString() + }).ToListAsync(); + } + messageMeta.Add(new ProfileMeta() { Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"], - Value = messages.Count + Value = messageMeta.Count }); return messageMeta; @@ -275,16 +291,20 @@ namespace IW4MAdmin.Plugins.Stats string totalKills(Server server) { - var serverStats = new GenericRepository(); - return serverStats.Find(s => s.Active) - .Sum(c => c.TotalKills).ToString("#,##0"); + using (var ctx = new DatabaseContext(disableTracking: true)) + { + long kills = ctx.Set().Where(s => s.Active).Sum(s => s.TotalKills); + return kills.ToString("#,##0"); + } } string totalPlayTime(Server server) { - var serverStats = new GenericRepository(); - return Math.Ceiling((serverStats.GetQuery(s => s.Active) - .Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0"); + using (var ctx = new DatabaseContext(disableTracking: true)) + { + long playTime = ctx.Set().Where(s => s.Active).Sum(s => s.TotalPlayTime); + return (playTime / 3600.0).ToString("#,##0"); + } } string topStats(Server s) diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index d7a61c4c..9a41b152 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 RaidMax.IW4MAdmin.Plugins.Stats @@ -26,7 +27,7 @@ - + diff --git a/Plugins/Tests/Tests.csproj b/Plugins/Tests/Tests.csproj index 254d6097..2d1e768b 100644 --- a/Plugins/Tests/Tests.csproj +++ b/Plugins/Tests/Tests.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 @@ -22,7 +23,7 @@ - + diff --git a/Plugins/Welcome/Plugin.cs b/Plugins/Welcome/Plugin.cs index 91119d61..e94de5dd 100644 --- a/Plugins/Welcome/Plugin.cs +++ b/Plugins/Welcome/Plugin.cs @@ -8,6 +8,8 @@ using SharedLibraryCore.Configuration; using SharedLibraryCore.Services; using SharedLibraryCore.Database.Models; using System.Linq; +using SharedLibraryCore.Database; +using Microsoft.EntityFrameworkCore; namespace IW4MAdmin.Plugins.Welcome { @@ -88,8 +90,18 @@ namespace IW4MAdmin.Plugins.Welcome if (newPlayer.Level == Player.Permission.Flagged) { - var penalty = await new GenericRepository().FindAsync(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag); - E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penalty.FirstOrDefault()?.Offense}) has joined!"); + string penaltyReason; + + using (var ctx = new DatabaseContext(disableTracking: true)) + { + penaltyReason = await ctx.Penalties + .Where(p => p.OffenderId == newPlayer.ClientId && p.Type == Penalty.PenaltyType.Flag) + .OrderByDescending(p => p.When) + .Select(p => p.AutomatedOffense ?? p.Offense) + .FirstOrDefaultAsync(); + } + + E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!"); } else E.Owner.Broadcast(ProcessAnnouncement(Config.Configuration().UserAnnouncementMessage, newPlayer)); diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index 532a4b3c..808c406b 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 RaidMax.IW4MAdmin.Plugins.Welcome @@ -25,7 +26,7 @@ - + diff --git a/SharedLibraryCore/ScriptPlugin.cs b/SharedLibraryCore/ScriptPlugin.cs index 3906a859..ad0f0d9b 100644 --- a/SharedLibraryCore/ScriptPlugin.cs +++ b/SharedLibraryCore/ScriptPlugin.cs @@ -68,7 +68,6 @@ namespace SharedLibraryCore ScriptEngine.Execute(script); ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization); - ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient); dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject(); this.Author = pluginObject.author; @@ -87,6 +86,7 @@ namespace SharedLibraryCore { ScriptEngine.SetValue("_gameEvent", E); ScriptEngine.SetValue("_server", S); + ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S)); return Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue()); } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 8dd2bcc7..0488bd7b 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -33,6 +33,7 @@ namespace SharedLibraryCore Port = config.Port; Manager = mgr; Logger = Manager.GetLogger(this.GetHashCode()); + Logger.WriteInfo(this.ToString()); ServerConfig = config; RemoteConnection = new RCon.Connection(IP, Port, Password, Logger); @@ -263,7 +264,7 @@ namespace SharedLibraryCore public override string ToString() { - return $"{IP}_{Port}"; + return $"{IP}-{Port}"; } protected async Task ScriptLoaded() diff --git a/SharedLibraryCore/Services/GenericRepository.cs b/SharedLibraryCore/Services/GenericRepository.cs deleted file mode 100644 index 0fe27248..00000000 --- a/SharedLibraryCore/Services/GenericRepository.cs +++ /dev/null @@ -1,154 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SharedLibraryCore.Database; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; - -namespace SharedLibraryCore.Services -{ - // https://stackoverflow.com/questions/43677906/crud-operations-with-entityframework-using-generic-type - public class GenericRepository where TEntity : class - { - private DatabaseContext _context; - private DbSet _dbSet; - private readonly bool ShouldTrack; - - public GenericRepository(bool shouldTrack) - { - this.ShouldTrack = shouldTrack; - } - - public GenericRepository() { } - - protected DbContext Context - { - get - { - if (_context == null) - { - _context = new DatabaseContext(!ShouldTrack); - } - - return _context; - } - } - - protected DbSet DBSet - { - get - { - if (_dbSet == null) - { - _dbSet = this.Context.Set(); - } - - return _dbSet; - } - } - - public virtual async Task> FindAsync(Expression> predicate, Func, IOrderedQueryable> orderExpression = null) - { - return await this.GetQuery(predicate, orderExpression).ToListAsync(); - } - - public virtual IEnumerable Find(Expression> predicate, Func, IOrderedQueryable> orderExpression = null) - { - return this.GetQuery(predicate, orderExpression).AsEnumerable(); - } - - public virtual IQueryable GetQuery(Expression> predicate = null, Func, IOrderedQueryable> orderExpression = null) - { - IQueryable qry = this.DBSet; - - foreach (var property in this.Context.Model.FindEntityType(typeof(TEntity)).GetNavigations()) - qry = qry.Include(property.Name); - - - if (predicate != null) - qry = qry.Where(predicate); - - if (orderExpression != null) - return orderExpression(qry); - - - return qry; - } - - public virtual void Insert(T entity) where T : class - { - DbSet dbSet = this.Context.Set(); - dbSet.Add(entity); - } - - public virtual TEntity Insert(TEntity entity) - { - return DBSet.Add(entity).Entity; - } - - public virtual void Update(T entity) where T : class - { - DbSet dbSet = this.Context.Set(); - dbSet.Attach(entity); - this.Context.Entry(entity).State = EntityState.Modified; - } - - public virtual void Update(TEntity entity) - { - this.Attach(entity); - this.Context.Entry(entity).State = EntityState.Modified; - } - - public virtual void Delete(T entity) where T : class - { - DbSet dbSet = this.Context.Set(); - - if (this.Context.Entry(entity).State == EntityState.Detached) - dbSet.Attach(entity); - - dbSet.Remove(entity); - } - - public virtual void Delete(TEntity entity) - { - if (this.Context.Entry(entity).State == EntityState.Detached) - this.Attach(entity); - - this.DBSet.Remove(entity); - - } - - public virtual void Delete(object[] id) where T : class - { - DbSet dbSet = this.Context.Set(); - T entity = dbSet.Find(id); - dbSet.Attach(entity); - dbSet.Remove(entity); - } - - public virtual void Delete(object id) - { - TEntity entity = this.DBSet.Find(id); - this.Delete(entity); - } - - - public virtual void Attach(TEntity entity) - { - if (this.Context.Entry(entity).State == EntityState.Detached) - this.DBSet.Attach(entity); - } - - public virtual void SaveChanges() - { - this.Context.SaveChanges(); - } - - public virtual Task SaveChangesAsync() - { - return this.Context.SaveChangesAsync(); - } - - } -} diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 489c781d..8613ceda 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -3,6 +3,7 @@ Library netcoreapp2.1 + 2.1.5 RaidMax.IW4MAdmin.SharedLibraryCore @@ -12,15 +13,6 @@ Debug;Release;Prerelease - - - - - - - - - @@ -39,7 +31,7 @@ - + diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 82ce9ad0..4e86d8e1 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -27,7 +27,7 @@ namespace SharedLibraryCore #endif public static Encoding EncodingType; public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary()); - public static Player IW4MAdminClient = new Player() { ClientId = 1, Level = Player.Permission.Console }; + public static Player IW4MAdminClient(Server server = null) => new Player() { ClientId = 1, Level = Player.Permission.Console, CurrentServer = server }; public static string HttpRequest(string location, string header, string headerValue) { diff --git a/WebfrontCore/Controllers/PenaltyController.cs b/WebfrontCore/Controllers/PenaltyController.cs index b996cdb2..00850d79 100644 --- a/WebfrontCore/Controllers/PenaltyController.cs +++ b/WebfrontCore/Controllers/PenaltyController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using SharedLibraryCore; +using SharedLibraryCore.Database; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; using SharedLibraryCore.Services; @@ -33,8 +35,14 @@ namespace WebfrontCore.Controllers public async Task PublicAsync() { - var penalties = await (new GenericRepository()) - .FindAsync(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active); + IList penalties; + + using (var ctx = new DatabaseContext(disableTracking: true)) + { + penalties = await ctx.Penalties + .Where(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active) + .ToListAsync(); + } var penaltiesDto = penalties.Select(p => new PenaltyInfo() { diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index cb364175..bf92a126 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -2,6 +2,7 @@ netcoreapp2.1 + 2.1.5 false false true @@ -63,7 +64,7 @@ - +