1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-25 06:30:21 -05:00

think I finished reworking the event system

added http log reading support for debugging remotely
started working on unit test framework
This commit is contained in:
RaidMax
2018-08-28 16:32:59 -05:00
parent 10c8b5b6c6
commit b6f37035a1
23 changed files with 543 additions and 296 deletions

View File

@ -140,26 +140,26 @@ namespace IW4MAdmin.Application.EventParsers
}
}
if (eventType == "Q")
{
var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
if (regexMatch.Success)
{
return new GameEvent()
{
Type = GameEvent.EventType.Quit,
Data = logLine,
Owner = server,
Origin = new Player()
{
Name = regexMatch.Groups[4].ToString().StripColors(),
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
State = Player.ClientState.Connecting
}
};
}
}
//if (eventType == "Q")
//{
// var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
// if (regexMatch.Success)
// {
// return new GameEvent()
// {
// Type = GameEvent.EventType.Quit,
// Data = logLine,
// Owner = server,
// Origin = new Player()
// {
// Name = regexMatch.Groups[4].ToString().StripColors(),
// NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
// ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
// State = Player.ClientState.Connecting
// }
// };
// }
//}
if (eventType.Contains("ExitLevel"))
{

View File

@ -6,11 +6,11 @@ using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO
{
class GameLogEvent
class GameLogEventDetection
{
Server Server;
long PreviousFileSize;
GameLogReader Reader;
IGameLogReader Reader;
readonly string GameLogFile;
class EventState
@ -19,14 +19,22 @@ namespace IW4MAdmin.Application.IO
public string ServerId { get; set; }
}
public GameLogEvent(Server server, string gameLogPath, string gameLogName)
public GameLogEventDetection(Server server, string gameLogPath, string gameLogName)
{
GameLogFile = gameLogPath;
Reader = new GameLogReader(gameLogPath, server.EventParser);
// todo: abtract this more
if (gameLogPath.StartsWith("http"))
{
Reader = new GameLogReaderHttp(gameLogPath, server.EventParser);
}
else
{
Reader = new GameLogReader(gameLogPath, server.EventParser);
}
Server = server;
Task.Run(async () =>
{
{
while (!server.Manager.ShutdownRequested())
{
if ((server.Manager as ApplicationManager).IsInitialized)
@ -44,7 +52,7 @@ namespace IW4MAdmin.Application.IO
private void OnEvent(object state)
{
long newLength = new FileInfo(GameLogFile).Length;
long newLength = Reader.Length;
try
{

View File

@ -7,11 +7,15 @@ using System.Text;
namespace IW4MAdmin.Application.IO
{
class GameLogReader
class GameLogReader : IGameLogReader
{
IEventParser Parser;
readonly string LogFile;
public long Length => new FileInfo(LogFile).Length;
public int UpdateInterval => 100;
public GameLogReader(string logFile, IEventParser parser)
{
LogFile = logFile;

View File

@ -0,0 +1,84 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
namespace IW4MAdmin.Application.IO
{
/// <summary>
/// provides capibility of reading log files over HTTP
/// </summary>
class GameLogReaderHttp : IGameLogReader
{
readonly IEventParser Parser;
readonly string LogFile;
public GameLogReaderHttp(string logFile, IEventParser parser)
{
LogFile = logFile;
Parser = parser;
}
public long Length
{
get
{
using (var cl = new HttpClient())
{
using (var re = cl.GetAsync($"{LogFile}?length=1").Result)
{
using (var content = re.Content)
{
return Convert.ToInt64(content.ReadAsStringAsync().Result ?? "0");
}
}
}
}
}
public int UpdateInterval => 1000;
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff, long startPosition)
{
string log;
using (var cl = new HttpClient())
{
using (var re = cl.GetAsync($"{LogFile}?start={fileSizeDiff}").Result)
{
using (var content = re.Content)
{
log = content.ReadAsStringAsync().Result;
}
}
}
List<GameEvent> events = new List<GameEvent>();
// parse each line
foreach (string eventLine in log.Split(Environment.NewLine))
{
if (eventLine.Length > 0)
{
try
{
// todo: catch elsewhere
events.Add(Parser.GetEvent(server, eventLine));
}
catch (Exception e)
{
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine);
}
}
}
return events;
}
}
}

View File

@ -50,9 +50,6 @@ namespace IW4MAdmin.Application
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
loc = Utilities.CurrentLocalization.LocalizationIndex;
using (var db = new DatabaseContext(ServerManager.GetApplicationSettings().Configuration()?.ConnectionString))
new ContextSeed(db).Seed().Wait();
var api = API.Master.Endpoint.Get();
var version = new API.Master.VersionInfo()

View File

@ -21,6 +21,7 @@ using Newtonsoft.Json.Linq;
using System.Text;
using IW4MAdmin.Application.API.Master;
using System.Reflection;
using SharedLibraryCore.Database;
namespace IW4MAdmin.Application
{
@ -36,7 +37,7 @@ namespace IW4MAdmin.Application
// define what the delagate function looks like
public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
// expose the event handler so we can execute the events
public OnServerEventEventHandler OnServerEvent { get; private set; }
public OnServerEventEventHandler OnServerEvent { get; set; }
public DateTime StartTime { get; private set; }
static ApplicationManager Instance;
@ -46,10 +47,10 @@ namespace IW4MAdmin.Application
ClientService ClientSvc;
readonly AliasService AliasSvc;
readonly PenaltyService PenaltySvc;
BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
public BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
EventApi Api;
GameEventHandler Handler;
ManualResetEventSlim OnEvent;
ManualResetEventSlim OnQuit;
readonly IPageList PageList;
public class GameEventArgs : System.ComponentModel.AsyncCompletedEventArgs
@ -78,7 +79,7 @@ namespace IW4MAdmin.Application
//ServerEventOccurred += Api.OnServerEvent;
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
StartTime = DateTime.UtcNow;
OnEvent = new ManualResetEventSlim();
OnQuit = new ManualResetEventSlim();
PageList = new PageList();
OnServerEvent += OnServerEventAsync;
}
@ -110,15 +111,16 @@ namespace IW4MAdmin.Application
await newEvent.Owner.ExecuteEvent(newEvent);
//// todo: this is a hacky mess
if (newEvent.Origin?.DelayedEvents?.Count > 0 &&
if (newEvent.Origin?.DelayedEvents.Count > 0 &&
newEvent.Origin?.State == Player.ClientState.Connected)
{
var events = newEvent.Origin.DelayedEvents;
// add the delayed event to the queue
while (events?.Count > 0)
while(events.Count > 0)
{
var e = events.Dequeue();
e.Origin = newEvent.Origin;
// check if the target was assigned
if (e.Target != null)
@ -133,9 +135,12 @@ namespace IW4MAdmin.Application
continue;
}
}
Logger.WriteDebug($"Adding delayed event of type {e.Type} for {e.Origin} back for processing");
this.GetEventHandler().AddEvent(e);
}
}
Api.OnServerEvent(this, newEvent);
#if DEBUG
Logger.WriteDebug("Processed Event");
#endif
@ -248,6 +253,11 @@ namespace IW4MAdmin.Application
Running = true;
#region DATABASE
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString))
{
await new ContextSeed(db).Seed();
}
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new
{
@ -513,8 +523,8 @@ namespace IW4MAdmin.Application
while (Running)
{
OnEvent.Wait();
OnEvent.Reset();
OnQuit.Wait();
OnQuit.Reset();
}
_servers.Clear();
}
@ -558,7 +568,7 @@ namespace IW4MAdmin.Application
public void SetHasEvent()
{
OnEvent.Set();
OnQuit.Set();
}
public IList<Assembly> GetPluginAssemblies() => SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;

View File

@ -27,7 +27,7 @@ namespace IW4MAdmin.Application.RconParsers
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
return response.Skip(1).ToArray();
}
@ -117,7 +117,6 @@ namespace IW4MAdmin.Application.RconParsers
IsBot = ip == 0,
State = Player.ClientState.Connecting
};
StatusPlayers.Add(P);
}
}

View File

@ -10,7 +10,7 @@ using SharedLibraryCore.Objects;
using SharedLibraryCore.RCon;
using SharedLibraryCore.Exceptions;
namespace IW4MAdmin.WApplication.RconParsers
namespace IW4MAdmin.Application.RconParsers
{
public class IW5MRConParser : IRConParser
{

View File

@ -20,14 +20,13 @@ using IW4MAdmin.Application.RconParsers;
using IW4MAdmin.Application.EventParsers;
using IW4MAdmin.Application.IO;
using IW4MAdmin.Application.Core;
using IW4MAdmin.WApplication.RconParsers;
namespace IW4MAdmin
{
public class IW4MServer : Server
{
private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex;
private GameLogEvent LogEvent;
private GameLogEventDetection LogEvent;
private ClientAuthentication AuthQueue;
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
@ -56,8 +55,11 @@ namespace IW4MAdmin
public async Task OnPlayerJoined(Player logClient)
{
if (Players[logClient.ClientNumber] == null ||
Players[logClient.ClientNumber].NetworkId != logClient.NetworkId)
var existingClient = Players[logClient.ClientNumber];
if (existingClient == null ||
(existingClient.NetworkId != logClient.NetworkId &&
existingClient.State != Player.ClientState.Connected))
{
Logger.WriteDebug($"Log detected {logClient} joining");
Players[logClient.ClientNumber] = logClient;
@ -68,9 +70,8 @@ namespace IW4MAdmin
override public async Task<bool> AddPlayer(Player polledPlayer)
{
//if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
// polledPlayer.Ping < 1 ||
if (
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
polledPlayer.Ping < 1 ||
polledPlayer.ClientNumber < 0)
{
//Logger.WriteDebug($"Skipping client not in connected state {P}");
@ -78,7 +79,7 @@ namespace IW4MAdmin
}
// set this when they are waiting for authentication
if (Players[polledPlayer.ClientNumber] == null &&
if (Players[polledPlayer.ClientNumber] == null &&
polledPlayer.State == Player.ClientState.Connecting)
{
Players[polledPlayer.ClientNumber] = polledPlayer;
@ -186,6 +187,8 @@ namespace IW4MAdmin
player.IsBot = polledPlayer.IsBot;
player.Score = polledPlayer.Score;
player.CurrentServer = this;
player.DelayedEvents = (Players[player.ClientNumber]?.DelayedEvents) ?? new Queue<GameEvent>();
Players[player.ClientNumber] = player;
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId, player.IPAddress);
@ -278,7 +281,6 @@ namespace IW4MAdmin
public override async Task ExecuteEvent(GameEvent E)
{
bool canExecuteCommand = true;
Manager.GetEventApi().OnServerEvent(this, E);
await ProcessEvent(E);
Command C = null;
@ -387,15 +389,16 @@ namespace IW4MAdmin
}
}
if (E.Type == GameEvent.EventType.Connect)
else if (E.Type == GameEvent.EventType.Connect)
{
E.Origin.State = Player.ClientState.Authenticated;
// add them to the server
if (!await AddPlayer(E.Origin))
{
throw new ServerException("Player didn't pass authorization, so we are discontinuing event");
E.Origin.State = Player.ClientState.Connecting;
throw new ServerException("client didn't pass authorization, so we are discontinuing event");
}
// hack makes the event propgate with the correct info
// hack: makes the event propgate with the correct info
E.Origin = Players[E.Origin.ClientNumber];
ChatHistory.Add(new ChatInfo()
@ -416,27 +419,27 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Quit)
{
var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
//var origin = Players.FirstOrDefault(p => p != null && p.NetworkId == E.Origin.NetworkId);
if (origin != null &&
// we only want to forward the event if they are connected.
origin.State == Player.ClientState.Connected)
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = origin,
Owner = this
};
//if (origin != null &&
// // we only want to forward the event if they are connected.
// origin.State == Player.ClientState.Connected)
//{
// var e = new GameEvent()
// {
// Type = GameEvent.EventType.Disconnect,
// Origin = origin,
// Owner = this
// };
Manager.GetEventHandler().AddEvent(e);
}
// Manager.GetEventHandler().AddEvent(e);
//}
else if (origin != null &&
origin.State != Player.ClientState.Connected)
{
await RemovePlayer(origin.ClientNumber);
}
//else if (origin != null &&
// origin.State != Player.ClientState.Connected)
//{
// await RemovePlayer(origin.ClientNumber);
//}
}
else if (E.Type == GameEvent.EventType.Disconnect)
@ -448,7 +451,13 @@ namespace IW4MAdmin
Time = DateTime.UtcNow
});
var currentState = E.Origin.State;
await RemovePlayer(E.Origin.ClientNumber);
if (currentState != Player.ClientState.Connected)
{
throw new ServerException("Disconnecting player was not in a connected state");
}
}
if (E.Type == GameEvent.EventType.Say)
@ -555,7 +564,7 @@ namespace IW4MAdmin
#endif
Throttled = false;
foreach(var client in polledClients)
foreach (var client in polledClients)
{
// todo: move out somehwere
var existingClient = Players[client.ClientNumber] ?? client;
@ -564,7 +573,7 @@ namespace IW4MAdmin
}
var disconnectingClients = currentClients.Except(polledClients);
var connectingClients = polledClients.Except(currentClients);
var connectingClients = polledClients.Except(currentClients.Where(c => c.State == Player.ClientState.Connected));
return new List<Player>[] { connectingClients.ToList(), disconnectingClients.ToList() };
}
@ -634,8 +643,8 @@ namespace IW4MAdmin
}
// wait for all the connect tasks to finish
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait())));
await Task.WhenAll(waiterList.Select(t => Task.Run(() => t.Wait(5000))));
if (ConnectionErrors > 0)
{
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}");
@ -806,10 +815,10 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir();
#if DEBUG
basepath.Value = @"";
basepath.Value = @"D:\";
#endif
string logPath;
if (GameName == Game.IW5)
if (GameName == Game.IW5 || ServerConfig.ManualLogPath?.Length > 0)
{
logPath = ServerConfig.ManualLogPath;
}
@ -831,11 +840,13 @@ namespace IW4MAdmin
Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}");
#if !DEBUG
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {logPath}");
#else
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
#endif
}
else
{
LogEvent = new GameLogEvent(this, logPath, logfile.Value);
LogEvent = new GameLogEventDetection(this, logPath, logfile.Value);
}
Logger.WriteInfo($"Log file is {logPath}");