fix alias command sending message to origin instead of target

(hopefully) fix an issue with banned players causing exception if they create events before they are kicked out
fix issues with sometimes wrong error message for timeout
show most recent IP address at top of alias list
optimization to some sql queries
This commit is contained in:
RaidMax 2019-11-15 14:50:20 -06:00
parent cb9119ac58
commit 161b27e2f2
31 changed files with 1553 additions and 279 deletions

View File

@ -33,8 +33,6 @@ namespace IW4MAdmin.Application
public ILogger Logger => GetLogger(0);
public bool Running { get; private set; }
public bool IsInitialized { get; private set; }
// expose the event handler so we can execute the events
public OnServerEventEventHandler OnServerEvent { get; set; }
public DateTime StartTime { get; private set; }
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
@ -73,21 +71,19 @@ namespace IW4MAdmin.Application
PageList = new PageList();
AdditionalEventParsers = new List<IEventParser>();
AdditionalRConParsers = new List<IRConParser>();
OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent;
//OnServerEvent += OnGameEvent;
//OnServerEvent += EventApi.OnGameEvent;
TokenAuthenticator = new TokenAuthentication();
_metaService = new MetaService();
_tokenSource = new CancellationTokenSource();
}
private async void OnGameEvent(object sender, GameEventArgs args)
public async Task ExecuteEvent(GameEvent newEvent)
{
#if DEBUG == true
Logger.WriteDebug($"Entering event process for {args.Event.Id}");
Logger.WriteDebug($"Entering event process for {newEvent.Id}");
#endif
var newEvent = args.Event;
// the event has failed already
if (newEvent.Failed)
{
@ -96,12 +92,11 @@ namespace IW4MAdmin.Application
try
{
await newEvent.Owner.EventProcessing.WaitAsync(CancellationToken);
await newEvent.Owner.ExecuteEvent(newEvent);
// save the event info to the database
var changeHistorySvc = new ChangeHistoryService();
await changeHistorySvc.Add(args.Event);
await changeHistorySvc.Add(newEvent);
#if DEBUG
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
@ -145,22 +140,12 @@ namespace IW4MAdmin.Application
Logger.WriteDebug(ex.GetExceptionInfo());
}
finally
{
if (newEvent.Owner.EventProcessing.CurrentCount == 0)
{
newEvent.Owner.EventProcessing.Release(1);
}
#if DEBUG == true
Logger.WriteDebug($"Exiting event process for {args.Event.Id}");
#endif
}
skip:
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
newEvent.Complete();
#if DEBUG == true
Logger.WriteDebug($"Exiting event process for {newEvent.Id}");
#endif
}
public IList<Server> GetServers()

View File

@ -256,6 +256,7 @@ namespace IW4MAdmin.Application.EventParsers
// this is a custom event printed out by _customcallbacks.gsc (used for anticheat)
if (eventType == "ScriptKill")
{
long originId = lineSplit[1].ConvertGuidToLong(1);
long targetId = lineSplit[2].ConvertGuidToLong(1);

View File

@ -1,6 +1,8 @@
using SharedLibraryCore;
using IW4MAdmin.Application.Misc;
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Linq;
using System.Threading;
@ -9,7 +11,11 @@ namespace IW4MAdmin.Application
class GameEventHandler : IEventHandler
{
readonly ApplicationManager Manager;
private static GameEvent.EventType[] overrideEvents = new[]
private readonly EventProfiler _profiler;
private delegate void GameEventAddedEventHandler(object sender, GameEventArgs args);
private event GameEventAddedEventHandler GameEventAdded;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
GameEvent.EventType.Disconnect,
@ -20,6 +26,17 @@ namespace IW4MAdmin.Application
public GameEventHandler(IManager mgr)
{
Manager = (ApplicationManager)mgr;
_profiler = new EventProfiler(mgr.GetLogger(0));
GameEventAdded += GameEventHandler_GameEventAdded;
}
private async void GameEventHandler_GameEventAdded(object sender, GameEventArgs args)
{
var start = DateTime.Now;
await Manager.ExecuteEvent(args.Event);
#if DEBUG
_profiler.Profile(start, DateTime.Now, args.Event);
#endif
}
public void AddEvent(GameEvent gameEvent)
@ -35,7 +52,7 @@ namespace IW4MAdmin.Application
#if DEBUG
gameEvent.Owner.Logger.WriteDebug($"Adding event with id {gameEvent.Id}");
#endif
Manager.OnServerEvent?.Invoke(gameEvent.Owner, new GameEventArgs(null, false, gameEvent));
GameEventAdded?.Invoke(this, new GameEventArgs(null, false, gameEvent));
}
#if DEBUG
else

View File

@ -76,7 +76,6 @@ namespace IW4MAdmin.Application.IO
#if DEBUG
_server.Logger.WriteVerbose(gameEvent.Data);
#endif
// we don't want to add the event if ignoreBots is on and the event comes from a bot
if (!_ignoreBots || (_ignoreBots && !((gameEvent.Origin?.IsBot ?? false) || (gameEvent.Target?.IsBot ?? false))))
{
@ -103,11 +102,6 @@ namespace IW4MAdmin.Application.IO
}
_server.Manager.GetEventHandler().AddEvent(gameEvent);
if (gameEvent.IsBlocking)
{
await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, _server.Manager.CancellationToken);
}
}
}

View File

