From 1dc0f5a2406b57541f3c059ddb961a111103b4c4 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Wed, 8 May 2019 20:34:17 -0500 Subject: [PATCH] fix aggregate issue with KDR on global top stats refactor some of the main application code to have a cleaner code flow add enviroment flag to opt out of .net core telemetry in start script fixed "a moment" missing the "ago" fixed case sensitive client searches on postgresql clean up command code flow Add missing map "mp_cairo" to default settings --- Application/Application.csproj | 12 +- Application/ApplicationManager.cs | 96 +++--- Application/BuildScripts/PostPublish.bat | 8 +- Application/DefaultSettings.json | 6 +- Application/IO/GameLogEventDetection.cs | 45 +-- Application/IW4MServer.cs | 24 +- Application/Main.cs | 291 +++++++++--------- .../Migration/ConfigurationMigration.cs | 21 ++ Application/Misc/Logger.cs | 1 + IW4MAdmin.sln | 4 +- Plugins/Stats/Helpers/StatManager.cs | 108 ++++--- SharedLibraryCore/Commands/NativeCommands.cs | 198 ++++++------ SharedLibraryCore/Database/DatabaseContext.cs | 6 +- SharedLibraryCore/Events/GameEvent.cs | 4 +- SharedLibraryCore/Interfaces/IManager.cs | 11 +- SharedLibraryCore/Objects/EFClient.cs | 27 +- SharedLibraryCore/ScriptPlugin.cs | 2 +- SharedLibraryCore/Server.cs | 12 +- SharedLibraryCore/Services/ClientService.cs | 4 +- SharedLibraryCore/Utilities.cs | 9 +- WebfrontCore/Controllers/ConsoleController.cs | 2 +- WebfrontCore/Program.cs | 10 +- 22 files changed, 492 insertions(+), 409 deletions(-) diff --git a/Application/Application.csproj b/Application/Application.csproj index c4d3a54df..b12c87b08 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -6,7 +6,7 @@ 2.2.2 false RaidMax.IW4MAdmin.Application - 2.2.7.1 + 2.2.7.2 RaidMax Forever None IW4MAdmin @@ -32,8 +32,8 @@ true true - 2.2.7.1 - 2.2.7.1 + 2.2.7.2 + 2.2.7.2 7.1 @@ -47,12 +47,6 @@ - - - Always - - - diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 723b5c034..2175e01f6 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -15,6 +15,7 @@ using SharedLibraryCore.Interfaces; using SharedLibraryCore.Objects; using SharedLibraryCore.Services; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -26,7 +27,7 @@ namespace IW4MAdmin.Application { public class ApplicationManager : IManager { - private List _servers; + private ConcurrentBag _servers; public List Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public ILogger Logger => GetLogger(0); public bool Running { get; private set; } @@ -41,8 +42,9 @@ namespace IW4MAdmin.Application public IList AdditionalRConParsers { get; } public IList AdditionalEventParsers { get; } public ITokenAuthentication TokenAuthenticator { get; } + public CancellationToken CancellationToken => _tokenSource.Token; public string ExternalIPAddress { get; private set; } - + public bool IsRestartRequested { get; private set; } static ApplicationManager Instance; readonly List TaskStatuses; List Commands; @@ -52,16 +54,16 @@ namespace IW4MAdmin.Application readonly PenaltyService PenaltySvc; public BaseConfigurationHandler ConfigHandler; GameEventHandler Handler; - ManualResetEventSlim OnQuit; readonly IPageList PageList; readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1); readonly Dictionary Loggers = new Dictionary(); private readonly MetaService _metaService; private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); + private readonly CancellationTokenSource _tokenSource; private ApplicationManager() { - _servers = new List(); + _servers = new ConcurrentBag(); Commands = new List(); TaskStatuses = new List(); MessageTokens = new List(); @@ -70,7 +72,6 @@ namespace IW4MAdmin.Application PenaltySvc = new PenaltyService(); ConfigHandler = new BaseConfigurationHandler("IW4MAdminSettings"); StartTime = DateTime.UtcNow; - OnQuit = new ManualResetEventSlim(); PageList = new PageList(); AdditionalEventParsers = new List(); AdditionalRConParsers = new List(); @@ -78,6 +79,7 @@ namespace IW4MAdmin.Application OnServerEvent += EventApi.OnGameEvent; TokenAuthenticator = new TokenAuthentication(); _metaService = new MetaService(); + _tokenSource = new CancellationTokenSource(); } private async void OnGameEvent(object sender, GameEventArgs args) @@ -155,12 +157,12 @@ namespace IW4MAdmin.Application return Instance ?? (Instance = new ApplicationManager()); } - public async Task UpdateServerStates(CancellationToken token) + public async Task UpdateServerStates() { // store the server hash code and task for it var runningUpdateTasks = new Dictionary(); - while (Running) + while (!_tokenSource.IsCancellationRequested) { // select the server ids that have completed the update task var serverTasksToRemove = runningUpdateTasks @@ -191,10 +193,11 @@ namespace IW4MAdmin.Application { try { - await server.ProcessUpdatesAsync(token); + await server.ProcessUpdatesAsync(_tokenSource.Token); + if (server.Throttled) { - await Task.Delay((int)_throttleTimeout.TotalMilliseconds); + await Task.Delay((int)_throttleTimeout.TotalMilliseconds, _tokenSource.Token); } } @@ -218,7 +221,7 @@ namespace IW4MAdmin.Application #endif try { - await Task.Delay(ConfigHandler.Configuration().RConPollRate, token); + await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token); } // if a cancellation is received, we want to return immediately catch { break; } @@ -341,7 +344,7 @@ namespace IW4MAdmin.Application GetApplicationSettings().Configuration()?.DatabaseProvider)) { await new ContextSeed(db).Seed(); - } + } #endregion #region COMMANDS @@ -351,6 +354,7 @@ namespace IW4MAdmin.Application } Commands.Add(new CQuit()); + Commands.Add(new CRestart()); Commands.Add(new CKick()); Commands.Add(new CSay()); Commands.Add(new CTempBan()); @@ -517,7 +521,12 @@ namespace IW4MAdmin.Application MetaService.AddRuntimeMeta(getPenaltyMeta); #endregion - #region INIT + await InitializeServers(); + } + + private async Task InitializeServers() + { + var config = ConfigHandler.Configuration(); int successServers = 0; Exception lastException = null; @@ -531,10 +540,7 @@ namespace IW4MAdmin.Application var ServerInstance = new IW4MServer(this, Conf); await ServerInstance.Initialize(); - lock (_servers) - { - _servers.Add(ServerInstance); - } + _servers.Add(ServerInstance); Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname)); // add the start event for this server @@ -577,25 +583,25 @@ namespace IW4MAdmin.Application throw lastException; } } - #endregion } - private async Task SendHeartbeat(object state) - { - var heartbeatState = (HeartbeatState)state; - while (Running) + private async Task SendHeartbeat() + { + bool connected = false; + + while (!_tokenSource.IsCancellationRequested) { - if (!heartbeatState.Connected) + if (!connected) { try { await Heartbeat.Send(this, true); - heartbeatState.Connected = true; + connected = true; } catch (Exception e) { - heartbeatState.Connected = false; + connected = false; Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}"); } } @@ -621,7 +627,7 @@ namespace IW4MAdmin.Application { if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized) { - heartbeatState.Connected = false; + connected = false; } } } @@ -631,9 +637,10 @@ namespace IW4MAdmin.Application Logger.WriteWarning($"Could not send heartbeat - {e.Message}"); if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized) { - heartbeatState.Connected = false; + connected = false; } } + catch (Exception e) { Logger.WriteWarning($"Could not send heartbeat - {e.Message}"); @@ -643,31 +650,32 @@ namespace IW4MAdmin.Application try { - await Task.Delay(30000, heartbeatState.Token); + await Task.Delay(30000, _tokenSource.Token); } catch { break; } } } - public void Start() + public Task Start() { - var tokenSource = new CancellationTokenSource(); - // this needs to be run seperately from the main thread - _ = Task.Run(() => SendHeartbeat(new HeartbeatState() { Token = tokenSource.Token })); - _ = Task.Run(() => UpdateServerStates(tokenSource.Token)); - - while (Running) + return Task.WhenAll(new[] { - OnQuit.Wait(); - tokenSource.Cancel(); - OnQuit.Reset(); - } + SendHeartbeat(), + UpdateServerStates() + }); } public void Stop() { + _tokenSource.Cancel(); Running = false; - OnQuit.Set(); + Instance = null; + } + + public void Restart() + { + IsRestartRequested = true; + Stop(); } public ILogger GetLogger(long serverId) @@ -725,21 +733,11 @@ namespace IW4MAdmin.Application return ConfigHandler; } - public bool ShutdownRequested() - { - return !Running; - } - public IEventHandler GetEventHandler() { return Handler; } - public void SetHasEvent() - { - - } - public IList GetPluginAssemblies() { return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies.Union(SharedLibraryCore.Plugins.PluginImporter.Assemblies).ToList(); diff --git a/Application/BuildScripts/PostPublish.bat b/Application/BuildScripts/PostPublish.bat index 0f159edc1..22d06a63a 100644 --- a/Application/BuildScripts/PostPublish.bat +++ b/Application/BuildScripts/PostPublish.bat @@ -108,8 +108,8 @@ if "%CurrentConfiguration%" == "Release" ( ) echo making start scripts -@(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd" -@(echo @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd" +@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd" +@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd" -@(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" -@(echo #!/bin/bash && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" +@(echo #!/bin/bash && echo export DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh" +@(echo #!/bin/bash && echo export DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib/IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh" diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index bd953c342..811565d16 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -16,7 +16,7 @@ "Keep grenade launcher use to a minimum", "Balance teams at ALL times" ], - "DisallowedClientNames": ["Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER"], + "DisallowedClientNames": [ "Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER", "Play77" ], "QuickMessages": [ { "Game": "IW4", @@ -517,6 +517,10 @@ "Alias": "Hanoi", "Name": "mp_hanoi" }, + { + "Alias": "Havana", + "Name": "mp_cairo" + }, { "Alias": "Hazard", "Name": "mp_golfcourse" diff --git a/Application/IO/GameLogEventDetection.cs b/Application/IO/GameLogEventDetection.cs index 3af8d2930..35cb588f3 100644 --- a/Application/IO/GameLogEventDetection.cs +++ b/Application/IO/GameLogEventDetection.cs @@ -8,10 +8,10 @@ namespace IW4MAdmin.Application.IO { class GameLogEventDetection { - Server Server; - long PreviousFileSize; - IGameLogReader Reader; - readonly string GameLogFile; + private long previousFileSize; + private readonly Server _server; + private readonly IGameLogReader _reader; + private readonly string _gameLogFile; class EventState { @@ -21,16 +21,16 @@ namespace IW4MAdmin.Application.IO public GameLogEventDetection(Server server, string gameLogPath, Uri gameLogServerUri) { - GameLogFile = gameLogPath; - Reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : Reader = new GameLogReader(gameLogPath, server.EventParser); - Server = server; + _gameLogFile = gameLogPath; + _reader = gameLogServerUri != null ? new GameLogReaderHttp(gameLogServerUri, gameLogPath, server.EventParser) : _reader = new GameLogReader(gameLogPath, server.EventParser); + _server = server; } public async Task PollForChanges() { - while (!Server.Manager.ShutdownRequested()) + while (!_server.Manager.CancellationToken.IsCancellationRequested) { - if (Server.IsInitialized) + if (_server.IsInitialized) { try { @@ -39,39 +39,40 @@ namespace IW4MAdmin.Application.IO catch (Exception e) { - Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}"); - Server.Logger.WriteDebug($"Exception: {e.Message}"); - Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); + _server.Logger.WriteWarning($"Failed to update log event for {_server.EndPoint}"); + _server.Logger.WriteDebug($"Exception: {e.Message}"); + _server.Logger.WriteDebug($"StackTrace: {e.StackTrace}"); } } - Thread.Sleep(Reader.UpdateInterval); + + await Task.Delay(_reader.UpdateInterval, _server.Manager.CancellationToken); } } private async Task UpdateLogEvents() { - long fileSize = Reader.Length; + long fileSize = _reader.Length; - if (PreviousFileSize == 0) - PreviousFileSize = fileSize; + if (previousFileSize == 0) + previousFileSize = fileSize; - long fileDiff = fileSize - PreviousFileSize; + long fileDiff = fileSize - previousFileSize; // this makes the http log get pulled if (fileDiff < 1 && fileSize != -1) return; - PreviousFileSize = fileSize; + previousFileSize = fileSize; - var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0); + var events = await _reader.ReadEventsFromLog(_server, fileDiff, 0); foreach (var ev in events) { - Server.Manager.GetEventHandler().AddEvent(ev); - await ev.WaitAsync(); + _server.Manager.GetEventHandler().AddEvent(ev); + await ev.WaitAsync(Utilities.DefaultCommandTimeout, _server.Manager.CancellationToken); } - PreviousFileSize = fileSize; + previousFileSize = fileSize; } } } diff --git a/Application/IW4MServer.cs b/Application/IW4MServer.cs index e63f836ca..903df21bf 100644 --- a/Application/IW4MServer.cs +++ b/Application/IW4MServer.cs @@ -296,6 +296,20 @@ namespace IW4MAdmin Target = E.Target, Reason = E.Data }); + + Penalty newReport = new Penalty() + { + Type = Penalty.PenaltyType.Report, + Expires = DateTime.UtcNow, + Offender = E.Target, + Offense = E.Message, + Punisher = E.Origin, + Active = true, + When = DateTime.UtcNow, + Link = E.Target.AliasLink + }; + + await Manager.GetPenaltyService().Create(newReport); } else if (E.Type == GameEvent.EventType.TempBan) @@ -548,7 +562,7 @@ namespace IW4MAdmin try { #region SHUTDOWN - if (Manager.ShutdownRequested()) + if (Manager.CancellationToken.IsCancellationRequested) { foreach (var client in GetClientsAsList()) { @@ -560,7 +574,7 @@ namespace IW4MAdmin }; Manager.GetEventHandler().AddEvent(e); - await e.WaitAsync(); + await e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken); } foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) @@ -597,7 +611,7 @@ namespace IW4MAdmin waiterList.Add(e); } // wait for all the disconnect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); waiterList.Clear(); // this are our new connecting clients @@ -621,7 +635,7 @@ namespace IW4MAdmin } // wait for all the connect tasks to finish - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); waiterList.Clear(); // these are the clients that have updated @@ -638,7 +652,7 @@ namespace IW4MAdmin waiterList.Add(e); } - await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); + await Task.WhenAll(waiterList.Select(e => e.WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken))); if (ConnectionErrors > 0) { diff --git a/Application/Main.cs b/Application/Main.cs index 841226802..b56b26144 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -1,204 +1,221 @@ using IW4MAdmin.Application.Migration; using SharedLibraryCore; -using SharedLibraryCore.Localization; using System; -using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace IW4MAdmin.Application { public class Program { - static public double Version { get; private set; } - static public ApplicationManager ServerManager; - private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim(); + public static double Version { get; private set; } = Utilities.GetVersionAsDouble(); + public static ApplicationManager ServerManager; + private static Task ApplicationTask; - public static void Main(string[] args) + /// + /// entrypoint of the application + /// + /// + public static async Task Main() { AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory); + Console.OutputEncoding = Encoding.UTF8; Console.ForegroundColor = ConsoleColor.Gray; - Version = Utilities.GetVersionAsDouble(); + Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.WriteLine("====================================================="); - Console.WriteLine(" IW4M ADMIN"); + Console.WriteLine(" IW4MAdmin"); Console.WriteLine(" by RaidMax "); Console.WriteLine($" Version {Utilities.GetVersionAsString()}"); Console.WriteLine("====================================================="); - Index loc = null; + await LaunchAsync(); + } + /// + /// event callback executed when the control + c combination is detected + /// gracefully stops the server manager and waits for all tasks to finish + /// + /// + /// + private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e) + { + ServerManager?.Stop(); + await ApplicationTask; + } + + /// + /// task that initializes application and starts the application monitoring and runtime tasks + /// + /// + private static async Task LaunchAsync() + { + restart: try { ServerManager = ApplicationManager.GetInstance(); var configuration = ServerManager.GetApplicationSettings().Configuration(); + Localization.Configure.Initialize(configuration?.EnableCustomLocale ?? false ? (configuration.CustomLocale ?? "windows-1252") : "windows-1252"); - if (configuration != null) - { - Localization.Configure.Initialize(configuration.EnableCustomLocale ? (configuration.CustomLocale ?? "windows-1252") : "windows-1252"); - } - - else - { - Localization.Configure.Initialize(); - } - - loc = Utilities.CurrentLocalization.LocalizationIndex; - Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); - - CheckDirectories(); - // do any needed migrations - // todo: move out + // do any needed housekeeping file/folder migrations ConfigurationMigration.MoveConfigFolder10518(null); + ConfigurationMigration.CheckDirectories(); - ServerManager.Logger.WriteInfo($"Version is {Version}"); + ServerManager.Logger.WriteInfo(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_VERSION"].FormatExt(Version)); - var api = API.Master.Endpoint.Get(); - - var version = new API.Master.VersionInfo() - { - CurrentVersionStable = 99.99f - }; - - try - { - version = api.GetVersion().Result; - } - - catch (Exception e) - { - ServerManager.Logger.WriteWarning(loc["MANAGER_VERSION_FAIL"]); - while (e.InnerException != null) - { - e = e.InnerException; - } - - ServerManager.Logger.WriteDebug(e.Message); - } - - if (version.CurrentVersionStable == 99.99f) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); - Console.ForegroundColor = ConsoleColor.Gray; - } - -#if !PRERELEASE - else if (version.CurrentVersionStable > Version) - { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); - Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]")); - Console.ForegroundColor = ConsoleColor.Gray; - } -#else - else if (version.CurrentVersionPrerelease > Version) - { - Console.ForegroundColor = ConsoleColor.DarkYellow; - Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); - Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}-pr]")); - Console.ForegroundColor = ConsoleColor.Gray; - } -#endif - else - { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); - Console.ForegroundColor = ConsoleColor.Gray; - } - - ServerManager.Init().Wait(); - - var consoleTask = Task.Run(async () => - { - string userInput; - var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); - - do - { - userInput = Console.ReadLine(); - - if (userInput?.ToLower() == "quit") - { - ServerManager.Stop(); - } - - if (ServerManager.Servers.Count == 0) - { - Console.WriteLine(loc["MANAGER_CONSOLE_NOSERV"]); - continue; - } - - if (userInput?.Length > 0) - { - GameEvent E = new GameEvent() - { - Type = GameEvent.EventType.Command, - Data = userInput, - Origin = Origin, - Owner = ServerManager.Servers[0] - }; - - ServerManager.GetEventHandler().AddEvent(E); - await E.WaitAsync(30 * 1000); - } - Console.Write('>'); - - } while (ServerManager.Running); - }); + await CheckVersion(); + await ServerManager.Init(); } catch (Exception e) { + var loc = Utilities.CurrentLocalization.LocalizationIndex; string failMessage = loc == null ? "Failed to initalize IW4MAdmin" : loc["MANAGER_INIT_FAIL"]; string exitMessage = loc == null ? "Press any key to exit..." : loc["MANAGER_EXIT"]; Console.WriteLine(failMessage); + while (e.InnerException != null) { e = e.InnerException; } + Console.WriteLine(e.Message); Console.WriteLine(exitMessage); Console.ReadKey(); - return; } - if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront) + try { - Task.Run(() => WebfrontCore.Program.Init(ServerManager)); + ApplicationTask = RunApplicationTasksAsync(); + await ApplicationTask; } - OnShutdownComplete.Reset(); - ServerManager.Start(); - ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]); - OnShutdownComplete.Set(); + catch { } + + if (ServerManager.IsRestartRequested) + { + goto restart; + } } - private static void OnCancelKey(object sender, ConsoleCancelEventArgs e) + /// + /// runs the core application tasks + /// + /// + private static async Task RunApplicationTasksAsync() { - ServerManager.Stop(); - OnShutdownComplete.Wait(); + var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront ? + WebfrontCore.Program.Init(ServerManager, ServerManager.CancellationToken) : + Task.CompletedTask; + + var tasks = new[] + { + webfrontTask, + ReadConsoleInput(), + ServerManager.Start(), + }; + + await Task.WhenAll(tasks); + + ServerManager.Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]); } - static void CheckDirectories() + /// + /// checks for latest version of the application + /// notifies user if an update is available + /// + /// + private static async Task CheckVersion() { - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins"))) + var api = API.Master.Endpoint.Get(); + var loc = Utilities.CurrentLocalization.LocalizationIndex; + + var version = new API.Master.VersionInfo() { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins")); + CurrentVersionStable = 99.99f + }; + + try + { + version = await api.GetVersion(); } - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database"))) + catch (Exception e) { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database")); + ServerManager.Logger.WriteWarning(loc["MANAGER_VERSION_FAIL"]); + while (e.InnerException != null) + { + e = e.InnerException; + } + + ServerManager.Logger.WriteDebug(e.Message); } - if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log"))) + if (version.CurrentVersionStable == 99.99f) { - Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log")); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); + Console.ForegroundColor = ConsoleColor.Gray; + } + +#if !PRERELEASE + else if (version.CurrentVersionStable > Version) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); + Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]")); + Console.ForegroundColor = ConsoleColor.Gray; + } +#else + else if (version.CurrentVersionPrerelease > Version) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); + Console.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}-pr]")); + Console.ForegroundColor = ConsoleColor.Gray; + } +#endif + else + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); + Console.ForegroundColor = ConsoleColor.Gray; + } + } + + /// + /// reads input from the console and executes entered commands on the default server + /// + /// + private static async Task ReadConsoleInput() + { + string lastCommand; + var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]); + + while (!ServerManager.CancellationToken.IsCancellationRequested) + { + lastCommand = Console.ReadLine(); + + if (lastCommand?.Length > 0) + { + if (lastCommand?.Length > 0) + { + GameEvent E = new GameEvent() + { + Type = GameEvent.EventType.Command, + Data = lastCommand, + Origin = Origin, + Owner = ServerManager.Servers[0] + }; + + ServerManager.GetEventHandler().AddEvent(E); + await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken); + Console.Write('>'); + } + } } } } diff --git a/Application/Migration/ConfigurationMigration.cs b/Application/Migration/ConfigurationMigration.cs index 8edd25152..487f7e735 100644 --- a/Application/Migration/ConfigurationMigration.cs +++ b/Application/Migration/ConfigurationMigration.cs @@ -15,6 +15,27 @@ namespace IW4MAdmin.Application.Migration /// class ConfigurationMigration { + /// + /// ensures required directories are created + /// + public static void CheckDirectories() + { + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins")); + } + + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database")); + } + + if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log"))) + { + Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log")); + } + } + /// /// moves existing configs from the root folder into a configs folder /// diff --git a/Application/Misc/Logger.cs b/Application/Misc/Logger.cs index 13ae7d3d7..79080c667 100644 --- a/Application/Misc/Logger.cs +++ b/Application/Misc/Logger.cs @@ -55,6 +55,7 @@ namespace IW4MAdmin.Application void Write(string msg, LogType type) { + return; OnLogWriting.Wait(); string stringType = type.ToString(); diff --git a/IW4MAdmin.sln b/IW4MAdmin.sln index 90bfe320b..b96653921 100644 --- a/IW4MAdmin.sln +++ b/IW4MAdmin.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.16 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8B310-269E-46D4-A612-24601F16065F}" EndProject diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 27a6b0988..5aea63a9f 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -20,25 +20,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { public class StatManager { - private ConcurrentDictionary Servers; - private ILogger Log; - private readonly IManager Manager; - + private readonly ConcurrentDictionary _servers; + private readonly ILogger _log; private readonly SemaphoreSlim OnProcessingPenalty; private readonly SemaphoreSlim OnProcessingSensitive; public StatManager(IManager mgr) { - Servers = new ConcurrentDictionary(); - Log = mgr.GetLogger(0); - Manager = mgr; + _servers = new ConcurrentDictionary(); + _log = mgr.GetLogger(0); OnProcessingPenalty = new SemaphoreSlim(1, 1); OnProcessingSensitive = new SemaphoreSlim(1, 1); } public EFClientStatistics GetClientStats(int clientId, long serverId) { - return Servers[serverId].PlayerStats[clientId]; + return _servers[serverId].PlayerStats[clientId]; } public static Expression> GetRankingFunc(long? serverId = null) @@ -135,6 +132,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var iqStatsInfo = (from stat in context.Set() where clientIds.Contains(stat.ClientId) + where stat.Kills > 0 || stat.Deaths > 0 group stat by stat.ClientId into s select new { @@ -242,7 +240,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // check to see if the stats have ever been initialized var serverStats = InitializeServerStats(server.ServerId); - Servers.TryAdd(serverId, new ServerStats(server, serverStats) + _servers.TryAdd(serverId, new ServerStats(server, serverStats) { IsTeamBased = sv.Gametype != "dm" }); @@ -250,8 +248,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception e) { - Log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); - Log.WriteDebug(e.GetExceptionInfo()); + _log.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_ERROR_ADD"]} - {e.Message}"); + _log.WriteDebug(e.GetExceptionInfo()); } } @@ -268,18 +266,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { long serverId = await GetIdForServer(pl.CurrentServer); - if (!Servers.ContainsKey(serverId)) + if (!_servers.ContainsKey(serverId)) { - Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); + _log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found"); return null; } - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; + var playerStats = _servers[serverId].PlayerStats; + var detectionStats = _servers[serverId].PlayerDetections; if (playerStats.ContainsKey(pl.ClientId)) { - Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); + _log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId}"); return playerStats[pl.ClientId]; } @@ -321,7 +319,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) { - Log.WriteWarning("Adding new client to stats failed"); + _log.WriteWarning("Adding new client to stats failed"); } } @@ -329,7 +327,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers { if (!playerStats.TryAdd(clientStats.ClientId, clientStats)) { - Log.WriteWarning("Adding pre-existing client to stats failed"); + _log.WriteWarning("Adding pre-existing client to stats failed"); } } @@ -366,9 +364,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers clientStats.SessionScore = pl.Score; clientStats.LastScore = pl.Score; - if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log, clientStats))) + if (!detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(_log, clientStats))) { - Log.WriteWarning("Could not add client to detection"); + _log.WriteWarning("Could not add client to detection"); } pl.CurrentServer.Logger.WriteInfo($"Adding {pl} to stats"); @@ -379,8 +377,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception ex) { - Log.WriteWarning("Could not add client to stats"); - Log.WriteDebug(ex.GetExceptionInfo()); + _log.WriteWarning("Could not add client to stats"); + _log.WriteDebug(ex.GetExceptionInfo()); } finally @@ -401,9 +399,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers pl.CurrentServer.Logger.WriteInfo($"Removing {pl} from stats"); long serverId = await GetIdForServer(pl.CurrentServer); - var playerStats = Servers[serverId].PlayerStats; - var detectionStats = Servers[serverId].PlayerDetections; - var serverStats = Servers[serverId].ServerStatistics; + var playerStats = _servers[serverId].PlayerStats; + var detectionStats = _servers[serverId].PlayerDetections; + var serverStats = _servers[serverId].ServerStatistics; if (!playerStats.ContainsKey(pl.ClientId)) { @@ -473,8 +471,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (FormatException) { - Log.WriteWarning("Could not parse kill or death origin or viewangle vectors"); - Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAngle - {viewAngles}"); + _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; } @@ -491,7 +489,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (FormatException) { - Log.WriteWarning("Could not parse snapshot angles"); + _log.WriteWarning("Could not parse snapshot angles"); return; } @@ -537,13 +535,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } // incase the add player event get delayed - if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) { await AddPlayer(attacker); } - var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId]; - var clientStats = Servers[serverId].PlayerStats[attacker.ClientId]; + var clientDetection = _servers[serverId].PlayerDetections[attacker.ClientId]; + var clientStats = _servers[serverId].PlayerStats[attacker.ClientId]; using (var ctx = new DatabaseContext(disableTracking: true)) { @@ -598,8 +596,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers catch (Exception ex) { - Log.WriteError("Could not save hit or AC info"); - Log.WriteDebug(ex.GetExceptionInfo()); + _log.WriteError("Could not save hit or AC info"); + _log.WriteDebug(ex.GetExceptionInfo()); } OnProcessingPenalty.Release(1); @@ -627,7 +625,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } }; - await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], penaltyClient, false).WaitAsync(); + await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], penaltyClient, false).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken); if (clientDetection.Tracker.HasChanges) { @@ -644,7 +642,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}"; - await attacker.Flag(flagReason, penaltyClient).WaitAsync(); + await attacker.Flag(flagReason, penaltyClient).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken); if (clientDetection.Tracker.HasChanges) { @@ -714,33 +712,33 @@ namespace IW4MAdmin.Plugins.Stats.Helpers long serverId = await GetIdForServer(attacker.CurrentServer); EFClientStatistics attackerStats = null; - if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(attacker.ClientId)) { attackerStats = await AddPlayer(attacker); } else { - attackerStats = Servers[serverId].PlayerStats[attacker.ClientId]; + attackerStats = _servers[serverId].PlayerStats[attacker.ClientId]; } EFClientStatistics victimStats = null; - if (!Servers[serverId].PlayerStats.ContainsKey(victim.ClientId)) + if (!_servers[serverId].PlayerStats.ContainsKey(victim.ClientId)) { victimStats = await AddPlayer(victim); } else { - victimStats = Servers[serverId].PlayerStats[victim.ClientId]; + victimStats = _servers[serverId].PlayerStats[victim.ClientId]; } #if DEBUG - Log.WriteDebug("Calculating standard kill"); + _log.WriteDebug("Calculating standard kill"); #endif // update the total stats - Servers[serverId].ServerStatistics.TotalKills += 1; + _servers[serverId].ServerStatistics.TotalKills += 1; await Sync(attacker.CurrentServer); // this happens when the round has changed @@ -777,14 +775,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // fixme: why? if (double.IsNaN(victimStats.SPM) || double.IsNaN(victimStats.Skill)) { - Log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + _log.WriteDebug($"[StatManager::AddStandardKill] victim SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); victimStats.SPM = 0.0; victimStats.Skill = 0.0; } if (double.IsNaN(attackerStats.SPM) || double.IsNaN(attackerStats.Skill)) { - Log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); + _log.WriteDebug($"[StatManager::AddStandardKill] attacker SPM/SKILL {victimStats.SPM} {victimStats.Skill}"); attackerStats.SPM = 0.0; attackerStats.Skill = 0.0; } @@ -1014,7 +1012,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats = UpdateStats(attackerStats); // calulate elo - if (Servers[attackerStats.ServerId].PlayerStats.Count > 1) + if (_servers[attackerStats.ServerId].PlayerStats.Count > 1) { #region DEPRECATED /* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats @@ -1095,7 +1093,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers } double killSPM = scoreDifference / timeSinceLastCalc; - double spmMultiplier = 2.934 * Math.Pow(Servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); + double spmMultiplier = 2.934 * Math.Pow(_servers[clientStats.ServerId].TeamCount(clientStats.Team == IW4Info.Team.Allies ? IW4Info.Team.Axis : IW4Info.Team.Allies), -0.454); killSPM *= Math.Max(1, spmMultiplier); // update this for ac tracking @@ -1120,8 +1118,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (clientStats.SPM < 0) { - Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); - Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); + _log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0"); + _log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}"); clientStats.SPM = 0; } @@ -1131,8 +1129,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers // fixme: how does this happen? if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) { - Log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); - Log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); + _log.WriteWarning("[StatManager::UpdateStats] clientStats SPM/Skill NaN"); + _log.WriteDebug($"{killSPM}-{KDRWeight}-{totalPlayTime}-{SPMAgainstPlayWeight}-{clientStats.SPM}-{clientStats.Skill}-{scoreDifference}"); clientStats.SPM = 0; clientStats.Skill = 0; } @@ -1154,7 +1152,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers if (serverStats == null) { - Log.WriteDebug($"Initializing server stats for {serverId}"); + _log.WriteDebug($"Initializing server stats for {serverId}"); // server stats have never been generated before serverStats = new EFServerStatistics() { @@ -1173,7 +1171,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void ResetKillstreaks(long serverId) { - var serverStats = Servers[serverId]; + var serverStats = _servers[serverId]; foreach (var stat in serverStats.PlayerStats.Values) { @@ -1183,7 +1181,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void ResetStats(int clientId, long serverId) { - var stats = Servers[serverId].PlayerStats[clientId]; + var stats = _servers[serverId].PlayerStats[clientId]; stats.Kills = 0; stats.Deaths = 0; stats.SPM = 0; @@ -1221,10 +1219,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers using (var ctx = new DatabaseContext(disableTracking: true)) { var serverSet = ctx.Set(); - serverSet.Update(Servers[serverId].Server); + serverSet.Update(_servers[serverId].Server); var serverStatsSet = ctx.Set(); - serverStatsSet.Update(Servers[serverId].ServerStatistics); + serverStatsSet.Update(_servers[serverId].ServerStatistics); await ctx.SaveChangesAsync(); } @@ -1232,7 +1230,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers public void SetTeamBased(long serverId, bool isTeamBased) { - Servers[serverId].IsTeamBased = isTeamBased; + _servers[serverId].IsTeamBased = isTeamBased; } public static async Task GetIdForServer(Server server) diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index dcc3e1b53..c6faf8e14 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -23,7 +23,22 @@ namespace SharedLibraryCore.Commands public override Task ExecuteAsync(GameEvent E) { - return Task.Run(() => { E.Owner.Manager.Stop(); }); + E.Owner.Manager.Stop(); + return Task.CompletedTask; + } + } + + public class CRestart : Command + { + public CRestart() : + base("restart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_DESC"], "rs", EFClient.Permission.Owner, false) + { } + + public override Task ExecuteAsync(GameEvent E) + { + E.Owner.Manager.Restart(); + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RESTART_SUCCESS"]); + return Task.CompletedTask; } } @@ -120,9 +135,18 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = !(await E.Target.Kick(E.Data, E.Origin).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name)) : - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name)); + switch ((await E.Target.Kick(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name)); + break; + } } } @@ -186,9 +210,18 @@ namespace SharedLibraryCore.Commands else { - var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText())) : - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name)); + switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText())); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name)); + break; + } } } } @@ -214,9 +247,18 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { - var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ? - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name)) : + switch ((await E.Target.Ban(E.Data, E.Origin, false).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Exception: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + default: E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"].FormatExt(E.Target.Name)); + break; + } } } @@ -240,12 +282,21 @@ namespace SharedLibraryCore.Commands public override async Task ExecuteAsync(GameEvent E) { + // todo: don't do the lookup here var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId); if (penalties.Where(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan).FirstOrDefault() != null) { - await E.Target.Unban(E.Data, E.Origin).WaitAsync(); - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target)); + switch ((await E.Target.Unban(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) + { + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target)); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; + } } + else { E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_FAIL"].FormatExt(E.Target)); @@ -587,12 +638,13 @@ namespace SharedLibraryCore.Commands if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) { E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"].FormatExt(m.Alias)); - await Task.Delay(5000); + await Task.Delay((int)(Utilities.DefaultCommandTimeout.TotalMilliseconds / 2.0)); await E.Owner.LoadMap(m.Name); return; } } + // todo: this can be moved into a single statement E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"].FormatExt(newMap)); await Task.Delay(5000); await E.Owner.LoadMap(newMap); @@ -720,27 +772,23 @@ namespace SharedLibraryCore.Commands }) { } - public override Task ExecuteAsync(GameEvent E) + public override async Task ExecuteAsync(GameEvent E) { - var flagEvent = E.Target.Flag(E.Data, E.Origin); - - if (E.FailReason == GameEvent.EventFailReason.Permission) + switch ((await E.Target.Flag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name)); + case GameEvent.EventFailReason.Permission: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_ALREADYFLAGGED"]}"); + break; + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name)); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; } - - else if (E.FailReason == GameEvent.EventFailReason.Invalid) - { - E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_ALREADYFLAGGED"]}"); - } - - else - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name)); - } - - return Task.CompletedTask; - } } @@ -757,27 +805,23 @@ namespace SharedLibraryCore.Commands }) { } - public override Task ExecuteAsync(GameEvent E) + public override async Task ExecuteAsync(GameEvent E) { - var unflagEvent = E.Target.Unflag(E.Data, E.Origin); - - if (unflagEvent.FailReason == GameEvent.EventFailReason.Permission) + switch ((await E.Target.Unflag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason) { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name)); + case GameEvent.EventFailReason.None: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Permission: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]); + break; + default: + E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_INGAME"]); + break; } - - else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid) - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_NOTFLAGGED"]); - } - - else - { - E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name)); - } - - return Task.CompletedTask; - // todo: update immediately? } } @@ -808,46 +852,30 @@ namespace SharedLibraryCore.Commands return; } - var reportEvent = commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin); + bool success = false; - if (reportEvent.FailReason == GameEvent.EventFailReason.Permission) + switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason) { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name)); + case GameEvent.EventFailReason.None: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); + success = true; + break; + case GameEvent.EventFailReason.Exception: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]); + break; + case GameEvent.EventFailReason.Permission: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name)); + break; + case GameEvent.EventFailReason.Invalid: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]); + break; + case GameEvent.EventFailReason.Throttle: + commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]); + break; } - else if (reportEvent.FailReason == GameEvent.EventFailReason.Invalid) + if (success) { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_SELF"]); - } - - else if (reportEvent.FailReason == GameEvent.EventFailReason.Throttle) - { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_TOOMANY"]); - } - - else if (reportEvent.Failed) - { - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL_DUPLICATE"]); - } - - else - { - // todo: move into server - Penalty newReport = new Penalty() - { - Type = Penalty.PenaltyType.Report, - Expires = DateTime.UtcNow, - Offender = commandEvent.Target, - Offense = commandEvent.Data, - Punisher = commandEvent.Origin, - Active = true, - When = DateTime.UtcNow, - Link = commandEvent.Target.AliasLink - }; - - await commandEvent.Owner.Manager.GetPenaltyService().Create(newReport); - - commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_SUCCESS"]); commandEvent.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", commandEvent.Origin.Name, commandEvent.Target.Name, commandEvent.Data)); } } @@ -1118,10 +1146,6 @@ namespace SharedLibraryCore.Commands E.Origin.Password = hashedPassword[0]; E.Origin.PasswordSalt = hashedPassword[1]; - // update the password for the client in privileged - //E.Owner.Manager.PrivilegedClients[E.Origin.ClientId].Password = hashedPassword[0]; - //E.Owner.Manager.PrivilegedClients[E.Origin.ClientId].PasswordSalt = hashedPassword[1]; - await E.Owner.Manager.GetClientService().Update(E.Origin); E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PASSWORD_SUCCESS"]); } diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index b9d9b10ff..bf644876b 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -29,7 +29,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true activeContextCount++; - Console.WriteLine($"Initialized DB Context #{activeContextCount}"); + //Console.WriteLine($"Initialized DB Context #{activeContextCount}"); #endif } @@ -37,7 +37,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true activeContextCount++; - Console.WriteLine($"Initialized DB Context #{activeContextCount}"); + //Console.WriteLine($"Initialized DB Context #{activeContextCount}"); #endif } @@ -45,7 +45,7 @@ namespace SharedLibraryCore.Database { #if DEBUG == true - Console.WriteLine($"Disposed DB Context #{activeContextCount}"); + //Console.WriteLine($"Disposed DB Context #{activeContextCount}"); activeContextCount--; #endif } diff --git a/SharedLibraryCore/Events/GameEvent.cs b/SharedLibraryCore/Events/GameEvent.cs index a77eb427a..9cafac0a6 100644 --- a/SharedLibraryCore/Events/GameEvent.cs +++ b/SharedLibraryCore/Events/GameEvent.cs @@ -212,11 +212,11 @@ namespace SharedLibraryCore /// asynchronously wait for GameEvent to be processed /// /// waitable task - public Task WaitAsync(int timeOut = int.MaxValue) + public Task WaitAsync(TimeSpan timeSpan, CancellationToken token) { return Task.Run(() => { - OnProcessed.Wait(timeOut); + OnProcessed.Wait(timeSpan, token); return this; }); } diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index 0f28d960d..5f2e1a4fb 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -7,14 +7,16 @@ using SharedLibraryCore.Configuration; using System.Reflection; using SharedLibraryCore.Database.Models; using System.Collections.Concurrent; +using System.Threading; namespace SharedLibraryCore.Interfaces { public interface IManager { Task Init(); - void Start(); + Task Start(); void Stop(); + void Restart(); ILogger GetLogger(long serverId); IList GetServers(); IList GetCommands(); @@ -29,11 +31,6 @@ namespace SharedLibraryCore.Interfaces /// /// EventHandler for the manager IEventHandler GetEventHandler(); - /// - /// Signal to the manager that event(s) needs to be processed - /// - void SetHasEvent(); - bool ShutdownRequested(); IList GetPluginAssemblies(); /// /// provides a page list to add and remove from @@ -47,5 +44,7 @@ namespace SharedLibraryCore.Interfaces string Version { get;} ITokenAuthentication TokenAuthenticator { get; } string ExternalIPAddress { get; } + CancellationToken CancellationToken { get; } + bool IsRestartRequested { get; } } } diff --git a/SharedLibraryCore/Objects/EFClient.cs b/SharedLibraryCore/Objects/EFClient.cs index e1203aca6..562657302 100644 --- a/SharedLibraryCore/Objects/EFClient.cs +++ b/SharedLibraryCore/Objects/EFClient.cs @@ -546,6 +546,7 @@ namespace SharedLibraryCore.Database.Models profileBan = (await CurrentServer.Manager .GetPenaltyService() .GetActivePenaltiesAsync(AliasLinkId)) + .OrderByDescending(_penalty => _penalty.When) .FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Ban); CurrentServer.Logger.WriteWarning($"Client {this} is GUID banned, but no previous penalty exists for their ban"); @@ -596,18 +597,6 @@ namespace SharedLibraryCore.Database.Models // 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); - #region CLIENT_LINKED_TEMPBAN - var tempBan = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan); - - // they have an active tempban tied to their AliasLink - if (tempBan != null) - { - CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not"); - TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient); - return false; - } - #endregion - #region CLIENT_LINKED_BAN var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban); @@ -642,6 +631,20 @@ namespace SharedLibraryCore.Database.Models } #endregion + #region CLIENT_LINKED_TEMPBAN + var tempBan = activePenalties + .OrderByDescending(_penalty => _penalty.When) + .FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan); + + // they have an active tempban tied to their AliasLink + if (tempBan != null) + { + CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not"); + TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient); + return false; + } + #endregion + #region CLIENT_LINKED_FLAG if (Level != Permission.Flagged) { diff --git a/SharedLibraryCore/ScriptPlugin.cs b/SharedLibraryCore/ScriptPlugin.cs index ed5bf3a1b..aecebd39b 100644 --- a/SharedLibraryCore/ScriptPlugin.cs +++ b/SharedLibraryCore/ScriptPlugin.cs @@ -42,7 +42,7 @@ namespace SharedLibraryCore } catch (Exception ex) { - Manager.GetLogger(0).WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Name}"); + Manager.GetLogger(0).WriteError(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(Name)); Manager.GetLogger(0).WriteDebug(ex.Message); } } diff --git a/SharedLibraryCore/Server.cs b/SharedLibraryCore/Server.cs index 3bb26ba58..dfc0f4ca9 100644 --- a/SharedLibraryCore/Server.cs +++ b/SharedLibraryCore/Server.cs @@ -23,10 +23,10 @@ namespace SharedLibraryCore IW4 = 2, IW5 = 3, IW6 = 4, - T4 = 5, - T5 = 6, - T6 = 7, - T7 = 8 + T4 = 5, + T5 = 6, + T6 = 7, + T7 = 8 } public Server(IManager mgr, ServerConfiguration config) @@ -159,14 +159,16 @@ namespace SharedLibraryCore if (Target.Level == EFClient.Permission.Console) { - Console.ForegroundColor = ConsoleColor.Cyan; + Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(Message.StripColors()); Console.ForegroundColor = ConsoleColor.Gray; } // prevent this from queueing up too many command responses if (CommandResult.Count > 15) + { CommandResult.RemoveAt(0); + } // it was a remote command so we need to add it to the command result queue if (Target.ClientNumber < 0) diff --git a/SharedLibraryCore/Services/ClientService.cs b/SharedLibraryCore/Services/ClientService.cs index e0f56ca64..e042ba5a0 100644 --- a/SharedLibraryCore/Services/ClientService.cs +++ b/SharedLibraryCore/Services/ClientService.cs @@ -447,8 +447,6 @@ namespace SharedLibraryCore.Services return new List(); } - identifier = identifier.ToLower(); - using (var context = new DatabaseContext(disableTracking: true)) { long? networkId = null; @@ -472,7 +470,7 @@ namespace SharedLibraryCore.Services // todo maybe not make it start with wildcard? else { - iqLinkIds = iqLinkIds.Where(_alias => EF.Functions.Like(_alias.Name, $"%{identifier}%")); + iqLinkIds = iqLinkIds.Where(_alias => EF.Functions.Like(_alias.Name.ToLower(), $"%{identifier.ToLower()}%")); } var linkIds = await iqLinkIds diff --git a/SharedLibraryCore/Utilities.cs b/SharedLibraryCore/Utilities.cs index dfffc0ff5..65b9e30f7 100644 --- a/SharedLibraryCore/Utilities.cs +++ b/SharedLibraryCore/Utilities.cs @@ -30,6 +30,8 @@ namespace SharedLibraryCore #endif public static Encoding EncodingType; public static Localization.Layout CurrentLocalization = new Localization.Layout(new Dictionary()); + public static TimeSpan DefaultCommandTimeout = new TimeSpan(0, 0, 10); + public static EFClient IW4MAdminClient(Server server = null) { return new EFClient() @@ -320,7 +322,7 @@ namespace SharedLibraryCore if (Elapsed.TotalSeconds < 30) { - return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"]; + return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"] + ago; } if (Elapsed.TotalMinutes < 120) { @@ -783,6 +785,11 @@ namespace SharedLibraryCore } } + /// + /// Determines if the given message is a quick message + /// + /// + /// true if the public static bool IsQuickMessage(this string message) { return Regex.IsMatch(message, @"^\u0014(?:[A-Z]|_)+$"); diff --git a/WebfrontCore/Controllers/ConsoleController.cs b/WebfrontCore/Controllers/ConsoleController.cs index e85dacd0b..ca8b5c85a 100644 --- a/WebfrontCore/Controllers/ConsoleController.cs +++ b/WebfrontCore/Controllers/ConsoleController.cs @@ -53,7 +53,7 @@ namespace WebfrontCore.Controllers Manager.GetEventHandler().AddEvent(remoteEvent); List response; // wait for the event to process - if (!(await remoteEvent.WaitAsync(60 * 1000)).Failed) + if (!(await remoteEvent.WaitAsync(Utilities.DefaultCommandTimeout, server.Manager.CancellationToken)).Failed) { response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); diff --git a/WebfrontCore/Program.cs b/WebfrontCore/Program.cs index cb093725c..8b5fd970a 100644 --- a/WebfrontCore/Program.cs +++ b/WebfrontCore/Program.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using SharedLibraryCore.Interfaces; @@ -9,18 +11,18 @@ namespace WebfrontCore { public static IManager Manager; - static void Main(string[] args) + static void Main() { throw new System.Exception("Webfront core cannot be run as a standalone application"); } - public static void Init(IManager mgr) + public static Task Init(IManager mgr, CancellationToken cancellationToken) { Manager = mgr; - BuildWebHost().Run(); + return BuildWebHost().RunAsync(cancellationToken); } - public static IWebHost BuildWebHost() + private static IWebHost BuildWebHost() { var config = new ConfigurationBuilder() .AddEnvironmentVariables()