update project to .net core 2.1.5

got rid of "threadsafe" stats service in stats plugin
This commit is contained in:
RaidMax 2018-10-07 21:34:30 -05:00
parent c8366a22e5
commit b289917319
24 changed files with 364 additions and 455 deletions

View File

@ -3,9 +3,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.1.9.4</Version>
<Version>2.1.9.5</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
@ -30,8 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.1.9.4</AssemblyVersion>
<FileVersion>2.1.9.4</FileVersion>
<AssemblyVersion>2.1.9.5</AssemblyVersion>
<FileVersion>2.1.9.5</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -78,7 +79,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -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();
}
/// <summary>
/// rotates logs when log is initialized
/// </summary>
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);
}

View File

@ -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,

View File

@ -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;
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
@ -17,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
</Project>

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
@ -21,7 +22,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
@ -19,7 +20,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App"/>
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -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);
}
},

View File

@ -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<EFClientStatistics>();
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<EFClientStatistics>()
.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"]);
}

View File

@ -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<EFClientStatistics>();
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<EFClientStatistics>().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<EFClientStatistics>().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())

View File

@ -24,20 +24,20 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public class StatManager
{
private ConcurrentDictionary<int, ServerStats> Servers;
private ConcurrentDictionary<int, ThreadSafeStatsService> 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<int, ServerStats>();
ContextThreads = new ConcurrentDictionary<int, ThreadSafeStatsService>();
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
/// <param name="sv"></param>
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<EFServer>();
// 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
/// <returns>EFClientStatistic of specified player</returns>
public async Task<EFClientStatistics> 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<IW4Info.HitLocation>().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<IW4Info.HitLocation>()
.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<EFClientStatistics>();
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<IW4Info.HitLocation>().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<IW4Info.HitLocation>()
.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;
}
/// <summary>
@ -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<EFClientStatistics>().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<EFClientStatistics>();
clientStatsSet.Update(attackerStats);
clientStatsSet.Update(victimStats);
await ctx.SaveChangesAsync();
}
}
/// <summary>
@ -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<EFServerStatistics>();
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<EFClientMessage>().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<EFServer>();
serverSet.Update(Servers[serverId].Server);
await ctx.SaveChangesAsync();
}
}
public void SetTeamBased(int serverId, bool isTeamBased)

View File

@ -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<EFClientStatistics> ClientStatSvc
{
get
{
return new GenericRepository<EFClientStatistics>(false);
}
}
public GenericRepository<EFServer> ServerSvc
{
get
{
return new GenericRepository<EFServer>(false);
}
}
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
public GenericRepository<EFClientMessage> MessageSvc
{
get
{
return new GenericRepository<EFClientMessage>();
}
}
public ThreadSafeStatsService()
{
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
}
}
}

View File

@ -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<List<ProfileMeta>> getStats(int clientId)
{
var statsSvc = new GenericRepository<EFClientStatistics>();
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>().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<List<ProfileMeta>> getAnticheatInfo(int clientId)
{
var statsSvc = new GenericRepository<EFClientStatistics>();
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
.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<List<ProfileMeta>> getMessages(int clientId)
{
var messageSvc = new GenericRepository<EFClientMessage>();
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId);
var messageMeta = messages.Select(m => new ProfileMeta()
List<ProfileMeta> 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<EFClientMessage>().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<EFServerStatistics>();
return serverStats.Find(s => s.Active)
.Sum(c => c.TotalKills).ToString("#,##0");
using (var ctx = new DatabaseContext(disableTracking: true))
{
long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills);
return kills.ToString("#,##0");
}
}
string totalPlayTime(Server server)
{
var serverStats = new GenericRepository<EFServerStatistics>();
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<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0");
}
}
string topStats(Server s)

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Stats</PackageId>
@ -26,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
@ -22,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
</Project>

View File

@ -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<EFPenalty>().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));

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.Plugins.Welcome</PackageId>
@ -25,7 +26,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -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());
}
}

View File

@ -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<bool> ScriptLoaded()

View File

@ -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<TEntity> where TEntity : class
{
private DatabaseContext _context;
private DbSet<TEntity> _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<TEntity> DBSet
{
get
{
if (_dbSet == null)
{
_dbSet = this.Context.Set<TEntity>();
}
return _dbSet;
}
}
public virtual async Task<IList<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return await this.GetQuery(predicate, orderExpression).ToListAsync();
}
public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return this.GetQuery(predicate, orderExpression).AsEnumerable();
}
public virtual IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
IQueryable<TEntity> 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>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Add(entity);
}
public virtual TEntity Insert(TEntity entity)
{
return DBSet.Add(entity).Entity;
}
public virtual void Update<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
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>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
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<T>(object[] id) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
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();
}
}
}

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
@ -12,15 +13,6 @@
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Migrations\20180502195240_Update.cs" />
<Compile Remove="Migrations\20180923025324_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923025702_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030248_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030426_FixForPostgreSQL.cs" />
<Compile Remove="Migrations\20180923030528_FixForPostgreSQL.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.4" />
@ -39,7 +31,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -27,7 +27,7 @@ namespace SharedLibraryCore
#endif
public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>());
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)
{

View File

@ -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<IActionResult> PublicAsync()
{
var penalties = await (new GenericRepository<EFPenalty>())
.FindAsync(p => p.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.Ban && p.Active);
IList<EFPenalty> 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()
{

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<RazorCompileOnPublish>false</RazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext>
@ -63,7 +64,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
<PackageReference Update="Microsoft.NETCore.App" />
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
<PackageReference Update="Microsoft.AspNetCore" Version="2.1.2" />
</ItemGroup>