@ -33,7 +33,7 @@ namespace IW4MAdmin
{
}
override public async Task OnClientConnected(EFClient clientFromLog)
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
{
Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved");
@ -57,6 +57,7 @@ namespace IW4MAdmin
Logger.WriteInfo($"Client {client} connected...");
// Do the player specific stuff
client.ProcessingEvent = clientFromLog.ProcessingEvent;
client.ClientNumber = clientFromLog.ClientNumber;
client.Score = clientFromLog.Score;
client.Ping = clientFromLog.Ping;
@ -73,9 +74,8 @@ namespace IW4MAdmin
Type = GameEvent.EventType.Connect
};
await client.OnJoin(client.IPAddress);
client.State = ClientState.Connected;
Manager.GetEventHandler().AddEvent(e);
return client;
}
override public async Task OnClientDisconnected(EFClient client)
@ -103,55 +103,85 @@ namespace IW4MAdmin
public override async Task ExecuteEvent(GameEvent E)
{
bool canExecuteCommand = true;
if (!await ProcessEvent(E))
if (E == null)
{
Logger.WriteError("Received NULL event");
return;
}
Command C = null;
if (E.Type == GameEvent.EventType.Command)
if (E.IsBlocking)
{
try
await E.Origin?.Lock();
}
bool canExecuteCommand = true;
Exception lastException = null;
try
{
if (!await ProcessEvent(E))
{
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E);
return;
}
catch (CommandException e)
Command C = null;
if (E.Type == GameEvent.EventType.Command)
{
Logger.WriteInfo(e.Message);
try
{
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E);
}
catch (CommandException e)
{
Logger.WriteInfo(e.Message);
}
if (C != null)
{
E.Extra = C;
}
}
if (C != null)
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{
E.Extra = C;
try
{
await plugin.OnEventAsync(E, this);
}
catch (AuthorizationException e)
{
E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
canExecuteCommand = false;
}
catch (Exception Except)
{
Logger.WriteError($"{loc["SERVER_PLUGIN_ERROR"]} [{plugin.Name}]");
Logger.WriteDebug(Except.GetExceptionInfo());
}
}
// hack: this prevents commands from getting executing that 'shouldn't' be
if (E.Type == GameEvent.EventType.Command && E.Extra is Command command &&
(canExecuteCommand || E.Origin?.Level == Permission.Console))
{
await command.ExecuteAsync(E);
}
}
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
catch (Exception e)
{
try
{
await plugin.OnEventAsync(E, this);
}
catch (AuthorizationException e)
{
E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
canExecuteCommand = false;
}
catch (Exception Except)
{
Logger.WriteError($"{loc["SERVER_PLUGIN_ERROR"]} [{plugin.Name}]");
Logger.WriteDebug(Except.GetExceptionInfo());
}
lastException = e;
}
// hack: this prevents commands from getting executing that 'shouldn't' be
if (E.Type == GameEvent.EventType.Command && E.Extra is Command command &&
(canExecuteCommand || E.Origin?.Level == Permission.Console))
finally
{
await command.ExecuteAsync(E);
E.Origin?.Unlock();
if (lastException != null)
{
throw lastException;
}
}
}
@ -195,6 +225,25 @@ namespace IW4MAdmin
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.Connect)
{
if (E.Origin.State != ClientState.Connected)
{
E.Origin.State = ClientState.Connected;
E.Origin.LastConnection = DateTime.UtcNow;
E.Origin.Connections += 1;
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
await E.Origin.OnJoin(E.Origin.IPAddress);
}
}
else if (E.Type == GameEvent.EventType.PreConnect)
{
// we don't want to track bots in the database at all if ignore bots is requested
@ -230,7 +279,8 @@ namespace IW4MAdmin
Clients[E.Origin.ClientNumber] = E.Origin;
try
{
await OnClientConnected(E.Origin);
E.Origin = await OnClientConnected(E.Origin);
E.Target = E.Origin;
}
catch (Exception ex)
@ -242,13 +292,6 @@ namespace IW4MAdmin
return false;
}
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
if (E.Origin.Level > EFClient.Permission.Moderator)
{
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
@ -624,7 +667,6 @@ namespace IW4MAdmin
#endif
var polledClients = await PollPlayersAsync();
var waiterList = new List<GameEvent>();
foreach (var disconnectingClient in polledClients[1])
{
@ -641,18 +683,9 @@ namespace IW4MAdmin
};
Manager.GetEventHandler().AddEvent(e);
// wait until the disconnect event is complete
// because we don't want to try to fill up a slot that's not empty yet
waiterList.Add(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
// wait for all the disconnect tasks to finish
foreach (var waiter in waiterList)
{
waiter.Wait();
}
waiterList.Clear();
// this are our new connecting clients
foreach (var client in polledClients[0])
{
@ -671,16 +704,9 @@ namespace IW4MAdmin
};
Manager.GetEventHandler().AddEvent(e);
waiterList.Add(e);
await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
// wait for all the connect tasks to finish
foreach (var waiter in waiterList)
{
waiter.Wait();
}
waiterList.Clear();
// these are the clients that have updated
foreach (var client in polledClients[2])
{
@ -692,12 +718,6 @@ namespace IW4MAdmin
};
Manager.GetEventHandler().AddEvent(e);
waiterList.Add(e);
}
foreach (var waiter in waiterList)
{
waiter.Wait();
}
if (ConnectionErrors > 0)

View File

@ -0,0 +1,63 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
namespace IW4MAdmin.Application.Misc
{
internal class EventPerformance
{
public long ExecutionTime { get; set; }
public GameEvent Event { get; set; }
public string EventInfo => $"{Event.Type}, {Event.FailReason}, {Event.IsBlocking}, {Event.Data}, {Event.Message}, {Event.Extra}";
}
public class DuplicateKeyComparer<TKey> : IComparer<TKey> where TKey : IComparable
{
public int Compare(TKey x, TKey y)
{
int result = x.CompareTo(y);
if (result == 0)
return 1;
else
return result;
}
}
internal class EventProfiler
{
public double AverageEventTime { get; private set; }
public double MaxEventTime => Events.Values.Last().ExecutionTime;
public double MinEventTime => Events.Values[0].ExecutionTime;
public int TotalEventCount => Events.Count;
public SortedList<long, EventPerformance> Events { get; private set; } = new SortedList<long, EventPerformance>(new DuplicateKeyComparer<long>());
private readonly ILogger _logger;
public EventProfiler(ILogger logger)
{
_logger = logger;
}
public void Profile(DateTime start, DateTime end, GameEvent gameEvent)
{
_logger.WriteDebug($"Starting profile of event {gameEvent.Id}");
long executionTime = (long)Math.Round((end - start).TotalMilliseconds);
var perf = new EventPerformance()
{
Event = gameEvent,
ExecutionTime = executionTime
};
lock (Events)
{
Events.Add(executionTime, perf);
}
AverageEventTime = (AverageEventTime * (TotalEventCount - 1) + executionTime) / TotalEventCount;
_logger.WriteDebug($"Finished profile of event {gameEvent.Id}");
}
}
}

View File

@ -3,7 +3,6 @@ using SharedLibraryCore.Interfaces;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
@ -20,16 +19,21 @@ namespace IW4MAdmin.Application
}
readonly string FileName;
readonly SemaphoreSlim OnLogWriting;
readonly ReaderWriterLockSlim WritingLock;
static readonly short MAX_LOG_FILES = 10;
public Logger(string fn)
{
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
OnLogWriting = new SemaphoreSlim(1, 1);
WritingLock = new ReaderWriterLockSlim();
RotateLogs();
}
~Logger()
{
WritingLock.Dispose();
}
/// <summary>
/// rotates logs when log is initialized
/// </summary>
@ -56,7 +60,7 @@ namespace IW4MAdmin.Application
void Write(string msg, LogType type)
{
OnLogWriting.Wait();
WritingLock.EnterWriteLock();
string stringType = type.ToString();
msg = msg.StripColors();
@ -74,7 +78,7 @@ namespace IW4MAdmin.Application
#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}");
//File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
//Debug.WriteLine(msg);
#else
if (type == LogType.Error || type == LogType.Verbose)
@ -91,7 +95,7 @@ namespace IW4MAdmin.Application
Console.WriteLine(ex.GetExceptionInfo());
}
OnLogWriting.Release(1);
WritingLock.ExitWriteLock();
}
public void WriteVerbose(string msg)

View File

@ -38,7 +38,7 @@ namespace IW4MAdmin.Application.RconParsers
},
};
Configuration.Status.Pattern = @"^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
Configuration.Status.Pattern = @"^ *([0-9]+) +-?([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback|unknown) +(-*[0-9]+) +([0-9]+) *$";
Configuration.Status.AddMapping(ParserRegex.GroupType.RConClientNumber, 1);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConScore, 2);
Configuration.Status.AddMapping(ParserRegex.GroupType.RConPing, 3);

