From 8a721bb53aec47378002c708d2aae15956193213 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Thu, 6 Sep 2018 13:25:58 -0500 Subject: [PATCH] add game log server --- .gitignore | 1 + .../API/GameLogServer/IGameLogServer.cs | 12 +++ Application/API/GameLogServer/LogInfo.cs | 17 ++++ Application/EventParsers/IW4EventParser.cs | 4 +- Application/IO/GameLogEventDetection.cs | 3 +- Application/IO/GameLogReaderHttp.cs | 53 +++------- Application/Main.cs | 2 +- Application/Manager.cs | 6 +- Application/Server.cs | 25 ++--- GameLogServer/GameLogServer.pyproj | 99 +++++++++++++++++++ GameLogServer/GameLogServer/__init__.py | 9 ++ GameLogServer/GameLogServer/log_reader.py | 75 ++++++++++++++ GameLogServer/GameLogServer/log_resource.py | 17 ++++ GameLogServer/GameLogServer/server.py | 9 ++ GameLogServer/requirements.txt | 12 +++ GameLogServer/runserver.py | 15 +++ IW4MAdmin.sln | 28 +++++- Plugins/Tests/PluginTests.cs | 5 +- SharedLibraryCore/Events/GameEvent.cs | 12 ++- .../Helpers/BaseConfigurationHandler.cs | 2 +- SharedLibraryCore/Server.cs | 1 + SharedLibraryCore/Utilities.cs | 1 + WebfrontCore/Controllers/ConsoleController.cs | 25 ++++- 23 files changed, 362 insertions(+), 71 deletions(-) create mode 100644 Application/API/GameLogServer/IGameLogServer.cs create mode 100644 Application/API/GameLogServer/LogInfo.cs create mode 100644 GameLogServer/GameLogServer.pyproj create mode 100644 GameLogServer/GameLogServer/__init__.py create mode 100644 GameLogServer/GameLogServer/log_reader.py create mode 100644 GameLogServer/GameLogServer/log_resource.py create mode 100644 GameLogServer/GameLogServer/server.py create mode 100644 GameLogServer/requirements.txt create mode 100644 GameLogServer/runserver.py diff --git a/.gitignore b/.gitignore index fbc08f88..081b23ae 100644 --- a/.gitignore +++ b/.gitignore @@ -228,3 +228,4 @@ bootstrap-custom.min.css /DiscordWebhook/env /DiscordWebhook/config.dev.json +/GameLogServer/env diff --git a/Application/API/GameLogServer/IGameLogServer.cs b/Application/API/GameLogServer/IGameLogServer.cs new file mode 100644 index 00000000..cbf9ff67 --- /dev/null +++ b/Application/API/GameLogServer/IGameLogServer.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using RestEase; + +namespace IW4MAdmin.Application.API.GameLogServer +{ + [Header("User-Agent", "IW4MAdmin-RestEase")] + public interface IGameLogServer + { + [Get("log/{path}")] + Task Log([Path] string path); + } +} diff --git a/Application/API/GameLogServer/LogInfo.cs b/Application/API/GameLogServer/LogInfo.cs new file mode 100644 index 00000000..12cd1048 --- /dev/null +++ b/Application/API/GameLogServer/LogInfo.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin.Application.API.GameLogServer +{ + public class LogInfo + { + [JsonProperty("success")] + public bool Success { get; set; } + [JsonProperty("length")] + public int Length { get; set; } + [JsonProperty("data")] + public string Data { get; set; } + } +} diff --git a/Application/EventParsers/IW4EventParser.cs b/Application/EventParsers/IW4EventParser.cs index 0e320b6b..d0f80fdd 100644 --- a/Application/EventParsers/IW4EventParser.cs +++ b/Application/EventParsers/IW4EventParser.cs @@ -84,7 +84,6 @@ namespace IW4MAdmin.Application.EventParsers } } - if (eventType == "ScriptKill") { var origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()); @@ -152,7 +151,8 @@ namespace IW4MAdmin.Application.EventParsers Name = regexMatch.Groups[4].ToString().StripColors(), NetworkId = regexMatch.Groups[2].ToString().ConvertLong(), ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()), - State = Player.ClientState.Connecting + State = Player.ClientState.Connecting, + CurrentServer = server } }; } diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index e2f056d9..57c0b2f3 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -68,7 +68,8 @@ namespace IW4MAdmin.Application.IO long fileDiff = fileSize - PreviousFileSize; - if (fileDiff < 1) + // this makes the http log get pulled + if (fileDiff < 1 && fileSize != -1) return; PreviousFileSize = fileSize; diff --git a/Application/IO/GameLogReaderHttp.cs b/Application/IO/GameLogReaderHttp.cs index a019445e..e8a8dc3a 100644 --- a/Application/IO/GameLogReaderHttp.cs +++ b/Application/IO/GameLogReaderHttp.cs @@ -1,8 +1,11 @@ -using SharedLibraryCore; +using IW4MAdmin.Application.API.GameLogServer; +using RestEase; +using SharedLibraryCore; using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; using System.Net.Http; +using static SharedLibraryCore.Utilities; namespace IW4MAdmin.Application.IO { @@ -12,31 +15,17 @@ namespace IW4MAdmin.Application.IO class GameLogReaderHttp : IGameLogReader { readonly IEventParser Parser; + readonly IGameLogServer Api; readonly string LogFile; public GameLogReaderHttp(string logFile, IEventParser parser) { LogFile = logFile; Parser = parser; + Api = RestClient.For(logFile); } - public long Length - { - get - { - using (var cl = new HttpClient()) - { - using (var re = cl.GetAsync($"{LogFile}&length=1").Result) - { - using (var content = re.Content) - { - string response = content.ReadAsStringAsync().Result ?? "0"; - return Convert.ToInt64(response); - } - } - } - } - } + public long Length => -1; public int UpdateInterval => 1000; @@ -45,29 +34,17 @@ namespace IW4MAdmin.Application.IO #if DEBUG == true server.Logger.WriteDebug($"Begin reading {fileSizeDiff} from http log"); #endif - 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; - } - } - } -#if DEBUG == true - server.Logger.WriteDebug($"retrieved events from http log"); -#endif - List events = new List(); - string[] lines = log.Split(Environment.NewLine); + var events = new List(); + string b64Path = server.LogPath.ToBase64UrlSafeString(); + var response = Api.Log(b64Path).Result; -#if DEBUG == true - server.Logger.WriteDebug($"Begin parse of {lines.Length} lines from http log"); -#endif + if (!response.Success) + { + server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})"); + } // parse each line - foreach (string eventLine in lines) + foreach (string eventLine in response.Data.Split(Environment.NewLine)) { if (eventLine.Length > 0) { diff --git a/Application/Main.cs b/Application/Main.cs index d9354d05..1974292a 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -138,7 +138,7 @@ namespace IW4MAdmin.Application }; ServerManager.GetEventHandler().AddEvent(E); - await E.OnProcessed.WaitAsync(30 * 1000); + await E.WaitAsync(30 * 1000); } Console.Write('>'); diff --git a/Application/Manager.cs b/Application/Manager.cs index a4b1eb7b..8e7b77ab 100644 --- a/Application/Manager.cs +++ b/Application/Manager.cs @@ -100,9 +100,7 @@ namespace IW4MAdmin.Application return; } - - - //// todo: this is a hacky mess + // todo: this is a hacky mess if (newEvent.Origin?.DelayedEvents.Count > 0 && newEvent.Origin?.State == Player.ClientState.Connected) { @@ -175,7 +173,7 @@ namespace IW4MAdmin.Application Logger.WriteDebug("Error Trace: " + ex.StackTrace); } // tell anyone waiting for the output that we're done - newEvent.OnProcessed.Release(); + newEvent.OnProcessed.Set(); } public IList GetServers() diff --git a/Application/Server.cs b/Application/Server.cs index 20c1e576..34b1b4c6 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -567,7 +567,7 @@ namespace IW4MAdmin try { var polledClients = await PollPlayersAsync(); - var waiterList = new List(); + var waiterList = new List(); foreach (var disconnectingClient in polledClients[1]) { @@ -586,10 +586,10 @@ 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.OnProcessed); + waiterList.Add(e); } // wait for all the disconnect tasks to finish - await Task.WhenAll(waiterList.Select(t => t.WaitAsync())); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); waiterList.Clear(); // this are our new connecting clients @@ -610,11 +610,11 @@ namespace IW4MAdmin }; Manager.GetEventHandler().AddEvent(e); - waiterList.Add(e.OnProcessed); + waiterList.Add(e); } // wait for all the connect tasks to finish - await Task.WhenAll(waiterList.Select(t => t.WaitAsync())); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync())); if (ConnectionErrors > 0) { @@ -788,25 +788,28 @@ namespace IW4MAdmin #if DEBUG basepath.Value = @"D:\"; #endif - string logPath; + string logPath = string.Empty; + + LogPath = game == string.Empty ? + $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : + $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; + if (GameName == Game.IW5 || ServerConfig.ManualLogPath?.Length > 0) { logPath = ServerConfig.ManualLogPath; } else { - logPath = game == string.Empty ? - $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{mainPath}{Path.DirectorySeparatorChar}{logfile.Value}" : - $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; + logPath = LogPath; } // hopefully fix wine drive name mangling if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{logPath}", @"[A-Z]:", ""); + logPath = Regex.Replace($"{Path.DirectorySeparatorChar}{LogPath}", @"[A-Z]:", ""); } - if (!File.Exists(logPath) && !logPath.StartsWith("http")) + if (!File.Exists(LogPath) && !logPath.StartsWith("http")) { Logger.WriteError($"{logPath} {loc["SERVER_ERROR_DNE"]}"); #if !DEBUG diff --git a/GameLogServer/GameLogServer.pyproj b/GameLogServer/GameLogServer.pyproj new file mode 100644 index 00000000..249fa7af --- /dev/null +++ b/GameLogServer/GameLogServer.pyproj @@ -0,0 +1,99 @@ + + + + 10.0 + Debug + 2.0 + 42efda12-10d3-4c40-a210-9483520116bc + . + {789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52} + runserver.py + + + . + Web launcher + http://localhost + . + true + GameLogServer + GameLogServer + MSBuild|env|$(MSBuildProjectFullPath) + + + true + false + + + true + false + + + + Code + + + Code + + + + + + + + + + + + + + + env + 3.6 + env (Python 3.6 (64-bit)) + Scripts\python.exe + Scripts\pythonw.exe + PYTHONPATH + X64 + + + + + + + + + + + + + True + True + http://localhost + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + \ No newline at end of file diff --git a/GameLogServer/GameLogServer/__init__.py b/GameLogServer/GameLogServer/__init__.py new file mode 100644 index 00000000..ff2c60f1 --- /dev/null +++ b/GameLogServer/GameLogServer/__init__.py @@ -0,0 +1,9 @@ +""" +The flask application package. +""" + +from flask import Flask +from flask_restful import Api + +app = Flask(__name__) +api = Api(app) \ No newline at end of file diff --git a/GameLogServer/GameLogServer/log_reader.py b/GameLogServer/GameLogServer/log_reader.py new file mode 100644 index 00000000..99731ef7 --- /dev/null +++ b/GameLogServer/GameLogServer/log_reader.py @@ -0,0 +1,75 @@ +import re +import os +import time + +class LogReader(object): + def __init__(self): + self.log_file_sizes = {} + # (if the file changes more than this, ignore ) - 1 MB + self.max_file_size_change = 1000000 + # (if the time between checks is greater, ignore ) - 5 minutes + self.max_file_time_change = 300 + + def read_file(self, path): + # prevent traversing directories + if re.search('r^.+\.\.\\.+$', path): + return False + # must be a valid log path and log file + if not re.search(r'^.+[\\|\/](userraw|mods)[\\|\/].+.log$', path): + return False + # set the initialze size to the current file size + file_size = 0 + if path not in self.log_file_sizes: + self.log_file_sizes[path] = { + 'length' : self.file_length(path), + 'read': time.time() + } + return '' + + # grab the previous values + last_length = self.log_file_sizes[path]['length'] + last_read = self.log_file_sizes[path]['read'] + + # the file is being tracked already + new_file_size = self.file_length(path) + + # the log size was unable to be read (probably the wrong path) + if new_file_size < 0: + return False + + now = time.time() + + file_size_difference = new_file_size - last_length + time_difference = now - last_read + + # update the new size and actually read the data + self.log_file_sizes[path] = { + 'length': new_file_size, + 'read': now + } + + # if it's been too long since we read and the amount changed is too great, discard it + # todo: do we really want old events? maybe make this an "or" + if file_size_difference > self.max_file_size_change and time_difference > self.max_file_time_change: + return '' + + new_log_info = self.get_file_lines(path, file_size_difference) + return new_log_info + + def get_file_lines(self, path, length): + try: + file_handle = open(path, 'rb') + file_handle.seek(-length, 2) + file_data = file_handle.read(length) + file_handle.close() + return file_data.decode('utf-8') + except: + return False + + def file_length(self, path): + try: + return os.stat(path).st_size + except: + return -1 + +reader = LogReader() diff --git a/GameLogServer/GameLogServer/log_resource.py b/GameLogServer/GameLogServer/log_resource.py new file mode 100644 index 00000000..8241d081 --- /dev/null +++ b/GameLogServer/GameLogServer/log_resource.py @@ -0,0 +1,17 @@ +from flask_restful import Resource +from GameLogServer.log_reader import reader +from base64 import urlsafe_b64decode + +class LogResource(Resource): + def get(self, path): + path = urlsafe_b64decode(path).decode('utf-8') + log_info = reader.read_file(path) + + if not log_info: + print('could not read log file ' + path) + + return { + 'success' : log_info is not False, + 'length': -1 if log_info is False else len(log_info), + 'data': log_info + } diff --git a/GameLogServer/GameLogServer/server.py b/GameLogServer/GameLogServer/server.py new file mode 100644 index 00000000..3ec49269 --- /dev/null +++ b/GameLogServer/GameLogServer/server.py @@ -0,0 +1,9 @@ +from flask import Flask +from flask_restful import Api +from .log_resource import LogResource + +app = Flask(__name__) + +def init(): + api = Api(app) + api.add_resource(LogResource, '/log/') diff --git a/GameLogServer/requirements.txt b/GameLogServer/requirements.txt new file mode 100644 index 00000000..4133f608 --- /dev/null +++ b/GameLogServer/requirements.txt @@ -0,0 +1,12 @@ +Flask==1.0.2 +aniso8601==3.0.2 +click==6.7 +Flask-RESTful==0.3.6 +itsdangerous==0.24 +Jinja2==2.10 +MarkupSafe==1.0 +pip==9.0.3 +pytz==2018.5 +setuptools==39.0.1 +six==1.11.0 +Werkzeug==0.14.1 diff --git a/GameLogServer/runserver.py b/GameLogServer/runserver.py new file mode 100644 index 00000000..4b283439 --- /dev/null +++ b/GameLogServer/runserver.py @@ -0,0 +1,15 @@ +""" +This script runs the GameLogServer application using a development server. +""" + +from os import environ +from GameLogServer.server import app, init + +if __name__ == '__main__': + HOST = environ.get('SERVER_HOST', '0.0.0.0') + try: + PORT = int(environ.get('SERVER_PORT', '1625')) + except ValueError: + PORT = 5555 + init() + app.run(HOST, PORT, debug=True) diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index e306c157..00705bcd 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -31,7 +31,7 @@ Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IW4ScriptCommands", "Plugins\IW4ScriptCommands\IW4ScriptCommands.csproj", "{6C706CE5-A206-4E46-8712-F8C48D526091}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IW4ScriptCommands", "Plugins\IW4ScriptCommands\IW4ScriptCommands.csproj", "{6C706CE5-A206-4E46-8712-F8C48D526091}" EndProject Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DiscordWebhook", "DiscordWebhook\DiscordWebhook.pyproj", "{15A81D6E-7502-46CE-8530-0647A380B5F4}" EndProject @@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js EndProjectSection EndProject +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "GameLogServer", "GameLogServer\GameLogServer.pyproj", "{42EFDA12-10D3-4C40-A210-9483520116BC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -306,6 +308,30 @@ Global {15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x64.ActiveCfg = Release|Any CPU {15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x86.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.Build.0 = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.Build.0 = Debug|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x86.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x86.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Any CPU.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.Build.0 = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.ActiveCfg = Release|Any CPU + {42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Plugins/Tests/PluginTests.cs b/Plugins/Tests/PluginTests.cs index 16148d50..7e4ce87a 100644 --- a/Plugins/Tests/PluginTests.cs +++ b/Plugins/Tests/PluginTests.cs @@ -1,9 +1,6 @@ using IW4MAdmin.Application; using SharedLibraryCore; using SharedLibraryCore.Objects; -using System; -using System.Collections.Generic; -using System.Text; using Xunit; namespace Tests @@ -43,7 +40,7 @@ namespace Tests Type = GameEvent.EventType.Say, Origin = client, Data = "nigger", - Owner = client.CurrentServer + Owner = e.Owner }; Manager.GetEventHandler().AddEvent(e); diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index 2c936736..fc5c537f 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.Threading.Tasks; using SharedLibraryCore.Objects; namespace SharedLibraryCore @@ -50,8 +51,7 @@ namespace SharedLibraryCore public GameEvent() { - OnProcessed = new SemaphoreSlim(0); - OnProcessed.Release(); + OnProcessed = new ManualResetEventSlim(); Time = DateTime.UtcNow; Id = GetNextEventId(); } @@ -64,10 +64,16 @@ namespace SharedLibraryCore public Server Owner; public Boolean Remote = false; public object Extra { get; set; } - public SemaphoreSlim OnProcessed { get; set; } + public ManualResetEventSlim OnProcessed { get; set; } public DateTime Time { get; set; } public long Id { get; private set; } + /// + /// asynchronously wait for GameEvent to be processed + /// + /// waitable task + public Task WaitAsync(int timeOut = int.MaxValue) => Task.FromResult(OnProcessed.Wait(timeOut)); + /// /// determine whether an event should be delayed or not /// applies only to the origin entity diff --git a/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs b/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs index 35223e65..ba49667b 100644 --- a/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs +++ b/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs @@ -12,7 +12,7 @@ namespace SharedLibraryCore.Configuration { public class BaseConfigurationHandler : IConfigurationHandler where T : IBaseConfiguration { - string Filename; + readonly string Filename; IConfigurationRoot ConfigurationRoot { get; set; } T _configuration; diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 6963c8b4..c8f89682 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -310,6 +310,7 @@ namespace SharedLibraryCore public RCon.Connection RemoteConnection { get; protected set; } public IRConParser RconParser { get; protected set; } public IEventParser EventParser { get; set; } + public string LogPath { get; protected set; } // Internal protected string IP; diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index 35e1f737..a5ab6802 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -479,6 +479,7 @@ namespace SharedLibraryCore return cmdLine.Length > 1 ? cmdLine[1] : cmdLine[0]; } + public static string ToBase64UrlSafeString(this string src) => Convert.ToBase64String(src.Select(c => Convert.ToByte(c)).ToArray()).Replace('+', '-').Replace('/', '_'); public static Task> GetDvarAsync(this Server server, string dvarName) => server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName); diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index b61ce1b7..43bf35a9 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -47,13 +47,28 @@ namespace WebfrontCore.Controllers }; Manager.GetEventHandler().AddEvent(remoteEvent); + List response; // wait for the event to process - await remoteEvent.OnProcessed.WaitAsync(60*1000); - var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); + if (await remoteEvent.WaitAsync(60 * 1000)) + { + response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); - // remove the added command response - for (int i = 0; i < response.Count; i++) - server.CommandResult.Remove(response[i]); + // remove the added command response + for (int i = 0; i < response.Count; i++) + server.CommandResult.Remove(response[i]); + } + + else + { + response = new List() + { + new CommandResponseInfo() + { + ClientId = client.ClientId, + Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"] + } + }; + } return View("_Response", response); }