fix for runaway regular expression on linux
explicitly set string dvars in quotes to allow setting empty dvars allow piping in input from command line (#114) update the distribution for top stats elo prevent game log file rotation from stopping event parsing
This commit is contained in:
parent
02a784ad09
commit
9fdf4bad9c
1
.gitignore
vendored
1
.gitignore
vendored
@ -241,3 +241,4 @@ launchSettings.json
|
||||
/WebfrontCore/wwwroot/fonts
|
||||
/WebfrontCore/wwwroot/font
|
||||
/Plugins/Tests/TestSourceFiles
|
||||
/Tests/ApplicationTests/Files/GameEvents.json
|
||||
|
@ -25,11 +25,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
||||
<PackageReference Include="RestEase" Version="1.4.10" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
@ -59,11 +59,12 @@ namespace IW4MAdmin.Application
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
private readonly IConfigurationHandler<CommandConfiguration> _commandConfiguration;
|
||||
private readonly IGameServerInstanceFactory _serverInstanceFactory;
|
||||
private readonly IParserRegexFactory _parserRegexFactory;
|
||||
|
||||
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
|
||||
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
|
||||
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
|
||||
IEnumerable<IPlugin> plugins)
|
||||
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
MiddlewareActionHandler = actionHandler;
|
||||
_servers = new ConcurrentBag<Server>();
|
||||
@ -74,8 +75,8 @@ namespace IW4MAdmin.Application
|
||||
ConfigHandler = appConfigHandler;
|
||||
StartTime = DateTime.UtcNow;
|
||||
PageList = new PageList();
|
||||
AdditionalEventParsers = new List<IEventParser>() { new BaseEventParser() };
|
||||
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser() };
|
||||
AdditionalEventParsers = new List<IEventParser>() { new BaseEventParser(parserRegexFactory) };
|
||||
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(parserRegexFactory) };
|
||||
TokenAuthenticator = new TokenAuthentication();
|
||||
_metaService = new MetaService();
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
@ -84,6 +85,7 @@ namespace IW4MAdmin.Application
|
||||
_translationLookup = translationLookup;
|
||||
_commandConfiguration = commandConfiguration;
|
||||
_serverInstanceFactory = serverInstanceFactory;
|
||||
_parserRegexFactory = parserRegexFactory;
|
||||
Plugins = plugins;
|
||||
}
|
||||
|
||||
@ -771,7 +773,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
public IRConParser GenerateDynamicRConParser(string name)
|
||||
{
|
||||
return new DynamicRConParser()
|
||||
return new DynamicRConParser(_parserRegexFactory)
|
||||
{
|
||||
Name = name
|
||||
};
|
||||
@ -779,7 +781,7 @@ namespace IW4MAdmin.Application
|
||||
|
||||
public IEventParser GenerateDynamicEventParser(string name)
|
||||
{
|
||||
return new DynamicEventParser()
|
||||
return new DynamicEventParser(_parserRegexFactory)
|
||||
{
|
||||
Name = name
|
||||
};
|
||||
|
@ -2,18 +2,16 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
public class BaseEventParser : IEventParser
|
||||
{
|
||||
public BaseEventParser()
|
||||
public BaseEventParser(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
Configuration = new DynamicEventParserConfiguration()
|
||||
Configuration = new DynamicEventParserConfiguration(parserRegexFactory)
|
||||
{
|
||||
GameDirectory = "main",
|
||||
};
|
||||
@ -66,6 +64,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.Damage, 11);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13);
|
||||
|
||||
Configuration.Time.Pattern = @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )";
|
||||
}
|
||||
|
||||
public IEventParserConfiguration Configuration { get; set; }
|
||||
@ -80,44 +80,48 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
public virtual GameEvent GenerateGameEvent(string logLine)
|
||||
{
|
||||
var timeMatch = Regex.Match(logLine, @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )");
|
||||
var timeMatch = Configuration.Time.PatternMatcher.Match(logLine);
|
||||
int gameTime = 0;
|
||||
|
||||
|
||||
if (timeMatch.Success)
|
||||
{
|
||||
gameTime = (timeMatch.Groups.Values as IEnumerable<object>)
|
||||
gameTime = timeMatch
|
||||
.Values
|
||||
.Skip(2)
|
||||
.Select(_value => int.Parse(_value.ToString()))
|
||||
// this converts the timestamp into seconds passed
|
||||
.Select((_value, index) => int.Parse(_value.ToString()) * (index == 0 ? 60 : 1))
|
||||
.Sum();
|
||||
logLine = logLine.Substring(timeMatch.Value.Length);
|
||||
}
|
||||
// we want to strip the time from the log line
|
||||
logLine = logLine.Substring(timeMatch.Values.First().Length);
|
||||
}
|
||||
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string eventType = lineSplit[0];
|
||||
|
||||
if (eventType == "say" || eventType == "sayteam")
|
||||
{
|
||||
var matchResult = Regex.Match(logLine, Configuration.Say.Pattern);
|
||||
var matchResult = Configuration.Say.PatternMatcher.Match(logLine);
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
string message = matchResult
|
||||
.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
string message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
.ToString()
|
||||
.Replace("\x15", "")
|
||||
.Trim();
|
||||
|
||||
if (message.Length > 0)
|
||||
{
|
||||
long originId = matchResult.Groups[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
long originId = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
|
||||
// todo: these need to defined outside of here
|
||||
if (message[0] == '!' || message[0] == '@')
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = message,
|
||||
Origin = new EFClient() { NetworkId = originId },
|
||||
Origin = new EFClient() { NetworkId = originId, ClientNumber = clientNumber },
|
||||
Message = message,
|
||||
Extra = logLine,
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
|
||||
@ -129,7 +133,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
Type = GameEvent.EventType.Say,
|
||||
Data = message,
|
||||
Origin = new EFClient() { NetworkId = originId },
|
||||
Origin = new EFClient() { NetworkId = originId, ClientNumber = clientNumber },
|
||||
Message = message,
|
||||
Extra = logLine,
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
|
||||
@ -141,19 +145,21 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
if (eventType == "K")
|
||||
{
|
||||
var match = Regex.Match(logLine, Configuration.Kill.Pattern);
|
||||
var match = Configuration.Kill.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
long originId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].Value.ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long targetId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].Value.ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long originId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long targetId = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Kill,
|
||||
Data = logLine,
|
||||
Origin = new EFClient() { NetworkId = originId },
|
||||
Target = new EFClient() { NetworkId = targetId },
|
||||
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
|
||||
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
|
||||
GameTime = gameTime
|
||||
};
|
||||
@ -162,19 +168,21 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
if (eventType == "D")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, Configuration.Damage.Pattern);
|
||||
var match = Configuration.Damage.PatternMatcher.Match(logLine);
|
||||
|
||||
if (regexMatch.Success)
|
||||
if (match.Success)
|
||||
{
|
||||
long originId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long targetId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long originId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
long targetId = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Damage,
|
||||
Data = logLine,
|
||||
Origin = new EFClient() { NetworkId = originId },
|
||||
Target = new EFClient() { NetworkId = targetId },
|
||||
Origin = new EFClient() { NetworkId = originId, ClientNumber = originClientNumber },
|
||||
Target = new EFClient() { NetworkId = targetId, ClientNumber = targetClientNumber },
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.Origin | GameEvent.EventRequiredEntity.Target,
|
||||
GameTime = gameTime
|
||||
};
|
||||
@ -183,9 +191,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
if (eventType == "J")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, Configuration.Join.Pattern);
|
||||
var match = Configuration.Join.PatternMatcher.Match(logLine);
|
||||
|
||||
if (regexMatch.Success)
|
||||
if (match.Success)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -195,10 +203,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
|
||||
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
|
||||
},
|
||||
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
|
||||
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
NetworkId = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
State = EFClient.ClientState.Connecting,
|
||||
},
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
@ -210,8 +218,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
if (eventType == "Q")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, Configuration.Quit.Pattern);
|
||||
if (regexMatch.Success)
|
||||
var match = Configuration.Quit.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -221,10 +230,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
|
||||
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
|
||||
},
|
||||
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
|
||||
ClientNumber = Convert.ToInt32(regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
NetworkId = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertGuidToLong(Configuration.GuidNumberStyle),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
State = EFClient.ClientState.Disconnecting
|
||||
},
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
@ -279,7 +288,6 @@ 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(Configuration.GuidNumberStyle, 1);
|
||||
long targetId = lineSplit[2].ConvertGuidToLong(Configuration.GuidNumberStyle, 1);
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static SharedLibraryCore.Server;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
@ -11,5 +8,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
/// </summary>
|
||||
sealed internal class DynamicEventParser : BaseEventParser
|
||||
{
|
||||
public DynamicEventParser(IParserRegexFactory parserRegexFactory) : base(parserRegexFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,24 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration
|
||||
{
|
||||
public string GameDirectory { get; set; }
|
||||
public ParserRegex Say { get; set; } = new ParserRegex();
|
||||
public ParserRegex Join { get; set; } = new ParserRegex();
|
||||
public ParserRegex Quit { get; set; } = new ParserRegex();
|
||||
public ParserRegex Kill { get; set; } = new ParserRegex();
|
||||
public ParserRegex Damage { get; set; } = new ParserRegex();
|
||||
public ParserRegex Action { get; set; } = new ParserRegex();
|
||||
public ParserRegex Say { get; set; }
|
||||
public ParserRegex Join { get; set; }
|
||||
public ParserRegex Quit { get; set; }
|
||||
public ParserRegex Kill { get; set; }
|
||||
public ParserRegex Damage { get; set; }
|
||||
public ParserRegex Action { get; set; }
|
||||
public ParserRegex Time { get; set; }
|
||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||
|
||||
public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
Say = parserRegexFactory.CreateParserRegex();
|
||||
Join = parserRegexFactory.CreateParserRegex();
|
||||
Quit = parserRegexFactory.CreateParserRegex();
|
||||
Kill = parserRegexFactory.CreateParserRegex();
|
||||
Damage = parserRegexFactory.CreateParserRegex();
|
||||
Action = parserRegexFactory.CreateParserRegex();
|
||||
Time = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
Application/EventParsers/ParserPatternMatcher.cs
Normal file
35
Application/EventParsers/ParserPatternMatcher.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of the IParserPatternMatcher for windows (really it's the only implementation)
|
||||
/// </summary>
|
||||
public class ParserPatternMatcher : IParserPatternMatcher
|
||||
{
|
||||
private Regex regex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Compile(string pattern)
|
||||
{
|
||||
regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IMatchResult Match(string input)
|
||||
{
|
||||
var match = regex.Match(input);
|
||||
|
||||
return new ParserMatchResult()
|
||||
{
|
||||
Success = match.Success,
|
||||
Values = (match.Groups as IEnumerable<object>)?
|
||||
.Select(_item => _item.ToString()).ToArray() ?? new string[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
26
Application/Factories/ParserRegexFactory.cs
Normal file
26
Application/Factories/ParserRegexFactory.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of the IParserRegexFactory
|
||||
/// </summary>
|
||||
public class ParserRegexFactory : IParserRegexFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ParserRegexFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ParserRegex CreateParserRegex()
|
||||
{
|
||||
return new ParserRegex(_serviceProvider.GetService<IParserPatternMatcher>());
|
||||
}
|
||||
}
|
||||
}
|
@ -6,12 +6,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
class GameLogEventDetection
|
||||
public class GameLogEventDetection
|
||||
{
|
||||
private long previousFileSize;
|
||||
private readonly Server _server;
|
||||
private readonly IGameLogReader _reader;
|
||||
private readonly string _gameLogFile;
|
||||
private readonly bool _ignoreBots;
|
||||
|
||||
class EventState
|
||||
@ -20,12 +19,13 @@ namespace IW4MAdmin.Application.IO
|
||||
public string ServerId { get; set; }
|
||||
}
|
||||
|
||||
public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri)
|
||||
public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri, IGameLogReader reader = null)
|
||||
{
|
||||
_gameLogFile = gameLogPath;
|
||||
_reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : _reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||
_reader = gameLogServerUri != null
|
||||
? reader ?? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser)
|
||||
: reader ?? new GameLogReader(gameLogPath, server.EventParser);
|
||||
_server = server;
|
||||
_ignoreBots = server.Manager.GetApplicationSettings().Configuration().IgnoreBots;
|
||||
_ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
|
||||
}
|
||||
|
||||
public async Task PollForChanges()
|
||||
@ -52,7 +52,7 @@ namespace IW4MAdmin.Application.IO
|
||||
_server.Logger.WriteDebug("Stopped polling for changes");
|
||||
}
|
||||
|
||||
private async Task UpdateLogEvents()
|
||||
public async Task UpdateLogEvents()
|
||||
{
|
||||
long fileSize = _reader.Length;
|
||||
|
||||
@ -65,7 +65,10 @@ namespace IW4MAdmin.Application.IO
|
||||
|
||||
// this makes the http log get pulled
|
||||
if (fileDiff < 1 && fileSize != -1)
|
||||
{
|
||||
previousFileSize = fileSize;
|
||||
return;
|
||||
}
|
||||
|
||||
var events = await _reader.ReadEventsFromLog(_server, fileDiff, previousFileSize);
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.IO
|
||||
_parser = parser;
|
||||
}
|
||||
|
||||
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
{
|
||||
// allocate the bytes for the new log lines
|
||||
List<string> logLines = new List<string>();
|
||||
|
@ -16,27 +16,27 @@ namespace IW4MAdmin.Application.IO
|
||||
/// </summary>
|
||||
class GameLogReaderHttp : IGameLogReader
|
||||
{
|
||||
readonly IEventParser Parser;
|
||||
readonly IGameLogServer Api;
|
||||
private readonly IEventParser _eventParser;
|
||||
private readonly IGameLogServer _logServerApi;
|
||||
readonly string logPath;
|
||||
private string lastKey = "next";
|
||||
|
||||
public GameLogReaderHttp(Uri gameLogServerUri, string logPath, IEventParser parser)
|
||||
{
|
||||
this.logPath = logPath.ToBase64UrlSafeString(); ;
|
||||
Parser = parser;
|
||||
Api = RestClient.For<IGameLogServer>(gameLogServerUri);
|
||||
this.logPath = logPath.ToBase64UrlSafeString();
|
||||
_eventParser = parser;
|
||||
_logServerApi = RestClient.For<IGameLogServer>(gameLogServerUri);
|
||||
}
|
||||
|
||||
public long Length => -1;
|
||||
|
||||
public int UpdateInterval => 500;
|
||||
|
||||
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
{
|
||||
var events = new List<GameEvent>();
|
||||
string b64Path = logPath;
|
||||
var response = await Api.Log(b64Path, lastKey);
|
||||
var response = await _logServerApi.Log(b64Path, lastKey);
|
||||
lastKey = response.NextKey;
|
||||
|
||||
if (!response.Success && string.IsNullOrEmpty(lastKey))
|
||||
@ -48,17 +48,17 @@ namespace IW4MAdmin.Application.IO
|
||||
else if (!string.IsNullOrWhiteSpace(response.Data))
|
||||
{
|
||||
// parse each line
|
||||
foreach (string eventLine in response.Data
|
||||
.Split(Environment.NewLine)
|
||||
.Where(_line => _line.Length > 0))
|
||||
var lines = response.Data
|
||||
.Split(Environment.NewLine)
|
||||
.Where(_line => _line.Length > 0);
|
||||
|
||||
foreach (string eventLine in lines)
|
||||
{
|
||||
try
|
||||
{
|
||||
var gameEvent = Parser.GenerateGameEvent(eventLine);
|
||||
// this trim end should hopefully fix the nasty runaway regex
|
||||
var gameEvent = _eventParser.GenerateGameEvent(eventLine.TrimEnd('\r'));
|
||||
events.Add(gameEvent);
|
||||
#if DEBUG == true
|
||||
server.Logger.WriteDebug($"Parsed event with id {gameEvent.Id} from http");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
|
@ -25,7 +25,7 @@ namespace IW4MAdmin
|
||||
public class IW4MServer : Server
|
||||
{
|
||||
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
private GameLogEventDetection LogEvent;
|
||||
public GameLogEventDetection LogEvent;
|
||||
private readonly ITranslationLookup _translationLookup;
|
||||
private const int REPORT_FLAG_COUNT = 4;
|
||||
private int lastGameTime = 0;
|
||||
@ -891,8 +891,8 @@ namespace IW4MAdmin
|
||||
EventParser = Manager.AdditionalEventParsers
|
||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
|
||||
|
||||
RconParser = RconParser ?? new BaseRConParser();
|
||||
EventParser = EventParser ?? new BaseEventParser();
|
||||
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
||||
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
||||
|
||||
RemoteConnection.SetConfiguration(RconParser.Configuration);
|
||||
|
||||
@ -949,6 +949,14 @@ namespace IW4MAdmin
|
||||
try
|
||||
{
|
||||
var website = await this.GetDvarAsync<string>("_website");
|
||||
|
||||
// this occurs for games that don't give us anything back when
|
||||
// the dvar is not set
|
||||
if (string.IsNullOrWhiteSpace(website.Value))
|
||||
{
|
||||
throw new DvarException("value is empty");
|
||||
}
|
||||
|
||||
Website = website.Value;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using IW4MAdmin.Application.Factories;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Factories;
|
||||
using IW4MAdmin.Application.Helpers;
|
||||
using IW4MAdmin.Application.IO;
|
||||
using IW4MAdmin.Application.Migration;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -87,7 +87,7 @@ namespace IW4MAdmin.Application
|
||||
catch (Exception e)
|
||||
{
|
||||
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||
string exitMessage = translationLookup == null ? "Press any key to exit..." : translationLookup["MANAGER_EXIT"];
|
||||
string exitMessage = translationLookup == null ? "Press enter to exit..." : translationLookup["MANAGER_EXIT"];
|
||||
|
||||
Console.WriteLine(failMessage);
|
||||
|
||||
@ -115,7 +115,7 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
Console.WriteLine(exitMessage);
|
||||
Console.ReadKey();
|
||||
await Console.In.ReadAsync(new char[1], 0, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -237,7 +237,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
while (!ServerManager.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
lastCommand = Console.ReadLine();
|
||||
lastCommand = await Console.In.ReadLineAsync();
|
||||
|
||||
if (lastCommand?.Length > 0)
|
||||
{
|
||||
@ -282,6 +282,8 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IRConConnectionFactory, RConConnectionFactory>()
|
||||
.AddSingleton<IGameServerInstanceFactory, GameServerInstanceFactory>()
|
||||
.AddSingleton<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
|
||||
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton(_serviceProvider =>
|
||||
{
|
||||
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
|
||||
|
21
Application/Misc/ParserMatchResult.cs
Normal file
21
Application/Misc/ParserMatchResult.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of the IMatchResult
|
||||
/// used to hold matching results
|
||||
/// </summary>
|
||||
public class ParserMatchResult : IMatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// array of matched pattern groups
|
||||
/// </summary>
|
||||
public string[] Values { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the match succeeded
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
@ -12,15 +12,11 @@ using static SharedLibraryCore.Server;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
#if DEBUG
|
||||
public class BaseRConParser : IRConParser
|
||||
#else
|
||||
class BaseRConParser : IRConParser
|
||||
#endif
|
||||
{
|
||||
public BaseRConParser()
|
||||
public BaseRConParser(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
Configuration = new DynamicRConParserConfiguration()
|
||||
Configuration = new DynamicRConParserConfiguration(parserRegexFactory)
|
||||
{
|
||||
CommandPrefixes = new CommandPrefix()
|
||||
{
|
||||
@ -90,7 +86,6 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
|
||||
string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
|
||||
|
||||
|
||||
value = removeTrailingColorCode(value);
|
||||
defaultValue = removeTrailingColorCode(defaultValue);
|
||||
latchedValue = removeTrailingColorCode(latchedValue);
|
||||
@ -134,7 +129,11 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
|
||||
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, $"{dvarName} {dvarValue}")).Length > 0;
|
||||
string dvarString = (dvarValue is string str)
|
||||
? $"{dvarName} \"{str}\""
|
||||
: $"{dvarName} {dvarValue.ToString()}";
|
||||
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// empty implementation of the IW4RConParser
|
||||
@ -6,5 +8,8 @@
|
||||
/// </summary>
|
||||
sealed internal class DynamicRConParser : BaseRConParser
|
||||
{
|
||||
public DynamicRConParser(IParserRegexFactory parserRegexFactory) : base(parserRegexFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using IW4MAdmin.Application.Factories;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System.Globalization;
|
||||
|
||||
@ -11,11 +12,18 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
sealed internal class DynamicRConParserConfiguration : IRConParserConfiguration
|
||||
{
|
||||
public CommandPrefix CommandPrefixes { get; set; }
|
||||
public ParserRegex Status { get; set; } = new ParserRegex();
|
||||
public ParserRegex MapStatus { get; set; } = new ParserRegex();
|
||||
public ParserRegex Dvar { get; set; } = new ParserRegex();
|
||||
public ParserRegex Status { get; set; }
|
||||
public ParserRegex MapStatus { get; set; }
|
||||
public ParserRegex Dvar { get; set; }
|
||||
public string ServerNotRunningResponse { get; set; }
|
||||
public bool WaitForResponse { get; set; } = true;
|
||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||
|
||||
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
Status = parserRegexFactory.CreateParserRegex();
|
||||
MapStatus = parserRegexFactory.CreateParserRegex();
|
||||
Dvar = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using IW4MAdmin.Application;
|
||||
using IW4MAdmin.Application.Factories;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
@ -42,7 +43,6 @@ namespace Tests
|
||||
|
||||
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test");
|
||||
Manager.ConfigHandler.Set(config);
|
||||
Manager.AdditionalRConParsers.Add(new TestRconParser());
|
||||
|
||||
Manager.Init().Wait();
|
||||
|
||||
|
@ -10,6 +10,11 @@ namespace Tests
|
||||
{
|
||||
class TestRconParser : IW4MAdmin.Application.RconParsers.BaseRConParser
|
||||
{
|
||||
public TestRconParser(IParserRegexFactory f) : base(f)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public int FakeClientCount { get; set; }
|
||||
public List<EFClient> FakeClients { get; set; } = new List<EFClient>();
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
@{
|
||||
Layout = null;
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex.Set;
|
||||
double getDeviation(double deviations) => Math.Pow(Math.E, 5.0813 + (deviations * 0.8694));
|
||||
double getDeviation(double deviations) => Math.Pow(Math.E, 5.259 + (deviations * 0.812));
|
||||
string rankIcon(double elo)
|
||||
{
|
||||
if (elo >= getDeviation(-0.75) && elo < getDeviation(1.25))
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
@ -41,10 +40,21 @@ namespace SharedLibraryCore.Interfaces
|
||||
AdditionalGroup = 200
|
||||
}
|
||||
|
||||
public IParserPatternMatcher PatternMatcher { get; private set; }
|
||||
|
||||
private string pattern;
|
||||
/// <summary>
|
||||
/// stores the regular expression groups that will be mapped to group types
|
||||
/// </summary>
|
||||
public string Pattern { get; set; }
|
||||
public string Pattern
|
||||
{
|
||||
get => pattern;
|
||||
set
|
||||
{
|
||||
pattern = value;
|
||||
PatternMatcher.Compile(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stores the mapping from group type to group index in the regular expression
|
||||
@ -90,9 +100,10 @@ namespace SharedLibraryCore.Interfaces
|
||||
}
|
||||
}
|
||||
|
||||
public ParserRegex()
|
||||
public ParserRegex(IParserPatternMatcher pattern)
|
||||
{
|
||||
GroupMapping = new Dictionary<GroupType, int>();
|
||||
PatternMatcher = pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,11 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
ParserRegex Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex information for the time prefix in game log
|
||||
/// </summary>
|
||||
ParserRegex Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
@ -17,11 +15,13 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="fileSizeDiff"></param>
|
||||
/// <param name="startPosition"></param>
|
||||
/// <returns></returns>
|
||||
Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
|
||||
Task<IEnumerable<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition);
|
||||
|
||||
/// <summary>
|
||||
/// how long the log file is
|
||||
/// </summary>
|
||||
long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// how often to poll the log file
|
||||
/// </summary>
|
||||
|
18
SharedLibraryCore/Interfaces/IMatchResult.cs
Normal file
18
SharedLibraryCore/Interfaces/IMatchResult.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// represents a pattern match result
|
||||
/// </summary>
|
||||
public interface IMatchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// array of matched pattern groups
|
||||
/// </summary>
|
||||
string[] Values { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the match succeeded
|
||||
/// </summary>
|
||||
bool Success { get; set; }
|
||||
}
|
||||
}
|
21
SharedLibraryCore/Interfaces/IParserPatternMatcher.cs
Normal file
21
SharedLibraryCore/Interfaces/IParserPatternMatcher.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of a parser pattern
|
||||
/// </summary>
|
||||
public interface IParserPatternMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// converts input string into pattern groups
|
||||
/// </summary>
|
||||
/// <param name="input">input string</param>
|
||||
/// <returns>group matches</returns>
|
||||
IMatchResult Match(string input);
|
||||
|
||||
/// <summary>
|
||||
/// compiles the pattern to be used for matching
|
||||
/// </summary>
|
||||
/// <param name="pattern"></param>
|
||||
void Compile(string pattern);
|
||||
}
|
||||
}
|
14
SharedLibraryCore/Interfaces/IParserRegexFactory.cs
Normal file
14
SharedLibraryCore/Interfaces/IParserRegexFactory.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// defines the capabilities of the parser regex factory
|
||||
/// </summary>
|
||||
public interface IParserRegexFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// creates a new ParserRegex instance
|
||||
/// </summary>
|
||||
/// <returns>ParserRegex instance</returns>
|
||||
ParserRegex CreateParserRegex();
|
||||
}
|
||||
}
|
@ -46,30 +46,30 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="8.6.1" />
|
||||
<PackageReference Include="FluentValidation" Version="8.6.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Npgsql" Version="4.1.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.1.2" />
|
||||
<PackageReference Include="Npgsql" Version="4.1.3.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
|
@ -20,6 +20,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Files\GameEvents.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Files\T6Game.log">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
58
Tests/ApplicationTests/BaseEventParserTests.cs
Normal file
58
Tests/ApplicationTests/BaseEventParserTests.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using ApplicationTests.Fixtures;
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Factories;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
|
||||
namespace ApplicationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class BaseEventParserTests
|
||||
{
|
||||
private EventLogTest eventLogData;
|
||||
private IServiceProvider serviceProvider;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
eventLogData = JsonConvert.DeserializeObject<EventLogTest>(System.IO.File.ReadAllText("Files/GameEvents.json"));
|
||||
serviceProvider = new ServiceCollection()
|
||||
.AddSingleton<BaseEventParser>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
|
||||
.BuildServiceProvider();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParsesAllEventData()
|
||||
{
|
||||
var eventParser = serviceProvider.GetService<BaseEventParser>();
|
||||
|
||||
void AssertMatch(GameEvent src, LogEvent expected)
|
||||
{
|
||||
Assert.AreEqual(expected.ExpectedEventType, src.Type);
|
||||
Assert.AreEqual(expected.ExpectedData, src.Data);
|
||||
Assert.AreEqual(expected.ExpectedMessage, src.Message);
|
||||
Assert.AreEqual(expected.ExpectedTime, src.GameTime);
|
||||
|
||||
//Assert.AreEqual(expected.ExpectedOriginClientName, src.Origin?.Name);
|
||||
Assert.AreEqual(expected.ExpectedOriginClientNumber, src.Origin?.ClientNumber);
|
||||
Assert.AreEqual(expected.ExpectedOriginNetworkId, src.Origin?.NetworkId.ToString("X"));
|
||||
|
||||
//Assert.AreEqual(expected.ExpectedTargetClientName, src.Target?.Name);
|
||||
Assert.AreEqual(expected.ExpectedTargetClientNumber, src.Target?.ClientNumber);
|
||||
Assert.AreEqual(expected.ExpectedTargetNetworkId, src.Target?.NetworkId.ToString("X"));
|
||||
}
|
||||
|
||||
foreach (var e in eventLogData.Events)
|
||||
{
|
||||
var parsedEvent = eventParser.GenerateGameEvent(e.EventLine);
|
||||
AssertMatch(parsedEvent, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
Tests/ApplicationTests/BaseRConParserTests.cs
Normal file
47
Tests/ApplicationTests/BaseRConParserTests.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using FakeItEasy;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using NUnit.Framework;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace ApplicationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class BaseRConParserTests
|
||||
{
|
||||
[Test]
|
||||
public void SetDvarAsync_FormatStringType()
|
||||
{
|
||||
var parser = new BaseRConParser(A.Fake<IParserRegexFactory>());
|
||||
var connection = A.Fake<IRConConnection>();
|
||||
|
||||
parser.SetDvarAsync(connection, "test", "test").Wait();
|
||||
|
||||
A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test \"test\""))
|
||||
.MustHaveHappened();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetDvarAsync_FormatEmptyStringTypeIncludesQuotes()
|
||||
{
|
||||
var parser = new BaseRConParser(A.Fake<IParserRegexFactory>());
|
||||
var connection = A.Fake<IRConConnection>();
|
||||
|
||||
parser.SetDvarAsync(connection, "test", "").Wait();
|
||||
|
||||
A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test \"\""))
|
||||
.MustHaveHappened();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetDvarAsync_FormatsNonString()
|
||||
{
|
||||
var parser = new BaseRConParser(A.Fake<IParserRegexFactory>());
|
||||
var connection = A.Fake<IRConConnection>();
|
||||
|
||||
parser.SetDvarAsync(connection, "test", 123).Wait();
|
||||
|
||||
A.CallTo(() => connection.SendQueryAsync(SharedLibraryCore.RCon.StaticHelpers.QueryType.SET_DVAR, "test 123"))
|
||||
.MustHaveHappened();
|
||||
}
|
||||
}
|
||||
}
|
26
Tests/ApplicationTests/Fixtures/EventLogTest.cs
Normal file
26
Tests/ApplicationTests/Fixtures/EventLogTest.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using static SharedLibraryCore.GameEvent;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace ApplicationTests.Fixtures
|
||||
{
|
||||
class LogEvent
|
||||
{
|
||||
public Game Game { get; set; }
|
||||
public string EventLine { get; set; }
|
||||
public EventType ExpectedEventType { get; set; }
|
||||
public string ExpectedData { get; set; }
|
||||
public string ExpectedMessage { get; set; }
|
||||
public string ExpectedOriginNetworkId { get; set; }
|
||||
public int? ExpectedOriginClientNumber { get; set; }
|
||||
public string ExpectedOriginClientName { get; set; }
|
||||
public string ExpectedTargetNetworkId { get; set; }
|
||||
public int? ExpectedTargetClientNumber { get; set; }
|
||||
public string ExpectedTargetClientName { get; set; }
|
||||
public int? ExpectedTime { get; set; }
|
||||
}
|
||||
|
||||
class EventLogTest
|
||||
{
|
||||
public LogEvent[] Events { get; set; }
|
||||
}
|
||||
}
|
42
Tests/ApplicationTests/IOTests.cs
Normal file
42
Tests/ApplicationTests/IOTests.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using FakeItEasy;
|
||||
using IW4MAdmin.Application.IO;
|
||||
using NUnit.Framework;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApplicationTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IOTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public async Task GameLogEventDetection_WorksAfterFileSizeReset()
|
||||
{
|
||||
var reader = A.Fake<IGameLogReader>();
|
||||
var detect = new GameLogEventDetection(null, "", A.Fake<Uri>(), reader);
|
||||
|
||||
A.CallTo(() => reader.Length)
|
||||
.Returns(100)
|
||||
.Once()
|
||||
.Then
|
||||
.Returns(200)
|
||||
.Once()
|
||||
.Then
|
||||
.Returns(10)
|
||||
.Once()
|
||||
.Then
|
||||
.Returns(100);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
await detect.UpdateLogEvents();
|
||||
}
|
||||
|
||||
A.CallTo(() => reader.ReadEventsFromLog(A<Server>.Ignored, A<long>.Ignored, A<long>.Ignored))
|
||||
.MustHaveHappenedTwiceExactly();
|
||||
}
|
||||
}
|
||||
}
|
@ -35,7 +35,7 @@ namespace ApplicationTests
|
||||
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
|
||||
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
|
||||
|
||||
var parser = new BaseEventParser();
|
||||
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>());
|
||||
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
|
||||
|
||||
var log = System.IO.File.ReadAllLines("Files\\T6MapRotation.log");
|
||||
@ -61,7 +61,7 @@ namespace ApplicationTests
|
||||
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
|
||||
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>());
|
||||
|
||||
var parser = new BaseEventParser();
|
||||
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>());
|
||||
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
|
||||
|
||||
var log = System.IO.File.ReadAllLines("Files\\T6Game.log");
|
||||
|
@ -56,7 +56,7 @@ namespace ApplicationTests
|
||||
A.Fake<ITranslationLookup>(),
|
||||
A.Fake<IRConConnectionFactory>());
|
||||
|
||||
var parser = new BaseEventParser();
|
||||
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>());
|
||||
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
|
||||
|
||||
var log = System.IO.File.ReadAllLines("Files\\T6GameStats.log");
|
||||
|
@ -70,7 +70,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user