View File

@ -9,4 +9,4 @@ pip==10.0.1
pytz==2018.9
setuptools==39.0.1
six==1.12.0
Werkzeug==0.15.2
Werkzeug==0.16.0

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace IW4MAdmin.Plugins.Stats.Helpers
{
@ -16,6 +17,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public EFServer Server { get; private set; }
private readonly Server _server;
public bool IsTeamBased { get; set; }
public SemaphoreSlim OnSaving { get; private set; }
public ServerStats(EFServer sv, EFServerStatistics st, Server server)
{
@ -23,6 +25,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerStatistics = st;
Server = sv;
_server = server;
OnSaving = new SemaphoreSlim(1, 1);
}
~ServerStats()
{
OnSaving.Dispose();
}
public int TeamCount(IW4Info.Team teamName)

View File

@ -410,6 +410,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Vector3 vDeathOrigin = null;
Vector3 vKillOrigin = null;
Vector3 vViewAngles = null;
var snapshotAngles = new List<Vector3>();
SemaphoreSlim waiter = null;
try
@ -419,20 +420,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
vDeathOrigin = Vector3.Parse(deathOrigin);
vKillOrigin = Vector3.Parse(killOrigin);
vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles();
}
catch (FormatException)
{
_log.WriteWarning("Could not parse kill or death origin or viewangle vectors");
_log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}");
await AddStandardKill(attacker, victim);
return;
}
var snapshotAngles = new List<Vector3>();
try
{
foreach (string angle in snapAngles.Split(':', StringSplitOptions.RemoveEmptyEntries))
{
snapshotAngles.Add(Vector3.Parse(angle).FixIW4Angles());
@ -441,7 +429,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
catch (FormatException)
{
_log.WriteWarning("Could not parse snapshot angles");
_log.WriteError("Could not parse vector data from hit");
_log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles} Snapshot - {string.Join(",", snapshotAngles.Select(_a => _a.ToString()))}");
return;
}
@ -478,7 +467,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var clientStats = attacker.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
waiter = clientStats.ProcessingHit;
await waiter.WaitAsync();
await waiter.WaitAsync(Utilities.DefaultCommandTimeout, Plugin.ServerManager.CancellationToken);
// increment their hit count
if (hit.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
@ -495,12 +484,31 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (Plugin.Config.Configuration().StoreClientKills)
{
var cache = _servers[serverId].HitCache;
cache.Add(hit);
if (cache.Count > MAX_CACHED_HITS)
var serverWaiter = _servers[serverId].OnSaving;
try
{
await SaveHitCache(serverId);
await serverWaiter.WaitAsync();
var cache = _servers[serverId].HitCache;
cache.Add(hit);
if (cache.Count > MAX_CACHED_HITS)
{
await SaveHitCache(serverId);
}
}
catch (Exception e)
{
_log.WriteError("Could not store client kills");
_log.WriteDebug(e.GetExceptionInfo());
}
finally
{
if (serverWaiter.CurrentCount == 0)
{
serverWaiter.Release(1);
}
}
}
@ -538,9 +546,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
}
}
#if DEBUG
await Sync(attacker.CurrentServer);
#endif
}
catch (Exception ex)
@ -552,7 +557,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
finally
{
waiter?.Release(1);
if (waiter?.CurrentCount == 0)
{
waiter.Release();
}
}
}
@ -561,7 +569,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
using (var ctx = new DatabaseContext(true))
{
var server = _servers[serverId];
ctx.AddRange(server.HitCache);
ctx.AddRange(server.HitCache.ToList());
await ctx.SaveChangesAsync();
server.HitCache.Clear();
}
@ -1110,21 +1118,41 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
long serverId = GetIdForServer(sv);
using (var ctx = new DatabaseContext())
var waiter = _servers[serverId].OnSaving;
try
{
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(_servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync();
await waiter.WaitAsync();
using (var ctx = new DatabaseContext())
{
var serverStatsSet = ctx.Set<EFServerStatistics>();
serverStatsSet.Update(_servers[serverId].ServerStatistics);
await ctx.SaveChangesAsync();
}
foreach (var stats in sv.GetClientsAsList()
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
.Where(_stats => _stats != null))
{
await SaveClientStats(stats);
}
await SaveHitCache(serverId);
}
foreach (var stats in sv.GetClientsAsList()
.Select(_client => _client.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY))
.Where(_stats => _stats != null))
catch (Exception e)
{
await SaveClientStats(stats);
_log.WriteError("There was a probably syncing server stats");
_log.WriteDebug(e.GetExceptionInfo());
}
await SaveHitCache(serverId);
finally
{
if (waiter.CurrentCount == 0)
{
waiter.Release(1);
}
}
}
public void SetTeamBased(long serverId, bool isTeamBased)

View File

@ -43,7 +43,7 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.Stop:
break;
case GameEvent.EventType.Connect:
case GameEvent.EventType.PreConnect:
await Manager.AddPlayer(E.Origin);
break;
case GameEvent.EventType.Disconnect:

View File

