From aa62646f0f021e69718708322bf72933bfde490d Mon Sep 17 00:00:00 2001 From: Brett Hewitson Date: Sun, 5 Dec 2021 15:08:37 +1000 Subject: [PATCH] Finished the remaing bot commands --- src/ARKServerManager/Art/favicon.ico | Bin 15086 -> 15086 bytes .../Globalization/en-US/en-US.xaml | 6 +- src/ARKServerManager/Lib/ServerApp.cs | 201 +++++---- .../Lib/ServerProfileSnapshot.cs | 4 +- src/ARKServerManager/Lib/ServerRuntime.cs | 18 +- .../Utils/DiscordBotHelper.cs | 380 +++++++++++++++++- .../Windows/RCONWindow.xaml.cs | 2 +- .../Windows/ServerMonitorWindow.xaml.cs | 2 +- .../Windows/ServerSettingsControl.xaml.cs | 2 +- .../Globalization/en-US/en-US.xaml | 6 +- src/ConanServerManager/Lib/ServerApp.cs | 215 +++++----- .../Lib/ServerProfileSnapshot.cs | 4 +- src/ConanServerManager/Lib/ServerRuntime.cs | 21 +- .../Utils/DiscordBotHelper.cs | 380 +++++++++++++++++- .../Windows/RconWindow.xaml.cs | 2 +- .../Windows/ServerMonitorWindow.xaml.cs | 2 +- .../Windows/ServerSettingsControl.xaml.cs | 2 +- .../Delegates/HandleCommandDelegate.cs | 3 +- .../Enums/CommandType.cs | 1 + .../Interfaces/IServerManagerBot.cs | 2 + .../Modules/ServerCommandModule.cs | 49 ++- .../Modules/ServerQueryModule.cs | 12 +- src/ServerManager.Discord/ServerManagerBot.cs | 14 +- 23 files changed, 1041 insertions(+), 287 deletions(-) diff --git a/src/ARKServerManager/Art/favicon.ico b/src/ARKServerManager/Art/favicon.ico index 2ed732cb19e52d16d183bc0cce3a936d5a1be24e..9f1a57607d3cfec64f75c1c1f35e2819809b49d4 100644 GIT binary patch delta 159 zcmaD?`mR))fq{{MnL$7RL@O|4urM%~F)%PFC_wltfa3FjVg?34{zUEMjprsYPd*{y zFgbz6is}FV$#YmHZPsIx;Gg_JBn^snIQ~PyWH-?hoBbrz6eph%sX|f#Q>nMP#4?Fd J0;U|D1^{0sMwb8p delta 117 zcmaD?`mR))fq{{MnL$8+0SFWrGFTWG%s?y!2!91od>&BDzyQdfsGU6d9*e`qzDdlJ z6IiU67#JqcVVN{pPi)TQI?The '{0}' command requires a profile id. Profile '{0}' was not found or is not associated with the channel. - Profile '{0}' must be '{1}' to perform the command. + Profile '{0}' is in a state '{1}' that cannot run this command. Profile '{0}' is currently being updated. Call to server '{0}' failed. A backup request for server '{0}' has been sent. + A restart request for server '{0}' has been sent. + A shutdown request for server '{0}' has been sent. + A start request for server '{0}' has been sent. + A stop request for server '{0}' has been sent. An update request for server '{0}' has been sent. Count: diff --git a/src/ARKServerManager/Lib/ServerApp.cs b/src/ARKServerManager/Lib/ServerApp.cs index 58f637fd..0f433ad8 100644 --- a/src/ARKServerManager/Lib/ServerApp.cs +++ b/src/ARKServerManager/Lib/ServerApp.cs @@ -114,7 +114,7 @@ namespace ServerManagerTool.Lib _startTime = DateTime.Now; } - private void BackupServer() + private void BackupServer(CancellationToken cancellationToken) { if (_profile == null || _profile.SotFEnabled) { @@ -142,65 +142,65 @@ namespace ServerManagerTool.Lib if (_serverRunning) { - // check if RCON is enabled - if (_profile.RCONEnabled) + try { - try + emailMessage.AppendLine(); + + var sent = false; + + // perform a world save + if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage)) { - try + ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage); + sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, cancellationToken).Result; + if (sent) { - emailMessage.AppendLine(); - - var sent = false; - - // perform a world save - if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage)) - { - ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage); - sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None).Result; - if (sent) - { - emailMessage.AppendLine("sent server save message."); - } - } - - sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result; - if (sent) - { - emailMessage.AppendLine("sent server save command."); - Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait(); - } - } - catch (Exception ex) - { - Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}"); + emailMessage.AppendLine("sent server save message."); } } - finally + + sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result; + if (sent) { - CloseRconConsole(); + emailMessage.AppendLine("sent server save command."); + Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait(); } } - else + catch (Exception ex) { - LogProfileMessage("RCON not enabled."); + Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}"); } } if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } // make a backup of the current profile and config files. CreateProfileBackupArchiveFile(_profile); if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } // make a backup of the current world file. CreateServerBackupArchiveFile(emailMessage, _profile); if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } if (Config.Default.EmailNotify_AutoBackup) { @@ -269,7 +269,7 @@ namespace ServerManagerTool.Lib try { ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); - UpgradeLocal(true, cancellationToken, true); + UpgradeLocal(true, true, cancellationToken); } finally { @@ -310,17 +310,20 @@ namespace ServerManagerTool.Lib return; } - // check if the server was previously running before the update. - if (!_serverRunning && !_profile.AutoRestartIfShutdown) + // check if the server was previously running. + if (!_serverRunning) { - LogProfileMessage("Server was not running, server will not be started."); + if (_profile.AutoRestartIfShutdown) + { + LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE."); + } + else + { + LogProfileMessage("Server was not running, server will not be started."); - ExitCode = EXITCODE_NORMALEXIT; - return; - } - if (!_serverRunning && _profile.AutoRestartIfShutdown) - { - LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE."); + ExitCode = EXITCODE_NORMALEXIT; + return; + } } // Find the server process. @@ -404,7 +407,7 @@ namespace ServerManagerTool.Lib try { // create a connection to the server - var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.QueryPort); + var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.QueryPort); gameServer = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint); // check if there is a shutdown reason @@ -417,10 +420,6 @@ namespace ServerManagerTool.Lib } LogProfileMessage("Starting shutdown timer..."); - if (!CheckForOnlinePlayers) - { - LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check."); - } var minutesLeft = ShutdownInterval; while (minutesLeft > 0) @@ -432,7 +431,7 @@ namespace ServerManagerTool.Lib if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) { ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage); - SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait(); + SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait(); } ExitCode = EXITCODE_CANCELLED; @@ -462,7 +461,8 @@ namespace ServerManagerTool.Lib } else { - Debug.WriteLine($"CheckForOnlinePlayers disabled"); + Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled."); + break; } var message = string.Empty; @@ -536,7 +536,7 @@ namespace ServerManagerTool.Lib { LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage); ProcessAlert(AlertType.ShutdownMessage, Config.Default.ServerShutdown_WorldSaveMessage); - SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait(); + SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait(cancellationToken); } if (SendCommandAsync(Config.Default.ServerSaveCommand, false).Result) @@ -561,7 +561,7 @@ namespace ServerManagerTool.Lib if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) { ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage); - SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait(); + SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait(); } ExitCode = EXITCODE_CANCELLED; @@ -587,8 +587,6 @@ namespace ServerManagerTool.Lib } finally { - CloseRconConsole(); - gameServer?.Dispose(); gameServer = null; } @@ -600,11 +598,9 @@ namespace ServerManagerTool.Lib if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) { ProcessAlert(AlertType.Shutdown, Config.Default.ServerShutdown_CancelMessage); - SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait(); + SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, cancellationToken).Wait(); } - CloseRconConsole(); - ExitCode = EXITCODE_CANCELLED; return; } @@ -653,8 +649,6 @@ namespace ServerManagerTool.Lib LogProfileMessage("Exiting server timed out, attempting to close the server."); } - CloseRconConsole(); - // Method 2 - Close the process sent = process.CloseMainWindow(); @@ -721,7 +715,7 @@ namespace ServerManagerTool.Lib ExitCode = EXITCODE_SHUTDOWN_TIMEOUT; } - private void UpgradeLocal(bool validate, CancellationToken cancellationToken, bool updateMods) + private void UpgradeLocal(bool validate, bool updateMods, CancellationToken cancellationToken) { if (_profile == null) { @@ -1250,7 +1244,7 @@ namespace ServerManagerTool.Lib { // perform a steamcmd validate to confirm all the files LogProfileMessage("Validating server files (*new*)."); - UpgradeLocal(true, CancellationToken.None, false); + UpgradeLocal(true, false, CancellationToken.None); LogProfileMessage("Validated server files (*new*)."); } @@ -1776,17 +1770,6 @@ namespace ServerManagerTool.Lib ExitCode = EXITCODE_NORMALEXIT; } - private void CloseRconConsole() - { - if (_rconConsole != null) - { - _rconConsole.Dispose(); - _rconConsole = null; - - Task.Delay(1000).Wait(); - } - } - public void CheckServerWorldFileExists(ServerProfileSnapshot profile = null) { // do nothing if profile is null or SotF @@ -2578,39 +2561,42 @@ namespace ServerManagerTool.Lib int rconRetries = 0; int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1; - while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) + try { - SetupRconConsole(); - - if (_rconConsole == null) + while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) { - LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); -#if DEBUG - LogProfileMessage("RCON connection not created.", false); -#endif - rconRetries++; - } - else - { - rconRetries = 0; - try - { - _rconConsole.SendCommand(command); - LogProfileMessage($"RCON> {command}"); + SetupRconConsole(); - return true; - } - catch (Exception ex) + if (_rconConsole == null) { - LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); -#if DEBUG - LogProfileMessage($"{ex.Message}", false); -#endif + LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); + LogProfileMessage("RCON connection not created.", false); + rconRetries++; } + else + { + rconRetries = 0; + try + { + _rconConsole.SendCommand(command); + LogProfileMessage($"RCON> {command}"); - retries++; + return true; + } + catch (Exception ex) + { + LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); + LogProfileMessage($"{ex.Message}", false); + } + + retries++; + } } } + finally + { + CloseRconConsole(); + } return false; } @@ -2672,6 +2658,17 @@ namespace ServerManagerTool.Lib } } + private void CloseRconConsole() + { + if (_rconConsole != null) + { + _rconConsole.Dispose(); + _rconConsole = null; + + Task.Delay(1000).Wait(); + } + } + private void SetupRconConsole() { CloseRconConsole(); @@ -2681,7 +2678,7 @@ namespace ServerManagerTool.Lib try { - var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.RCONPort); + var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.RCONPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000); if (server == null) { @@ -2718,7 +2715,7 @@ namespace ServerManagerTool.Lib } } - public int PerformProfileBackup(ServerProfileSnapshot profile) + public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken) { _profile = profile; @@ -2743,7 +2740,7 @@ namespace ServerManagerTool.Lib // check if the mutex was established if (createdNew) { - BackupServer(); + BackupServer(cancellationToken); if (ExitCode != EXITCODE_NORMALEXIT) { @@ -3099,7 +3096,7 @@ namespace ServerManagerTool.Lib SendEmails = true, ServerProcess = ServerProcessType.AutoBackup }; - exitCodes.TryAdd(profile, app.PerformProfileBackup(profile)); + exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None)); }); foreach (var profile in _profiles.Keys) diff --git a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs index 97367600..0d2238ec 100644 --- a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs +++ b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs @@ -20,7 +20,7 @@ namespace ServerManagerTool.Lib public string AdminPassword; public string ServerName; public string ServerArgs; - public string ServerIP; + public IPAddress ServerIPAddress; public int ServerPort; public int ServerPeerPort; public int QueryPort; @@ -71,7 +71,7 @@ namespace ServerManagerTool.Lib AdminPassword = profile.AdminPassword, ServerName = profile.ServerName, ServerArgs = profile.GetServerArgs(), - ServerIP = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback.ToString() : profile.ServerIP.Trim(), + ServerIPAddress = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback : IPAddress.TryParse(profile.ServerIP.Trim(), out IPAddress ipAddress) ? ipAddress : IPAddress.Loopback, ServerPort = profile.ServerPort, ServerPeerPort = profile.ServerPeerPort, QueryPort = profile.QueryPort, diff --git a/src/ARKServerManager/Lib/ServerRuntime.cs b/src/ARKServerManager/Lib/ServerRuntime.cs index e69b802e..b1276696 100644 --- a/src/ARKServerManager/Lib/ServerRuntime.cs +++ b/src/ARKServerManager/Lib/ServerRuntime.cs @@ -169,6 +169,9 @@ namespace ServerManagerTool.Lib ServerProfile.ServerIPProperty, ServerProfile.MaxPlayersProperty, + ServerProfile.ServerPasswordProperty, + ServerProfile.AdminPasswordProperty, + ServerProfile.ServerMapProperty, ServerProfile.ServerModIdsProperty, ServerProfile.TotalConversionModIdProperty, @@ -177,7 +180,7 @@ namespace ServerManagerTool.Lib }, (s, p) => { - if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown) + if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown || Status == ServerStatus.Updating) { AttachToProfileCore(profile); } @@ -198,16 +201,7 @@ namespace ServerManagerTool.Lib return; } - if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress)) - { - // Use the explicit Server IP - localServerQueryEndPoint = new IPEndPoint(localServerIpAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); - } - else - { - // No Server IP specified, use Loopback - localServerQueryEndPoint = new IPEndPoint(IPAddress.Loopback, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); - } + localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); // // Get the public endpoint for querying Steam @@ -1093,7 +1087,7 @@ namespace ServerManagerTool.Lib try { - var endPoint = new IPEndPoint(IPAddress.Parse(this.ProfileSnapshot.ServerIP), this.ProfileSnapshot.RCONPort); + var endPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, this.ProfileSnapshot.RCONPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000); if (server == null) diff --git a/src/ARKServerManager/Utils/DiscordBotHelper.cs b/src/ARKServerManager/Utils/DiscordBotHelper.cs index 90fa05ea..648bd133 100644 --- a/src/ARKServerManager/Utils/DiscordBotHelper.cs +++ b/src/ARKServerManager/Utils/DiscordBotHelper.cs @@ -23,7 +23,7 @@ namespace ServerManagerTool.Utils public static bool HasRunningCommands => _currentProfileCommands.Count > 0; - public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) + public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token) { // check if incoming values are valid if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId)) @@ -49,15 +49,17 @@ namespace ServerManagerTool.Utils return GetServerStatus(channelId, profileId); case CommandType.Backup: - return BackupServer(channelId, profileId); + return BackupServer(channelId, profileId, token); + case CommandType.Restart: + return RestartServer(channelId, profileId, token); case CommandType.Shutdown: - return ShutdownServer(channelId, profileId); + return ShutdownServer(channelId, profileId, token); case CommandType.Stop: - return StopServer(channelId, profileId); + return StopServer(channelId, profileId, token); case CommandType.Start: - return StartServer(channelId, profileId); + return StartServer(channelId, profileId, token); case CommandType.Update: - return UpdateServer(channelId, profileId); + return UpdateServer(channelId, profileId, token); default: return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) }; @@ -108,6 +110,17 @@ namespace ServerManagerTool.Utils throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + } + serverName = server.Profile.ServerName; if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) { @@ -183,7 +196,7 @@ namespace ServerManagerTool.Utils return response; } - private static IList BackupServer(string channelId, string profileId) + private static IList BackupServer(string channelId, string profileId, CancellationToken token) { if (string.IsNullOrWhiteSpace(profileId)) { @@ -211,6 +224,16 @@ namespace ServerManagerTool.Utils throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + } + profile = ServerProfileSnapshot.Create(server.Profile); }).Wait(); @@ -223,11 +246,19 @@ namespace ServerManagerTool.Utils SendAlerts = true, SendEmails = false, ServerProcess = ServerProcessType.Backup, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } }; task = Task.Run(() => { - app.PerformProfileBackup(profile); + app.PerformProfileBackup(profile, token); _currentProfileCommands.Remove(profileId); }); @@ -244,22 +275,337 @@ namespace ServerManagerTool.Utils } } - private static IList ShutdownServer(string channelId, string profileId) + private static IList RestartServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Shutdown) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Restart) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Restart); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + profile.AutoRestartIfShutdown = true; + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Restart, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, true, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_RestartRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList StopServer(string channelId, string profileId) + private static IList ShutdownServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Stop) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Shutdown); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Shutdown, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_ShutdownRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList StartServer(string channelId, string profileId) + private static IList StopServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Start) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Shutdown); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Shutdown, + ShutdownInterval = 0, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StopRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList UpdateServer(string channelId, string profileId) + private static IList StartServer(string channelId, string profileId, CancellationToken token) + { + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Start) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Start); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Running: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + profile.AutoRestartIfShutdown = true; + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Restart, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, true, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StartRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } + } + + private static IList UpdateServer(string channelId, string profileId, CancellationToken token) { if (string.IsNullOrWhiteSpace(profileId)) { @@ -293,7 +639,7 @@ namespace ServerManagerTool.Utils case ServerStatus.Initializing: case ServerStatus.Stopping: case ServerStatus.Unknown: - throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, ServerRuntime.GetServerStatusString(ServerStatus.Stopped))); + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); case ServerStatus.Running: performRestart = true; @@ -327,7 +673,7 @@ namespace ServerManagerTool.Utils task = Task.Run(() => { - app.PerformProfileShutdown(profile, performRestart, true, false, CancellationToken.None); + app.PerformProfileShutdown(profile, performRestart, true, false, token); _currentProfileCommands.Remove(profileId); }); diff --git a/src/ARKServerManager/Windows/RCONWindow.xaml.cs b/src/ARKServerManager/Windows/RCONWindow.xaml.cs index 05ef0af8..e2ec9fbf 100644 --- a/src/ARKServerManager/Windows/RCONWindow.xaml.cs +++ b/src/ARKServerManager/Windows/RCONWindow.xaml.cs @@ -664,7 +664,7 @@ namespace ServerManagerTool ProfileId = server.Runtime.ProfileSnapshot.ProfileId, ProfileName = server.Runtime.ProfileSnapshot.ProfileName, MaxPlayers = server.Runtime.MaxPlayers, - RCONHost = server.Runtime.ProfileSnapshot.ServerIP, + RCONHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(), RCONPort = server.Runtime.ProfileSnapshot.RCONPort, PGM_Enabled = server.Profile.PGM_Enabled, diff --git a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs index 35f78c99..a72fba33 100644 --- a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs +++ b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs @@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows var profile = ServerProfileSnapshot.Create(server.Profile); - var exitCode = await Task.Run(() => app.PerformProfileBackup(profile)); + var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None)); if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED) { throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); diff --git a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs index f78a1888..0175709a 100644 --- a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs +++ b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs @@ -1099,7 +1099,7 @@ namespace ServerManagerTool var profile = ServerProfileSnapshot.Create(Server.Profile); - var exitCode = await Task.Run(() => app.PerformProfileBackup(profile)); + var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None)); if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED) throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); diff --git a/src/ConanServerManager/Globalization/en-US/en-US.xaml b/src/ConanServerManager/Globalization/en-US/en-US.xaml index 831ab091..134d9f4c 100644 --- a/src/ConanServerManager/Globalization/en-US/en-US.xaml +++ b/src/ConanServerManager/Globalization/en-US/en-US.xaml @@ -1225,11 +1225,15 @@ The '{0}' command requires a profile id. Profile '{0}' was not found or is not associated with the channel. - Profile '{0}' must be '{1}' to perform the command. + Profile '{0}' is in a state '{1}' that cannot run this command. Profile '{0}' is currently being updated. Call to server '{0}' failed. A backup request for server '{0}' has been sent. + A restart request for server '{0}' has been sent. + A shutdown request for server '{0}' has been sent. + A start request for server '{0}' has been sent. + A stop request for server '{0}' has been sent. An update request for server '{0}' has been sent. Count: diff --git a/src/ConanServerManager/Lib/ServerApp.cs b/src/ConanServerManager/Lib/ServerApp.cs index cc50048a..7b5be7cb 100644 --- a/src/ConanServerManager/Lib/ServerApp.cs +++ b/src/ConanServerManager/Lib/ServerApp.cs @@ -117,7 +117,7 @@ namespace ServerManagerTool.Lib _startTime = DateTime.Now; } - private void BackupServer() + private void BackupServer(CancellationToken cancellationToken) { if (_profile == null) { @@ -146,58 +146,67 @@ namespace ServerManagerTool.Lib if (_serverRunning) { - // check if RCON is enabled - //if (_profile.RconEnabled) - //{ - // try - // { - // emailMessage.AppendLine(); + try + { + emailMessage.AppendLine(); - // // perform a world save - // if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage)) - // { - // ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage); - // sent = SendMessage(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None); - // if (sent) - // { - // emailMessage.AppendLine("sent server save message."); - // } - // } + var sent = false; - // sent = SendCommand(Config.Default.ServerSaveCommand, false); - // if (sent) - // { - // emailMessage.AppendLine("sent server save command."); - // Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait(); - // } - // } - // catch (Exception ex) - // { - // CloseRconConsole(); + // perform a world save + if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage)) + { + ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage); + sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None).Result; + if (sent) + { + emailMessage.AppendLine("sent server save message."); + } + } - // Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}"); - // } - //} - //else - //{ - // LogProfileMessage("RCON not enabled."); - //} + sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result; + if (sent) + { + emailMessage.AppendLine("sent server save command."); + Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait(); + } + } + catch (Exception ex) + { + CloseRconConsole(); + + Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}"); + } } if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } // make a backup of the current profile and config files. CreateProfileBackupArchiveFile(); if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } // make a backup of the current world file. CreateServerBackupArchiveFile(emailMessage); if (ExitCode != EXITCODE_NORMALEXIT) return; + if (cancellationToken.IsCancellationRequested) + { + ExitCode = EXITCODE_CANCELLED; + return; + } if (Config.Default.EmailNotify_AutoBackup) { @@ -266,7 +275,7 @@ namespace ServerManagerTool.Lib try { ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); - UpgradeLocal(true, steamCmdRemoveQuit, cancellationToken, true); + UpgradeLocal(true, true, steamCmdRemoveQuit, cancellationToken); } finally { @@ -307,17 +316,20 @@ namespace ServerManagerTool.Lib return; } - // check if the server was previously running before the update. - if (!_serverRunning && !_profile.AutoRestartIfShutdown) + // check if the server was previously running. + if (!_serverRunning) { - LogProfileMessage("Server was not running, server will not be started."); + if (_profile.AutoRestartIfShutdown) + { + LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE."); + } + else + { + LogProfileMessage("Server was not running, server will not be started."); - ExitCode = EXITCODE_NORMALEXIT; - return; - } - if (!_serverRunning && _profile.AutoRestartIfShutdown) - { - LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE."); + ExitCode = EXITCODE_NORMALEXIT; + return; + } } // Find the server process. @@ -390,10 +402,15 @@ namespace ServerManagerTool.Lib _serverRunning = true; LogProfileMessage($"Server process found PID {process.Id}."); + QueryMaster.Server gameServer = null; bool sent = false; try { + // create a connection to the server + var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.QueryPort); + gameServer = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint); + // check if there is a shutdown reason if (!string.IsNullOrWhiteSpace(ShutdownReason) && !Config.Default.ServerShutdown_AllMessagesShowReason) { @@ -404,10 +421,6 @@ namespace ServerManagerTool.Lib } LogProfileMessage("Starting shutdown timer..."); - if (!CheckForOnlinePlayers) - { - LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check."); - } var minutesLeft = ShutdownInterval; while (minutesLeft > 0) @@ -430,8 +443,11 @@ namespace ServerManagerTool.Lib { try { - var gameFile = GetServerWorldFile(); - var playerCount = DataContainer.GetOnlinePlayerCount(gameFile); + // BH - commented out until Funcom fix the Online player status column in the world save database + //var gameFile = GetServerWorldFile(); + //var playerCount = DataContainer.GetOnlinePlayerCount(gameFile); + var playerInfo = gameServer?.GetPlayers()?.Where(p => !string.IsNullOrWhiteSpace(p.Name?.Trim())).ToList(); + var playerCount = playerInfo?.Count ?? -1; // check if anyone is logged into the server if (playerCount <= 0) @@ -449,7 +465,8 @@ namespace ServerManagerTool.Lib } else { - Debug.WriteLine($"CheckForOnlinePlayers disabled"); + Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled."); + break; } var message = string.Empty; @@ -516,7 +533,7 @@ namespace ServerManagerTool.Lib // BH - commented out until funcom provide a way to send a save command // check if we need to perform a world save - //if (_profile.RconEnabled && Config.Default.ServerShutdown_EnableWorldSave) + //if (Config.Default.ServerShutdown_EnableWorldSave) //{ // try // { @@ -525,10 +542,10 @@ namespace ServerManagerTool.Lib // { // LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage); // ProcessAlert(AlertType.ShutdownMessage, Config.Default.ServerShutdown_WorldSaveMessage); - // SendMessage(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken); + // SendMessageAsync(Config.Default.ServerShutdown_WorldSaveMessage, cancellationToken).Wait(cancellationToken); // } - // if (SendCommand(Config.Default.ServerSaveCommand, false)) + // if (SendCommandAsync(Config.Default.ServerSaveCommand, false).Result) // { // try // { @@ -576,7 +593,8 @@ namespace ServerManagerTool.Lib } finally { - CloseRconConsole(); + gameServer?.Dispose(); + gameServer = null; } if (cancellationToken.IsCancellationRequested) @@ -589,8 +607,6 @@ namespace ServerManagerTool.Lib SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait(); } - CloseRconConsole(); - ExitCode = EXITCODE_CANCELLED; return; } @@ -664,7 +680,7 @@ namespace ServerManagerTool.Lib ExitCode = EXITCODE_SHUTDOWN_TIMEOUT; } - private void UpgradeLocal(bool validate, bool steamCmdRemoveQuit, CancellationToken cancellationToken, bool updateMods) + private void UpgradeLocal(bool validate, bool updateMods, bool steamCmdRemoveQuit, CancellationToken cancellationToken) { if (_profile == null) { @@ -1182,7 +1198,7 @@ namespace ServerManagerTool.Lib { // perform a steamcmd validate to confirm all the files LogProfileMessage("Validating server files (*new*)."); - UpgradeLocal(true, false, CancellationToken.None, false); + UpgradeLocal(true, false, false, CancellationToken.None); LogProfileMessage("Validated server files (*new*)."); } @@ -1703,17 +1719,6 @@ namespace ServerManagerTool.Lib ExitCode = EXITCODE_NORMALEXIT; } - private void CloseRconConsole() - { - if (_rconConsole != null) - { - _rconConsole.Dispose(); - _rconConsole = null; - - Task.Delay(1000).Wait(); - } - } - public void CreateProfileBackupArchiveFile(ServerProfileSnapshot profile = null) { var oldProfile = _profile; @@ -2413,36 +2418,43 @@ namespace ServerManagerTool.Lib int rconRetries = 0; int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1; - while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) + try { - SetupRconConsole(); - - if (_rconConsole == null) + while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) { - LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); - LogProfileMessage("RCON connection could not be created.", false); - rconRetries++; - } - else - { - rconRetries = 0; - try - { - _rconConsole.SendCommand(command); - LogProfileMessage($"RCON> {command}"); + SetupRconConsole(); - return true; - } - catch (Exception ex) + if (_rconConsole == null) { - LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); - LogProfileMessage($"{ex.Message}", false); - LogProfileMessage($"{ex.StackTrace}", false); + LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); + LogProfileMessage("RCON connection could not be created.", false); + rconRetries++; } + else + { + rconRetries = 0; + try + { + _rconConsole.SendCommand(command); + LogProfileMessage($"RCON> {command}"); - retries++; + return true; + } + catch (Exception ex) + { + LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); + LogProfileMessage($"{ex.Message}", false); + LogProfileMessage($"{ex.StackTrace}", false); + } + + retries++; + } } } + finally + { + CloseRconConsole(); + } return false; } @@ -2504,6 +2516,17 @@ namespace ServerManagerTool.Lib } } + private void CloseRconConsole() + { + if (_rconConsole != null) + { + _rconConsole.Dispose(); + _rconConsole = null; + + Task.Delay(1000).Wait(); + } + } + private void SetupRconConsole() { CloseRconConsole(); @@ -2513,7 +2536,7 @@ namespace ServerManagerTool.Lib try { - var endPoint = new IPEndPoint(IPAddress.Parse(_profile.ServerIP), _profile.RconPort); + var endPoint = new IPEndPoint(_profile.ServerIPAddress, _profile.RconPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000); if (server == null) { @@ -2551,7 +2574,7 @@ namespace ServerManagerTool.Lib } } - public int PerformProfileBackup(ServerProfileSnapshot profile) + public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken) { _profile = profile; @@ -2573,7 +2596,7 @@ namespace ServerManagerTool.Lib // check if the mutex was established if (createdNew) { - BackupServer(); + BackupServer(cancellationToken); if (ExitCode != EXITCODE_NORMALEXIT) { @@ -2926,7 +2949,7 @@ namespace ServerManagerTool.Lib SendEmails = true, ServerProcess = ServerProcessType.AutoBackup }; - exitCodes.TryAdd(profile, app.PerformProfileBackup(profile)); + exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None)); }); foreach (var profile in _profiles.Keys) diff --git a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs index cd341e57..e74521b8 100644 --- a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs +++ b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs @@ -17,7 +17,7 @@ namespace ServerManagerTool.Lib public string InstallDirectory; public string GameFile; public string AdminPassword; - public string ServerIP; + public IPAddress ServerIPAddress; public int ServerPort; public int ServerPeerPort; public int QueryPort; @@ -60,7 +60,7 @@ namespace ServerManagerTool.Lib InstallDirectory = profile.InstallDirectory, GameFile = profile.GetServerWorldFile(), AdminPassword = profile.AdminPassword, - ServerIP = string.IsNullOrWhiteSpace(profile.ServerIP) ? IPAddress.Loopback.ToString() : profile.ServerIP.Trim(), + ServerIPAddress = string.IsNullOrWhiteSpace(profile.ServerIP) || !IPAddress.TryParse(profile.ServerIP.Trim(), out IPAddress ipAddress) ? IPAddress.Loopback : ipAddress, ServerPort = profile.ServerPort, ServerPeerPort = profile.ServerPeerPort, QueryPort = profile.QueryPort, diff --git a/src/ConanServerManager/Lib/ServerRuntime.cs b/src/ConanServerManager/Lib/ServerRuntime.cs index 0d9e20da..d2370360 100644 --- a/src/ConanServerManager/Lib/ServerRuntime.cs +++ b/src/ConanServerManager/Lib/ServerRuntime.cs @@ -167,13 +167,19 @@ namespace ServerManagerTool.Lib ServerProfile.QueryPortProperty, ServerProfile.ServerIPProperty, ServerProfile.MaxPlayersProperty, + + ServerProfile.ServerPasswordProperty, + ServerProfile.AdminPasswordProperty, + ServerProfile.ServerMapProperty, ServerProfile.ServerMapSaveFileNameProperty, ServerProfile.ServerModIdsProperty, + + ServerProfile.MOTDIntervalProperty, }, (s, p) => { - if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown) + if (Status == ServerStatus.Stopped || Status == ServerStatus.Uninstalled || Status == ServerStatus.Unknown || Status == ServerStatus.Updating) { AttachToProfileCore(profile); } @@ -194,16 +200,7 @@ namespace ServerManagerTool.Lib return; } - if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress)) - { - // Use the explicit Server IP - localServerQueryEndPoint = new IPEndPoint(localServerIpAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); - } - else - { - // No Server IP specified, use Loopback - localServerQueryEndPoint = new IPEndPoint(IPAddress.Loopback, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); - } + localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort)); // // Get the public endpoint for querying Steam @@ -1061,7 +1058,7 @@ namespace ServerManagerTool.Lib try { - var endPoint = new IPEndPoint(IPAddress.Parse(this.ProfileSnapshot.ServerIP), this.ProfileSnapshot.RconPort); + var endPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, this.ProfileSnapshot.RconPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000); if (server == null) diff --git a/src/ConanServerManager/Utils/DiscordBotHelper.cs b/src/ConanServerManager/Utils/DiscordBotHelper.cs index b1f16177..9e5b8597 100644 --- a/src/ConanServerManager/Utils/DiscordBotHelper.cs +++ b/src/ConanServerManager/Utils/DiscordBotHelper.cs @@ -23,7 +23,7 @@ namespace ServerManagerTool.Utils public static bool HasRunningCommands => _currentProfileCommands.Count > 0; - public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) + public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token) { // check if incoming values are valid if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId)) @@ -49,15 +49,17 @@ namespace ServerManagerTool.Utils return GetServerStatus(channelId, profileId); case CommandType.Backup: - return BackupServer(channelId, profileId); + return BackupServer(channelId, profileId, token); + case CommandType.Restart: + return RestartServer(channelId, profileId, token); case CommandType.Shutdown: - return ShutdownServer(channelId, profileId); + return ShutdownServer(channelId, profileId, token); case CommandType.Stop: - return StopServer(channelId, profileId); + return StopServer(channelId, profileId, token); case CommandType.Start: - return StartServer(channelId, profileId); + return StartServer(channelId, profileId, token); case CommandType.Update: - return UpdateServer(channelId, profileId); + return UpdateServer(channelId, profileId, token); default: return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) }; @@ -108,6 +110,17 @@ namespace ServerManagerTool.Utils throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + } + serverName = server.Profile.ServerName; if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) { @@ -183,7 +196,7 @@ namespace ServerManagerTool.Utils return response; } - private static IList BackupServer(string channelId, string profileId) + private static IList BackupServer(string channelId, string profileId, CancellationToken token) { if (string.IsNullOrWhiteSpace(profileId)) { @@ -211,6 +224,16 @@ namespace ServerManagerTool.Utils throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + } + profile = ServerProfileSnapshot.Create(server.Profile); }).Wait(); @@ -223,11 +246,19 @@ namespace ServerManagerTool.Utils SendAlerts = true, SendEmails = false, ServerProcess = ServerProcessType.Backup, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } }; task = Task.Run(() => { - app.PerformProfileBackup(profile); + app.PerformProfileBackup(profile, token); _currentProfileCommands.Remove(profileId); }); @@ -244,22 +275,337 @@ namespace ServerManagerTool.Utils } } - private static IList ShutdownServer(string channelId, string profileId) + private static IList RestartServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Shutdown) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Restart) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Restart); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + profile.AutoRestartIfShutdown = true; + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Restart, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, true, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_RestartRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList StopServer(string channelId, string profileId) + private static IList ShutdownServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Stop) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Shutdown); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Shutdown, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, false, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_ShutdownRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList StartServer(string channelId, string profileId) + private static IList StopServer(string channelId, string profileId, CancellationToken token) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Start) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Shutdown) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Shutdown); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Stopped: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Shutdown, + ShutdownInterval = 0, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, false, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StopRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } - private static IList UpdateServer(string channelId, string profileId) + private static IList StartServer(string channelId, string profileId, CancellationToken token) + { + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Start) }; + } + + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Start); + + ServerProfileSnapshot profile = null; + Task task = null; + + try + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) + { + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); + } + + switch (server.Runtime.Status) + { + case ServerStatus.Initializing: + case ServerStatus.Stopping: + case ServerStatus.Running: + case ServerStatus.Uninstalled: + case ServerStatus.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); + + case ServerStatus.Updating: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileUpdating"), profileId)); + } + + profile = ServerProfileSnapshot.Create(server.Profile); + profile.AutoRestartIfShutdown = true; + }).Wait(); + + List response = new List(); + + var app = new ServerApp(true) + { + DeleteOldServerBackupFiles = !Config.Default.AutoBackup_EnableBackup, + OutputLogs = false, + SendAlerts = true, + SendEmails = false, + ServerProcess = ServerProcessType.Restart, + ServerStatusChangeCallback = (ServerStatus serverStatus) => + { + TaskUtils.RunOnUIThreadAsync(() => + { + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + server.Runtime.UpdateServerStatus(serverStatus, true); + }).Wait(); + } + }; + + task = Task.Run(() => + { + app.PerformProfileShutdown(profile, true, false, false, false, token); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_StartRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } + } + + private static IList UpdateServer(string channelId, string profileId, CancellationToken token) { if (string.IsNullOrWhiteSpace(profileId)) { @@ -293,7 +639,7 @@ namespace ServerManagerTool.Utils case ServerStatus.Initializing: case ServerStatus.Stopping: case ServerStatus.Unknown: - throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, ServerRuntime.GetServerStatusString(ServerStatus.Stopped))); + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, server.Runtime.StatusString)); case ServerStatus.Running: performRestart = true; @@ -327,7 +673,7 @@ namespace ServerManagerTool.Utils task = Task.Run(() => { - app.PerformProfileShutdown(profile, performRestart, true, false, false, CancellationToken.None); + app.PerformProfileShutdown(profile, performRestart, true, false, false, token); _currentProfileCommands.Remove(profileId); }); diff --git a/src/ConanServerManager/Windows/RconWindow.xaml.cs b/src/ConanServerManager/Windows/RconWindow.xaml.cs index f426ad8c..21f11ed4 100644 --- a/src/ConanServerManager/Windows/RconWindow.xaml.cs +++ b/src/ConanServerManager/Windows/RconWindow.xaml.cs @@ -542,7 +542,7 @@ namespace ServerManagerTool ProfileId = server.Runtime.ProfileSnapshot.ProfileId, ProfileName = server.Runtime.ProfileSnapshot.ProfileName, MaxPlayers = server.Runtime.MaxPlayers, - RconHost = server.Runtime.ProfileSnapshot.ServerIP, + RconHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(), RconPort = server.Runtime.ProfileSnapshot.RconPort, RconPassword = server.Runtime.ProfileSnapshot.RconPassword, }); diff --git a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs index 62f3b844..c9e0c693 100644 --- a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs +++ b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs @@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows var profile = ServerProfileSnapshot.Create(server.Profile); - var exitCode = await Task.Run(() => app.PerformProfileBackup(profile)); + var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None)); if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED) { throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); diff --git a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs index 55975a98..99e2ed60 100644 --- a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs +++ b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs @@ -834,7 +834,7 @@ namespace ServerManagerTool var profile = ServerProfileSnapshot.Create(Server.Profile); - var exitCode = await Task.Run(() => app.PerformProfileBackup(profile)); + var exitCode = await Task.Run(() => app.PerformProfileBackup(profile, CancellationToken.None)); if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED) throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); diff --git a/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs b/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs index a6b973f1..e4301ce4 100644 --- a/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs +++ b/src/ServerManager.Discord/Delegates/HandleCommandDelegate.cs @@ -1,7 +1,8 @@ using ServerManagerTool.DiscordBot.Enums; using System.Collections.Generic; +using System.Threading; namespace ServerManagerTool.DiscordBot.Delegates { - public delegate IList HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId); + public delegate IList HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token); } diff --git a/src/ServerManager.Discord/Enums/CommandType.cs b/src/ServerManager.Discord/Enums/CommandType.cs index efe398a8..1cc4be15 100644 --- a/src/ServerManager.Discord/Enums/CommandType.cs +++ b/src/ServerManager.Discord/Enums/CommandType.cs @@ -7,6 +7,7 @@ Status, Backup, + Restart, Shutdown, Start, Stop, diff --git a/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs b/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs index 1d5867af..f44c1878 100644 --- a/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs +++ b/src/ServerManager.Discord/Interfaces/IServerManagerBot.cs @@ -6,6 +6,8 @@ namespace ServerManagerTool.DiscordBot.Interfaces { public interface IServerManagerBot { + CancellationToken Token { get; } + Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token); } } diff --git a/src/ServerManager.Discord/Modules/ServerCommandModule.cs b/src/ServerManager.Discord/Modules/ServerCommandModule.cs index 57b772b2..ea993166 100644 --- a/src/ServerManager.Discord/Modules/ServerCommandModule.cs +++ b/src/ServerManager.Discord/Modules/ServerCommandModule.cs @@ -4,7 +4,9 @@ using Discord.Commands; using Microsoft.Extensions.Configuration; using ServerManagerTool.DiscordBot.Delegates; using ServerManagerTool.DiscordBot.Enums; +using ServerManagerTool.DiscordBot.Interfaces; using System; +using System.Threading; using System.Threading.Tasks; namespace ServerManagerTool.DiscordBot.Modules @@ -12,19 +14,21 @@ namespace ServerManagerTool.DiscordBot.Modules [Name("Server Commands")] public sealed class ServerCommandModule : InteractiveBase { + private readonly IServerManagerBot _serverManagerBot; private readonly CommandService _service; private readonly HandleCommandDelegate _handleCommandCallback; private readonly IConfigurationRoot _config; - public ServerCommandModule(CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config) + public ServerCommandModule(IServerManagerBot serverManagerBot, CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config) { + _serverManagerBot = serverManagerBot; _service = service; _handleCommandCallback = handleCommandCallback; _config = config; } [Command("backup", RunMode = RunMode.Async)] - [Summary("Perform a backup of the server")] + [Summary("Backup the server")] [Remarks("backup profileId")] [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] public async Task BackupServerAsync(string profileId) @@ -34,7 +38,38 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Backup, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Backup, serverId, channelId, profileId, _serverManagerBot.Token); + if (response is null) + { + await ReplyAsync("No servers associated with this channel."); + } + else + { + foreach (var output in response) + { + await ReplyAsync(output.Replace("&", "_")); + await Task.Delay(1000); + } + } + } + catch (Exception ex) + { + await ReplyAsync($"'{Context.Message}' command sent and failed with exception ({ex.Message})"); + } + } + + [Command("restart", RunMode = RunMode.Async)] + [Summary("Restart the server")] + [Remarks("restart profileId")] + [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] + public async Task RestartServerAsync(string profileId) + { + try + { + var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; + var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; + + var response = _handleCommandCallback?.Invoke(CommandType.Restart, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -65,7 +100,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Shutdown, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Shutdown, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -96,7 +131,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Start, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Start, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -127,7 +162,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Stop, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Stop, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -158,7 +193,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Update, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Update, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); diff --git a/src/ServerManager.Discord/Modules/ServerQueryModule.cs b/src/ServerManager.Discord/Modules/ServerQueryModule.cs index c4d89e6b..a793c3b7 100644 --- a/src/ServerManager.Discord/Modules/ServerQueryModule.cs +++ b/src/ServerManager.Discord/Modules/ServerQueryModule.cs @@ -4,7 +4,9 @@ using Discord.Commands; using Microsoft.Extensions.Configuration; using ServerManagerTool.DiscordBot.Delegates; using ServerManagerTool.DiscordBot.Enums; +using ServerManagerTool.DiscordBot.Interfaces; using System; +using System.Threading; using System.Threading.Tasks; namespace ServerManagerTool.DiscordBot.Modules @@ -12,12 +14,14 @@ namespace ServerManagerTool.DiscordBot.Modules [Name("Server Query")] public sealed class ServerQueryModule : InteractiveBase { + private readonly IServerManagerBot _serverManagerBot; private readonly CommandService _service; private readonly HandleCommandDelegate _handleCommandCallback; private readonly IConfigurationRoot _config; - public ServerQueryModule(CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config) + public ServerQueryModule(IServerManagerBot serverManagerBot, CommandService service, HandleCommandDelegate handleCommandCallback, IConfigurationRoot config) { + _serverManagerBot = serverManagerBot; _service = service; _handleCommandCallback = handleCommandCallback; _config = config; @@ -43,7 +47,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Info, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Info, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -74,7 +78,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.List, serverId, channelId, null); + var response = _handleCommandCallback?.Invoke(CommandType.List, serverId, channelId, null, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); @@ -114,7 +118,7 @@ namespace ServerManagerTool.DiscordBot.Modules var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var channelId = Context?.Channel?.Id.ToString() ?? string.Empty; - var response = _handleCommandCallback?.Invoke(CommandType.Status, serverId, channelId, profileId); + var response = _handleCommandCallback?.Invoke(CommandType.Status, serverId, channelId, profileId, _serverManagerBot.Token); if (response is null) { await ReplyAsync("No servers associated with this channel."); diff --git a/src/ServerManager.Discord/ServerManagerBot.cs b/src/ServerManager.Discord/ServerManagerBot.cs index 3b5fedab..6c7f0d63 100644 --- a/src/ServerManager.Discord/ServerManagerBot.cs +++ b/src/ServerManager.Discord/ServerManagerBot.cs @@ -24,11 +24,8 @@ namespace ServerManagerTool.DiscordBot Started = false; } - private bool Started - { - get; - set; - } + public CancellationToken Token { get; private set; } + public bool Started { get; private set; } public async Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token) { @@ -43,6 +40,8 @@ namespace ServerManagerTool.DiscordBot return; } + Token = token; + if (commandPrefix.Any(c => !char.IsLetterOrDigit(c))) { throw new Exception("#DiscordBot_InvalidPrefixError"); @@ -57,7 +56,7 @@ namespace ServerManagerTool.DiscordBot { { "DiscordSettings:Token", discordToken }, { "DiscordSettings:Prefix", commandPrefix }, - { "ServerManager:DataDirectory", dataDirectory } + { "ServerManager:DataDirectory", dataDirectory }, }; // Begin building the configuration file @@ -107,7 +106,8 @@ namespace ServerManagerTool.DiscordBot .AddSingleton() .AddSingleton(config) .AddSingleton(handleCommandCallback) - .AddSingleton(handleTranslationCallback); + .AddSingleton(handleTranslationCallback) + .AddSingleton(this); // Create the service provider using (var provider = services.BuildServiceProvider())