@ -73,7 +73,7 @@ namespace Tests
};
_manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
e.Complete();
e = new GameEvent()
{
@ -92,7 +92,7 @@ namespace Tests
};
_manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
e.Complete();
e = new GameEvent()
{
@ -111,7 +111,7 @@ namespace Tests
};
_manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
e.Complete();
}
@ -126,13 +126,13 @@ namespace Tests
Thread.Sleep(100);
}
_manager.OnServerEvent += (sender, eventArgs) =>
{
if (eventArgs.Event.Type == GameEvent.EventType.Connect)
{
onJoined.Set();
}
};
//_manager.OnServerEvent += (sender, eventArgs) =>
//{
// if (eventArgs.Event.Type == GameEvent.EventType.Connect)
// {
// onJoined.Set();
// }
//};
server.EmulateClientJoinLog();
onJoined.Wait();
@ -140,25 +140,25 @@ namespace Tests
var client = server.Clients[0];
var warnEvent = client.Warn("test warn", Utilities.IW4MAdminClient(server));
warnEvent.OnProcessed.Wait(5000);
warnEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.False(warnEvent.Failed);
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(5000);
warnEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.True(warnEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was applied without proper permissions");
// warn clear
var warnClearEvent = client.WarnClear(new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnClearEvent.OnProcessed.Wait(5000);
warnClearEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.True(warnClearEvent.FailReason == GameEvent.EventFailReason.Permission &&
client.Warnings == 1, "warning was removed without proper permissions");
warnClearEvent = client.WarnClear(Utilities.IW4MAdminClient(server));
warnClearEvent.OnProcessed.Wait(5000);
warnClearEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.True(!warnClearEvent.Failed && client.Warnings == 0, "warning was not cleared");
}
@ -178,14 +178,14 @@ namespace Tests
var player = new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer };
player.SetAdditionalProperty("_reportCount", 3);
var reportEvent = client.Report("test report", player);
reportEvent.OnProcessed.Wait(TestTimeout);
reportEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.True(reportEvent.FailReason == GameEvent.EventFailReason.Throttle &
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 0, $"too many reports were applied [{reportEvent.FailReason.ToString()}]");
// succeed
reportEvent = client.Report("test report", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
reportEvent.OnProcessed.Wait(TestTimeout);
reportEvent.WaitAsync(new TimeSpan(0, 0, 10), new CancellationToken()).Wait();
Assert.True(!reportEvent.Failed &&
client.CurrentServer.Reports.Count(r => r.Target.NetworkId == client.NetworkId) == 1, $"report was not applied [{reportEvent.FailReason.ToString()}]");
@ -222,7 +222,7 @@ namespace Tests
Assert.False(client == null, "no client found to flag");
var flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
flagEvent.Complete();
// succeed
Assert.True(!flagEvent.Failed &&
@ -230,31 +230,31 @@ namespace Tests
Assert.False(client.ReceivedPenalties.FirstOrDefault(p => p.Offense == "test flag") == null, "flag was not applied");
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
flagEvent.Complete();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "flag was applied without permisions");
flagEvent = client.Flag("test flag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
flagEvent.OnProcessed.Wait();
flagEvent.Complete();
// fail
Assert.True(client.ReceivedPenalties.Count == 1, "duplicate flag was applied");
var unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
unflagEvent.Complete();
// fail
Assert.False(client.Level == EFClient.Permission.User, "user was unflagged without permissions");
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
unflagEvent.Complete();
// succeed
Assert.True(client.Level == EFClient.Permission.User, "user was not unflagged");
unflagEvent = client.Unflag("test unflag", new EFClient { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
unflagEvent.OnProcessed.Wait();
unflagEvent.Complete();
// succeed
Assert.True(unflagEvent.FailReason == GameEvent.EventFailReason.Invalid, "user was not flagged");
@ -272,12 +272,12 @@ namespace Tests
Assert.False(client == null, "no client found to kick");
var kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
kickEvent.Complete();
Assert.True(kickEvent.FailReason == GameEvent.EventFailReason.Permission, "client was kicked without permission");
kickEvent = client.Kick("test kick", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
kickEvent.OnProcessed.Wait();
kickEvent.Complete();
Assert.True(_manager.Servers.First().GetClientsAsList().FirstOrDefault(c => c.NetworkId == client.NetworkId) == null, "client was not kicked");
}

View File

@ -31,7 +31,7 @@ namespace Tests
};
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
e.Complete();
var client = Manager.GetServers()[0].Clients[0];
@ -44,7 +44,7 @@ namespace Tests
};
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
e.Complete();
Assert.True(client.Warnings == 1, "client wasn't warned for objectional language");
}

View File

@ -28,11 +28,11 @@ namespace Tests
var currentClientCount = server.ClientNum;
int eventsProcessed = 0;
_manager.OnServerEvent += (sender, eventArgs) =>
/*_manager.OnServerEvent += (sender, eventArgs) =>
{
if (eventArgs.Event.Type == GameEvent.EventType.Connect)
{
eventArgs.Event.OnProcessed.Wait();
eventArgs.Event.Complete();
Assert.False(eventArgs.Event.Failed, "connect event was not processed");
Assert.True(server.ClientNum == currentClientCount + 1, "client count was not incremented");
eventsProcessed++;
@ -41,13 +41,13 @@ namespace Tests
if (eventArgs.Event.Type == GameEvent.EventType.Disconnect)
{
eventArgs.Event.OnProcessed.Wait();
eventArgs.Event.Complete();
Assert.False(eventArgs.Event.Failed, "disconnect event was not processed");
Assert.True(server.ClientNum == currentClientCount, "client count was not decremented");
eventsProcessed++;
resetEvent.Set();
}
};
};*/
server.EmulateClientJoinLog();
@ -73,11 +73,11 @@ namespace Tests
int eventsProcessed = 0;
_manager.GetApplicationSettings().Configuration().RConPollRate = 5000;
_manager.OnServerEvent += (sender, eventArgs) =>
/*_manager.OnServerEvent += (sender, eventArgs) =>
{
if (eventArgs.Event.Type == GameEvent.EventType.Connect)
{
eventArgs.Event.OnProcessed.Wait();
eventArgs.Event.Complete();
Assert.False(eventArgs.Event.Failed, "connect event was not processed");
Assert.True(server.ClientNum == currentClientCount + 1, "client count was not incremented");
eventsProcessed++;
@ -86,13 +86,13 @@ namespace Tests
if (eventArgs.Event.Type == GameEvent.EventType.Disconnect)
{
eventArgs.Event.OnProcessed.Wait();
eventArgs.Event.Complete();
Assert.False(eventArgs.Event.Failed, "disconnect event was not processed");
Assert.True(server.ClientNum == currentClientCount, "client count was not decremented");
eventsProcessed++;
resetEvent.Set();
}
};
};*/
(server.RconParser as TestRconParser).FakeClientCount = 1;

View File

@ -998,7 +998,7 @@ namespace SharedLibraryCore.Commands
var names = new List<string>(E.Target.AliasLink.Children.Select(a => a.Name));
var IPs = new List<string>(E.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString()).Distinct());
E.Target.Tell($"[^3{E.Target}^7]");
E.Origin.Tell($"[^3{E.Target}^7]");
message.Append($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_ALIASES"]}: ");
message.Append(String.Join(" | ", names));

View File

@ -22,40 +22,26 @@ namespace SharedLibraryCore.Database
public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
//[Obsolete]
//private static readonly ILoggerFactory _loggerFactory = new LoggerFactory(new[] {
// new ConsoleLoggerProvider((category, level) => level == LogLevel.Information, true)
//});
static string _ConnectionString;
static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins";
private static int activeContextCount;
private static readonly ILoggerFactory _loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole()
.AddDebug()
.AddFilter((category, level) => true);
});
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt)
{
#if DEBUG == true
activeContextCount++;
//Console.WriteLine($"Initialized DB Context #{activeContextCount}");
#endif
}
public DatabaseContext()
{
#if DEBUG == true
activeContextCount++;
//Console.WriteLine($"Initialized DB Context #{activeContextCount}");
#endif
}
public override void Dispose()
{
#if DEBUG == true
//Console.WriteLine($"Disposed DB Context #{activeContextCount}");
activeContextCount--;
#endif
}
public DatabaseContext(bool disableTracking) : this()
@ -83,6 +69,9 @@ namespace SharedLibraryCore.Database
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// optionsBuilder.UseLoggerFactory(_loggerFactory)
// .EnableSensitiveDataLogging();
if (string.IsNullOrEmpty(_ConnectionString))
{
string currentPath = Utilities.OperatingDirectory;
@ -153,6 +142,7 @@ namespace SharedLibraryCore.Database
ent.HasIndex(a => a.Name);
ent.Property(_alias => _alias.SearchableName).HasMaxLength(24);
ent.HasIndex(_alias => _alias.SearchableName);
ent.HasIndex(_alias => new { _alias.Name, _alias.IPAddress }).IsUnique();
});
modelBuilder.Entity<EFMeta>(ent =>

View File

@ -8,9 +8,6 @@ namespace SharedLibraryCore
{
public class GameEvent
{
// define what the delagate function looks like
public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
public enum EventFailReason
{
/// <summary>
@ -205,11 +202,17 @@ namespace SharedLibraryCore
public GameEvent()
{
OnProcessed = new ManualResetEventSlim(false);
_eventFinishedWaiter = new ManualResetEvent(false);
Time = DateTime.UtcNow;
Id = GetNextEventId();
}
~GameEvent()
{
_eventFinishedWaiter.Set();
_eventFinishedWaiter.Dispose();
}
public EventType Type;
public EventRequiredEntity RequiredEntity { get; set; }
public string Data; // Data is usually the message sent by player
@ -219,34 +222,56 @@ namespace SharedLibraryCore
public Server Owner;
public bool IsRemote { get; set; } = false;
public object Extra { get; set; }
public ManualResetEventSlim OnProcessed { get; set; }
private readonly ManualResetEvent _eventFinishedWaiter;
public DateTime Time { get; set; }
public long Id { get; private set; }
public EventFailReason FailReason { get; set; }
public bool Failed => FailReason != EventFailReason.None;
/// <summary>
/// Indicates if the event should block until it is complete
/// </summary>
public bool IsBlocking { get; set; }
public void Complete()
{
_eventFinishedWaiter.Set();
#if DEBUG
Owner?.Logger.WriteDebug($"Completed internal for event {Id}");
#endif
}
/// <summary>
/// asynchronously wait for GameEvent to be processed
/// </summary>
/// <returns>waitable task </returns>
public Task<GameEvent> WaitAsync(TimeSpan timeSpan, CancellationToken token)
public async Task<GameEvent> WaitAsync(TimeSpan timeSpan, CancellationToken token)
{
return Task.Run(() =>
{
bool processed = OnProcessed.Wait(timeSpan, token);
// this let's us know if the the action timed out
FailReason = FailReason == EventFailReason.None & !processed ? EventFailReason.Timeout : FailReason;
return this;
});
}
bool processed = false;
public GameEvent Wait()
{
OnProcessed.Wait();
#if DEBUG
Owner?.Logger.WriteDebug($"Begin wait for event {Id}");
#endif
try
{
processed = await Task.Run(() => _eventFinishedWaiter.WaitOne(timeSpan), token);
}
catch { }
if (!processed)
{
#if DEBUG
//throw new Exception();
#endif
Owner?.Logger.WriteError("Waiting for event to complete timed out");
Owner?.Logger.WriteDebug($"{Id}, {Type}, {Data}, {Extra}, {FailReason.ToString()}, {Message}, {Origin}, {Target}");
}
// this lets us know if the the action timed out
FailReason = FailReason == EventFailReason.None && !processed ? EventFailReason.Timeout : FailReason;
return this;
}
}

View File

@ -55,6 +55,7 @@ namespace SharedLibraryCore.Interfaces
string ExternalIPAddress { get; }
CancellationToken CancellationToken { get; }
bool IsRestartRequested { get; }
OnServerEventEventHandler OnServerEvent { get; set; }
//OnServerEventEventHandler OnServerEvent { get; set; }
Task ExecuteEvent(GameEvent gameEvent);
}
}

View File

@ -0,0 +1,909 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20191030000713_EnforceUniqueIndexForEFAliasIPName")]
partial class EnforceUniqueIndexForEFAliasIPName
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<int>("CurrentSessionLength")
.HasColumnType("INTEGER");
b.Property<double>("CurrentStrain")
.HasColumnType("REAL");
b.Property<int>("CurrentViewAngleId")
.HasColumnType("INTEGER");
b.Property<int>("Deaths")
.HasColumnType("INTEGER");
b.Property<double>("Distance")
.HasColumnType("REAL");
b.Property<double>("EloRating")
.HasColumnType("REAL");
b.Property<int>("HitDestinationId")
.HasColumnType("INTEGER");
b.Property<int>("HitLocation")
.HasColumnType("INTEGER");
b.Property<int>("HitOriginId")
.HasColumnType("INTEGER");
b.Property<int>("HitType")
.HasColumnType("INTEGER");
b.Property<int>("Hits")
.HasColumnType("INTEGER");
b.Property<int>("Kills")
.HasColumnType("INTEGER");
b.Property<int>("LastStrainAngleId")
.HasColumnType("INTEGER");
b.Property<double>("RecoilOffset")
.HasColumnType("REAL");
b.Property<double>("SessionAngleOffset")
.HasColumnType("REAL");
b.Property<double>("SessionAverageSnapValue")
.HasColumnType("REAL");
b.Property<double>("SessionSPM")
.HasColumnType("REAL");
b.Property<int>("SessionScore")
.HasColumnType("INTEGER");
b.Property<int>("SessionSnapHits")
.HasColumnType("INTEGER");
b.Property<double>("StrainAngleBetween")
.HasColumnType("REAL");
b.Property<int>("TimeSinceLastEvent")
.HasColumnType("INTEGER");
b.Property<int>("WeaponId")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleId");
b.HasIndex("HitDestinationId");
b.HasIndex("HitOriginId");
b.HasIndex("LastStrainAngleId");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.Property<int>("ACSnapshotVector3Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("SnapshotId")
.HasColumnType("INTEGER");
b.Property<int>("Vector3Id")
.HasColumnType("INTEGER");
b.HasKey("ACSnapshotVector3Id");
b.HasIndex("SnapshotId");
b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("AttackerId")
.HasColumnType("INTEGER");
b.Property<int>("Damage")
.HasColumnType("INTEGER");
b.Property<int?>("DeathOriginVector3Id")
.HasColumnType("INTEGER");
b.Property<int>("DeathType")
.HasColumnType("INTEGER");
b.Property<double>("Fraction")
.HasColumnType("REAL");
b.Property<int>("HitLoc")
.HasColumnType("INTEGER");
b.Property<bool>("IsKill")
.HasColumnType("INTEGER");
b.Property<int?>("KillOriginVector3Id")
.HasColumnType("INTEGER");
b.Property<int>("Map")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<int>("VictimId")
.HasColumnType("INTEGER");
b.Property<int?>("ViewAnglesVector3Id")
.HasColumnType("INTEGER");
b.Property<double>("VisibilityPercentage")
.HasColumnType("REAL");
b.Property<int>("Weapon")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("TimeSent")
.HasColumnType("TEXT");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.HasIndex("TimeSent");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<double>("AverageRecoilOffset")
.HasColumnType("REAL");
b.Property<double>("AverageSnapValue")
.HasColumnType("REAL");
b.Property<int>("Deaths")
.HasColumnType("INTEGER");
b.Property<double>("EloRating")
.HasColumnType("REAL");
b.Property<int>("Kills")
.HasColumnType("INTEGER");
b.Property<double>("MaxStrain")
.HasColumnType("REAL");
b.Property<double>("RollingWeightedKDR")
.HasColumnType("REAL");
b.Property<double>("SPM")
.HasColumnType("REAL");
b.Property<double>("Skill")
.HasColumnType("REAL");
b.Property<int>("SnapHitCount")
.HasColumnType("INTEGER");
b.Property<int>("TimePlayed")
.HasColumnType("INTEGER");
b.Property<double>("VisionAverage")
.HasColumnType("REAL");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId")
.HasColumnType("INTEGER");
b.Property<long>("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId")
.HasColumnType("INTEGER");
b.Property<int>("HitCount")
.HasColumnType("INTEGER");
b.Property<float>("HitOffsetAverage")
.HasColumnType("REAL");
b.Property<int>("Location")
.HasColumnType("INTEGER");
b.Property<float>("MaxAngleDistance")
.HasColumnType("REAL");
b.HasKey("HitLocationCountId");
b.HasIndex("EFClientStatisticsServerId");
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ActivityAmount")
.HasColumnType("INTEGER");
b.Property<bool>("Newest")
.HasColumnType("INTEGER");
b.Property<double>("Performance")
.HasColumnType("REAL");
b.Property<int>("Ranking")
.HasColumnType("INTEGER");
b.Property<int>("RatingHistoryId")
.HasColumnType("INTEGER");
b.Property<long?>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.HasIndex("Performance", "Ranking", "When");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("EndPoint")
.HasColumnType("TEXT");
b.Property<int?>("GameName")
.HasColumnType("INTEGER");
b.Property<int>("Port")
.HasColumnType("INTEGER");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<long>("TotalKills")
.HasColumnType("INTEGER");
b.Property<long>("TotalPlayTime")
.HasColumnType("INTEGER");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<DateTime>("DateAdded")
.HasColumnType("TEXT");
b.Property<int?>("IPAddress")
.HasColumnType("INTEGER");
b.Property<int>("LinkId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(24);
b.Property<string>("SearchableName")
.HasColumnType("TEXT")
.HasMaxLength(24);
b.HasKey("AliasId");
b.HasIndex("LinkId");
b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress")
.IsUnique();
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("Comment")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("CurrentValue")
.HasColumnType("TEXT");
b.Property<int>("OriginEntityId")
.HasColumnType("INTEGER");
b.Property<string>("PreviousValue")
.HasColumnType("TEXT");
b.Property<int>("TargetEntityId")
.HasColumnType("INTEGER");
b.Property<DateTime>("TimeChanged")
.HasColumnType("TEXT");
b.Property<int>("TypeOfChange")
.HasColumnType("INTEGER");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("AliasLinkId")
.HasColumnType("INTEGER");
b.Property<int>("Connections")
.HasColumnType("INTEGER");
b.Property<int>("CurrentAliasId")
.HasColumnType("INTEGER");
b.Property<DateTime>("FirstConnection")
.HasColumnType("TEXT");
b.Property<DateTime>("LastConnection")
.HasColumnType("TEXT");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<bool>("Masked")
.HasColumnType("INTEGER");
b.Property<long>("NetworkId")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<string>("PasswordSalt")
.HasColumnType("TEXT");
b.Property<int>("TotalConnectionTime")
.HasColumnType("INTEGER");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Extra")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT")
.HasMaxLength(32);
b.Property<DateTime>("Updated")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<string>("AutomatedOffense")
.HasColumnType("TEXT");
b.Property<DateTime?>("Expires")
.HasColumnType("TEXT");
b.Property<bool>("IsEvadedOffense")
.HasColumnType("INTEGER");
b.Property<int>("LinkId")
.HasColumnType("INTEGER");
b.Property<int>("OffenderId")
.HasColumnType("INTEGER");
b.Property<string>("Offense")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("PunisherId")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("When")
.HasColumnType("TEXT");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<float>("X")
.HasColumnType("REAL");
b.Property<float>("Y")
.HasColumnType("REAL");
b.Property<float>("Z")
.HasColumnType("REAL");
b.HasKey("Vector3Id");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshotVector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", "Snapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("SnapshotId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "Vector")
.WithMany()
.HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("EFClientStatisticsClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", null)
.WithMany("HitLocations")
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,120 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class EnforceUniqueIndexForEFAliasIPName : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite")
{
migrationBuilder.Sql(@"DELETE FROM EFAlias WHERE AliasId IN (
SELECT AliasId from (
SELECT AliasId, Name, Min(DateAdded), IPAddress FROM EFAlias where (IPAddress, Name) in (SELECT DISTINCT IPAddress, Name FROM EFAlias GROUP BY EFAlias.IPAddress, Name HAVING count(IPAddress) > 1 AND count(Name) > 1)
GROUP BY IPAddress ORDER BY IPAddress))", true);
migrationBuilder.Sql(@"CREATE UNIQUE INDEX IX_EFAlias_Name_IPAddress ON EFAlias ( IPAddress, Name );", true);
return;
}
else if (migrationBuilder.ActiveProvider == "Pomelo.EntityFrameworkCore.MySql")
{
migrationBuilder.Sql(@"CREATE TEMPORARY TABLE DUPLICATE_ALIASES
SELECT
MIN(`AliasId`) `MIN`,
MAX(`AliasId`) `MAX`,
`LinkId`
FROM
`EFAlias`
WHERE
(`IPAddress`, `NAME`) IN(
SELECT DISTINCT
`IPAddress`,
`NAME`
FROM
`EFAlias`
GROUP BY
`EFAlias`.`IPAddress`,
`NAME`
HAVING
COUNT(`IPAddress`) > 1 AND COUNT(`NAME`) > 1
)
GROUP BY
`IPAddress`
ORDER BY
`IPAddress`;
SET
SQL_SAFE_UPDATES = 0;
UPDATE
`EFClients` AS `Client`
JOIN
DUPLICATE_ALIASES `Duplicate`
ON
`Client`.CurrentAliasId = `Duplicate`.`MIN`
SET
`Client`.CurrentAliasId = `Duplicate`.`MAX`
WHERE
`Client`.`CurrentAliasId` IN(
SELECT
`MIN`
FROM
DUPLICATE_ALIASES
);
DELETE
FROM
`EFAlias`
WHERE
`AliasId` IN(
SELECT
`MIN`
FROM
DUPLICATE_ALIASES
);
SET
SQL_SAFE_UPDATES = 1;
DROP TABLE
DUPLICATE_ALIASES;");
}
else
{
migrationBuilder.Sql(@"DELETE
FROM ""EFAlias""
WHERE ""AliasId""
IN
(
SELECT MIN(""AliasId"") AliasId
FROM ""EFAlias"" WHERE(""IPAddress"", ""Name"")
IN
(
SELECT DISTINCT ""IPAddress"", ""Name""
FROM ""EFAlias""
GROUP BY ""EFAlias"".""IPAddress"", ""Name""
HAVING COUNT(""IPAddress"") > 1 AND COUNT(""Name"") > 1
)
GROUP BY ""IPAddress""
ORDER BY ""IPAddress""
)", true);
}
migrationBuilder.CreateIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias",
columns: new[] { "Name", "IPAddress" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
}
}
}

View File

@ -464,14 +464,13 @@ namespace SharedLibraryCore.Migrations
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.HasIndex("Name");
b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress")
.IsUnique();
b.ToTable("EFAlias");
});

View File

@ -3,8 +3,8 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
@ -82,6 +82,12 @@ namespace SharedLibraryCore.Database.Models
{ "_reportCount", 0 }
};
ReceivedPenalties = new List<EFPenalty>();
ProcessingEvent = new SemaphoreSlim(1, 1);
}
~EFClient()
{
ProcessingEvent.Dispose();
}
public override string ToString()
@ -108,6 +114,7 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public string IPAddressString => IPAddress.ConvertIPtoString();
[NotMapped]
public virtual IDictionary<int, long> LinkedAccounts { get; set; }
@ -452,13 +459,10 @@ namespace SharedLibraryCore.Database.Models
/// <summary>
/// Handles any client related logic on connection
/// </summary>
public bool OnConnect()
public bool IsAbleToConnectSimple()
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
LastConnection = DateTime.UtcNow;
Connections += 1;
string strippedName = Name.StripColors();
if (string.IsNullOrWhiteSpace(Name) || strippedName.Replace(" ", "").Length < 3)
{
@ -524,27 +528,29 @@ namespace SharedLibraryCore.Database.Models
{
IPAddress = ipAddress;
await CurrentServer.Manager.GetClientService().UpdateAlias(this);
CurrentServer.Logger.WriteDebug($"Updated alias for {this}");
await CurrentServer.Manager.GetClientService().Update(this);
CurrentServer.Logger.WriteDebug($"Updated client for {this}");
bool canConnect = await CanConnect(ipAddress);
if (canConnect)
if (!canConnect)
{
CurrentServer.Logger.WriteDebug($"Client {this} is not allowed to join the server");
}
else
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = this,
Target = this,
Owner = CurrentServer
Owner = CurrentServer,
};
CurrentServer.Manager.GetEventHandler().AddEvent(e);
}
else
{
CurrentServer.Logger.WriteDebug($"Client {this} is not allowed to join the server");
}
}
else
@ -560,6 +566,13 @@ namespace SharedLibraryCore.Database.Models
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
bool isAbleToConnectSimple = IsAbleToConnectSimple();
if (!isAbleToConnectSimple)
{
return false;
}
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress);
@ -609,7 +622,7 @@ namespace SharedLibraryCore.Database.Models
Unflag(Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTOFLAG_UNFLAG"], autoKickClient);
}
return OnConnect();
return true;
}
[NotMapped]
@ -661,6 +674,29 @@ namespace SharedLibraryCore.Database.Models
.LocalizationIndex[$"GLOBAL_PERMISSION_{Level.ToString().ToUpper()}"]
};
[NotMapped]
public SemaphoreSlim ProcessingEvent;
public async Task Lock()
{
bool result = await ProcessingEvent.WaitAsync(Utilities.DefaultCommandTimeout);
#if DEBUG
if (!result)
{
throw new InvalidOperationException();
}
#endif
}
public void Unlock()
{
if (ProcessingEvent.CurrentCount == 0)
{
ProcessingEvent.Release(1);
}
}
public override bool Equals(object obj)
{
return ((EFClient)obj).NetworkId == this.NetworkId;

View File

@ -19,19 +19,25 @@ namespace SharedLibraryCore
private Jint.Engine ScriptEngine;
private readonly string FileName;
private IManager Manager;
private readonly FileSystemWatcher _watcher;
public ScriptPlugin(string fileName)
{
FileName = fileName;
var watcher = new FileSystemWatcher()
_watcher = new FileSystemWatcher()
{
Path = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}",
NotifyFilter = NotifyFilters.Size,
Filter = fileName.Split(Path.DirectorySeparatorChar).Last()
};
watcher.Changed += Watcher_Changed;
watcher.EnableRaisingEvents = true;
_watcher.Changed += Watcher_Changed;
_watcher.EnableRaisingEvents = true;
}
~ScriptPlugin()
{
_watcher.Dispose();
}
private async void Watcher_Changed(object sender, FileSystemEventArgs e)

View File

@ -67,7 +67,7 @@ namespace SharedLibraryCore
/// </summary>
/// <param name="P">EFClient pulled from memory reading</param>
/// <returns>True if player added sucessfully, false otherwise</returns>
public abstract Task OnClientConnected(EFClient P);
public abstract Task<EFClient> OnClientConnected(EFClient P);
/// <summary>
/// Remove player by client number

View File

@ -21,19 +21,23 @@ namespace SharedLibraryCore.Services
if (entity.IPAddress != null)
{
var existingAlias = await context.Aliases
var existingAliases = await context.Aliases
.Select(_alias => new { _alias.AliasId, _alias.LinkId, _alias.IPAddress, _alias.Name })
.FirstOrDefaultAsync(_alias => _alias.IPAddress == entity.IPAddress);
.Where(_alias => _alias.IPAddress == entity.IPAddress)
.ToListAsync();
if (existingAlias != null)
if (existingAliases.Count > 0)
{
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing link {existingAlias.LinkId}");
linkId = existingAliases.First().LinkId;
linkId = existingAlias.LinkId;
if (existingAlias.Name == entity.Name)
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing link {linkId}");
var existingExactAlias = existingAliases.FirstOrDefault(_alias => _alias.Name == entity.Name);
if (existingExactAlias != null)
{
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing alias {existingAlias.AliasId}");
aliasId = existingAlias.AliasId;
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing alias {existingExactAlias.AliasId}");
aliasId = existingExactAlias.AliasId;
}
}
}
@ -99,6 +103,8 @@ namespace SharedLibraryCore.Services
private async Task UpdateAlias(string name, int? ip, EFClient entity, DatabaseContext context)
{
entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"Begin update alias for {entity}");
// entity is the tracked db context item
// get all aliases by IP address and LinkId
var iqAliases = context.Aliases
@ -106,11 +112,33 @@ namespace SharedLibraryCore.Services
// we only want alias that have the same IP address or share a link
.Where(_alias => _alias.IPAddress == ip || (_alias.LinkId == entity.AliasLinkId));
#if DEBUG == true
var aliasSql = iqAliases.ToSql();
#endif
var aliases = await iqAliases.ToListAsync();
//// update each of the aliases where this is no IP but the name is identical
//foreach (var alias in aliases.Where(_alias => (_alias.IPAddress == null || _alias.IPAddress == 0)))
//{
// alias.IPAddress = ip;
//}
//// remove any possible duplicates after updating
//foreach (var aliasGroup in aliases.GroupBy(_alias => new { _alias.IPAddress, _alias.Name })
// .Where(_group => _group.Count() > 1))
//{
// var oldestDuplicateAlias = aliasGroup.OrderBy(_alias => _alias.DateAdded).First();
// entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"Oldest duplicate is {oldestDuplicateAlias.AliasId}");
// await context.Clients.Where(_client => aliasGroup.Select(_grp => _grp.AliasId).Contains(_client.CurrentAliasId))
// .ForEachAsync(_client => _client.CurrentAliasId = oldestDuplicateAlias.AliasId);
// var duplicateAliases = aliasGroup.Where(_alias => _alias.AliasId != oldestDuplicateAlias.AliasId);
// context.RemoveRange(duplicateAliases);
// await context.SaveChangesAsync();
// entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"Removed duplicate aliases {string.Join(",", duplicateAliases.Select(_alias => _alias.AliasId))}");
//}
// see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip);
bool hasExactAliasMatch = existingExactAlias != null;
@ -125,12 +153,6 @@ namespace SharedLibraryCore.Services
bool hasExistingAlias = aliases.Count > 0;
bool isAliasLinkUpdated = newAliasLink.AliasLinkId != entity.AliasLink.AliasLinkId;
// update each of the aliases where this is no IP but the name is identical
foreach (var alias in aliases.Where(_alias => (_alias.IPAddress == null || _alias.IPAddress == 0)))
{
alias.IPAddress = ip;
}
await context.SaveChangesAsync();
// this happens when the link we found is different than the one we create before adding an IP
@ -191,9 +213,12 @@ namespace SharedLibraryCore.Services
await context.SaveChangesAsync();
entity.CurrentServer.Logger.WriteDebug($"[updatealias] {entity} has exact alias match, so we're going to try to remove aliasId {oldAlias.AliasId} with linkId {oldAlias.AliasId}");
context.Aliases.Remove(oldAlias);
await context.SaveChangesAsync();
if (context.Entry(oldAlias).State != EntityState.Deleted)
{
entity.CurrentServer.Logger.WriteDebug($"[updatealias] {entity} has exact alias match, so we're going to try to remove aliasId {oldAlias.AliasId} with linkId {oldAlias.AliasId}");
context.Aliases.Remove(oldAlias);
await context.SaveChangesAsync();
}
}
}
@ -216,6 +241,8 @@ namespace SharedLibraryCore.Services
entity.CurrentAliasId = 0;
await context.SaveChangesAsync();
}
entity.CurrentServer.Manager.GetLogger(0).WriteDebug($"End update alias for {entity}");
}
/// <summary>
@ -286,18 +313,41 @@ namespace SharedLibraryCore.Services
throw new NotImplementedException();
}
public async Task<EFClient> Get(int entityID)
public async Task<EFClient> Get(int entityId)
{
// todo: this needs to be optimized for large linked accounts
using (var context = new DatabaseContext(true))
{
var iqClient = from _c in context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Include(c => c.Meta)
where _c.ClientId == entityID
select _c;
var client = await iqClient.FirstOrDefaultAsync();
var client = context.Clients
.Select(_client => new EFClient()
{
ClientId = _client.ClientId,
AliasLinkId = _client.AliasLinkId,
Level = _client.Level,
Connections = _client.Connections,
FirstConnection = _client.FirstConnection,
LastConnection = _client.LastConnection,
Masked = _client.Masked,
NetworkId = _client.NetworkId,
CurrentAlias = new EFAlias()
{
Name = _client.CurrentAlias.Name,
IPAddress = _client.CurrentAlias.IPAddress
}
})
.FirstOrDefault(_client => _client.ClientId == entityId);
client.AliasLink = new EFAliasLink()
{
Children = await context.Aliases
.Where(_alias => _alias.LinkId == client.AliasLinkId)
.Select(_alias => new EFAlias()
{
Name = _alias.Name,
IPAddress = _alias.IPAddress
}).ToListAsync()
};
if (client == null)
{
@ -337,8 +387,19 @@ namespace SharedLibraryCore.Services
EF.CompileAsyncQuery((DatabaseContext context, long networkId) =>
context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.Include(c => c.ReceivedPenalties)
//.Include(c => c.AliasLink.Children)
//.Include(c => c.ReceivedPenalties)
.Select(_client => new EFClient()
{
ClientId = _client.ClientId,
AliasLinkId = _client.AliasLinkId,
Level = _client.Level,
Connections = _client.Connections,
FirstConnection = _client.FirstConnection,
LastConnection = _client.LastConnection,
Masked = _client.Masked,
NetworkId = _client.NetworkId
})
.FirstOrDefault(c => c.NetworkId == networkId)
);

View File

@ -74,6 +74,13 @@ namespace SharedLibraryCore.Services
return await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.Select(_meta => new EFMeta()
{
MetaId = _meta.MetaId,
Key = _meta.Key,
ClientId = _meta.ClientId,
Value = _meta.Value
})
.FirstOrDefaultAsync();
}
}

View File

@ -48,7 +48,7 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Npgsql" Version="4.1.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc3.final" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>

View File

@ -31,7 +31,7 @@ namespace SharedLibraryCore
#endif
public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary<string, string>());
public static TimeSpan DefaultCommandTimeout = new TimeSpan(0, 0, 10);
public static TimeSpan DefaultCommandTimeout = new TimeSpan(0, 0, 25);
public static EFClient IW4MAdminClient(Server server = null)
{

View File

@ -45,17 +45,17 @@ namespace WebfrontCore.Controllers
NetworkId = client.NetworkId,
Meta = new List<ProfileMeta>(),
Aliases = client.AliasLink.Children
.Where(a => a.Name != client.Name)
.Select(a => a.Name)
.Distinct()
.Prepend(client.Name)
.OrderBy(a => a)
.Distinct()
.ToList(),
IPs = client.AliasLink.Children
.Where(i => i.IPAddress != null)
.OrderByDescending(i => i.DateAdded)
.Select(i => i.IPAddress.ConvertIPtoString())
.Union(new List<string>() { client.CurrentAlias.IPAddress.ConvertIPtoString() })
.Where(i => !string.IsNullOrEmpty(i))
.Prepend(client.CurrentAlias.IPAddress.ConvertIPtoString())
.Distinct()
.OrderBy(i => i)
.ToList(),
HasActivePenalty = activePenalties.Count() > 0,
ActivePenaltyType = activePenalties.Count() > 0 ? activePenalties.First().Type.ToString() : null,
@ -139,7 +139,7 @@ namespace WebfrontCore.Controllers
var clientsDto = await Manager.GetClientService().FindClientsByIdentifier(clientName);
foreach(var client in clientsDto)
foreach (var client in clientsDto)
{
if (!Authorized && ((Permission)client.LevelInt).ShouldHideLevel())
{
@ -152,7 +152,7 @@ namespace WebfrontCore.Controllers
return View("Find/Index", clientsDto);
}
public async Task<IActionResult> Meta(int id, int count, int offset, DateTime? startAt)
public async Task<IActionResult> GetMeta(int id, int count, int offset, DateTime? startAt)
{
IEnumerable<ProfileMeta> meta = await MetaService.GetRuntimeMeta(id, startAt == null ? offset : 0, count, startAt ?? DateTime.UtcNow);

View File

@ -23,7 +23,7 @@ namespace WebfrontCore.Middleware
public ClaimsPermissionRemoval(RequestDelegate nextRequest, IManager manager)
{
_manager = manager;
_manager.OnServerEvent += OnGameEvent;
//_manager.OnServerEvent += OnGameEvent;
_privilegedClientIds = new List<int>();
_nextRequest = nextRequest;
}