diff --git a/src/ARKServerManager/ARKServerManager.csproj b/src/ARKServerManager/ARKServerManager.csproj index d18ce262..f7baf7b7 100644 --- a/src/ARKServerManager/ARKServerManager.csproj +++ b/src/ARKServerManager/ARKServerManager.csproj @@ -173,6 +173,7 @@ MSBuild:Compile Designer + @@ -196,13 +197,15 @@ - + + + AddUserWindow.xaml diff --git a/src/ARKServerManager/App.config b/src/ARKServerManager/App.config index 605e2a65..446c2530 100644 --- a/src/ARKServerManager/App.config +++ b/src/ARKServerManager/App.config @@ -347,6 +347,12 @@ asmdata + + https://discord.com/developers/applications + + + https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot + @@ -804,6 +810,39 @@ 50 + + False + + + asm + + + + + + + + + False + + + True + + + True + + + True + + + True + + + True + + + True + diff --git a/src/ARKServerManager/App.xaml.cs b/src/ARKServerManager/App.xaml.cs index 52716ef4..321e4a93 100644 --- a/src/ARKServerManager/App.xaml.cs +++ b/src/ARKServerManager/App.xaml.cs @@ -1,16 +1,16 @@ using ArkData; -using Microsoft.WindowsAPICodePack.Dialogs; using NLog; using NLog.Config; using NLog.Targets; using ServerManagerTool.Common; using ServerManagerTool.Common.Utils; +using ServerManagerTool.DiscordBot; using ServerManagerTool.Enums; using ServerManagerTool.Lib; using ServerManagerTool.Plugin.Common; +using ServerManagerTool.Utils; using ServerManagerTool.Windows; using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; @@ -21,7 +21,6 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.Xml; using WPFSharp.Globalizer; namespace ServerManagerTool @@ -39,6 +38,7 @@ namespace ServerManagerTool public event PropertyChangedEventHandler PropertyChanged; + private CancellationTokenSource _tokenSource; private GlobalizedApplication _globalizer; private bool _applicationStarted; private string _args; @@ -177,11 +177,6 @@ namespace ServerManagerTool } } - private IList FetchProfiles() - { - return ServerManager.Instance.Servers.Select(s => new ServerManagerTool.Plugin.Common.Lib.Profile() { ProfileName = s?.Profile?.ProfileName ?? string.Empty, InstallationFolder = s?.Profile?.InstallDirectory ?? string.Empty }).ToList(); - } - public static string GetLogFolder() => IOUtils.NormalizePath(Path.Combine(Config.Default.DataDir, Config.Default.LogsDir)); public static string GetProfileLogFolder(string profileId) => IOUtils.NormalizePath(Path.Combine(Config.Default.DataDir, Config.Default.LogsDir, profileId.ToLower())); @@ -312,7 +307,7 @@ namespace ServerManagerTool var installPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); PluginHelper.Instance.BetaEnabled = this.BetaVersion; PluginHelper.Instance.LoadPlugins(installPath, true); - PluginHelper.Instance.SetFetchProfileCallback(FetchProfiles); + PluginHelper.Instance.SetFetchProfileCallback(DiscordPluginHelper.FetchProfiles); OnResourceDictionaryChanged(Thread.CurrentThread.CurrentCulture.Name); // check if we are starting ASM for the old server restart - no longer supported @@ -412,6 +407,7 @@ namespace ServerManagerTool ApplicationStarted = true; + var restartRequired = false; if (string.IsNullOrWhiteSpace(Config.Default.DataDir)) { var dataDirectoryWindow = new DataDirectoryWindow(); @@ -422,6 +418,8 @@ namespace ServerManagerTool { Environment.Exit(0); } + + restartRequired = true; } Config.Default.ConfigDirectory = Path.Combine(Config.Default.DataDir, Config.Default.ProfilesDir); @@ -429,6 +427,11 @@ namespace ServerManagerTool Config.Default.Save(); CommonConfig.Default.Save(); + if (restartRequired) + { + Environment.Exit(0); + } + DataFileDetails.PlayerFileExtension = Config.Default.PlayerFileExtension; DataFileDetails.TribeFileExtension = Config.Default.TribeFileExtension; @@ -446,6 +449,25 @@ namespace ServerManagerTool StartupUri = new Uri("Windows/AutoUpdateWindow.xaml", UriKind.RelativeOrAbsolute); } + + if (Config.Default.DiscordBotEnabled) + { + _tokenSource = new CancellationTokenSource(); + + Task discordTask = Task.Run(async () => + { + await ServerManagerBotFactory.GetServerManagerBot()?.StartAsync(Config.Default.DiscordBotToken, Config.Default.DiscordBotPrefix, Config.Default.DataDir, DiscordBotHelper.HandleDiscordCommand, DiscordBotHelper.HandleTranslation, _tokenSource.Token); + }, _tokenSource.Token) + .ContinueWith(t => { + var message = t.Exception.InnerException is null ? t.Exception.Message : t.Exception.InnerException.Message; + if (message.StartsWith("#")) + { + message = _globalizer.GetResourceString(message.Substring(1)) ?? message.Substring(1); + } + + MessageBox.Show(message, _globalizer.GetResourceString("DiscordBot_ErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error); + }, TaskContinuationOptions.OnlyOnFaulted); + } } protected override void OnExit(ExitEventArgs e) @@ -486,6 +508,12 @@ namespace ServerManagerTool private void ShutDownApplication() { + if (!(_tokenSource is null)) + { + _tokenSource.Cancel(); + _tokenSource.Dispose(); + } + if (ApplicationStarted) { foreach (var server in ServerManager.Instance.Servers) diff --git a/src/ARKServerManager/Art/favicon.ico b/src/ARKServerManager/Art/favicon.ico index 2ed732cb..9f1a5760 100644 Binary files a/src/ARKServerManager/Art/favicon.ico and b/src/ARKServerManager/Art/favicon.ico differ diff --git a/src/ARKServerManager/Config.Designer.cs b/src/ARKServerManager/Config.Designer.cs index dbac3d1f..7eed5ed1 100644 --- a/src/ARKServerManager/Config.Designer.cs +++ b/src/ARKServerManager/Config.Designer.cs @@ -2812,5 +2812,155 @@ namespace ServerManagerTool { return ((string)(this["DefaultDataDirectoryName"])); } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool DiscordBotEnabled { + get { + return ((bool)(this["DiscordBotEnabled"])); + } + set { + this["DiscordBotEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("asm")] + public string DiscordBotPrefix { + get { + return ((string)(this["DiscordBotPrefix"])); + } + set { + this["DiscordBotPrefix"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string DiscordBotToken { + get { + return ((string)(this["DiscordBotToken"])); + } + set { + this["DiscordBotToken"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string DiscordBotServerId { + get { + return ((string)(this["DiscordBotServerId"])); + } + set { + this["DiscordBotServerId"] = value; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("https://discord.com/developers/applications")] + public string DiscordBotApplyUrl { + get { + return ((string)(this["DiscordBotApplyUrl"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot")] + public string DiscordBotHelpUrl { + get { + return ((string)(this["DiscordBotHelpUrl"])); + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool SectionDiscordBotIsExpanded { + get { + return ((bool)(this["SectionDiscordBotIsExpanded"])); + } + set { + this["SectionDiscordBotIsExpanded"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordBackup { + get { + return ((bool)(this["AllowDiscordBackup"])); + } + set { + this["AllowDiscordBackup"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordUpdate { + get { + return ((bool)(this["AllowDiscordUpdate"])); + } + set { + this["AllowDiscordUpdate"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordStart { + get { + return ((bool)(this["AllowDiscordStart"])); + } + set { + this["AllowDiscordStart"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordRestart { + get { + return ((bool)(this["AllowDiscordRestart"])); + } + set { + this["AllowDiscordRestart"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordShutdown { + get { + return ((bool)(this["AllowDiscordShutdown"])); + } + set { + this["AllowDiscordShutdown"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool AllowDiscordStop { + get { + return ((bool)(this["AllowDiscordStop"])); + } + set { + this["AllowDiscordStop"] = value; + } + } } } diff --git a/src/ARKServerManager/Config.settings b/src/ARKServerManager/Config.settings index 72345566..9ee1d712 100644 --- a/src/ARKServerManager/Config.settings +++ b/src/ARKServerManager/Config.settings @@ -779,5 +779,44 @@ asmdata + + False + + + asm + + + + + + + + + https://discord.com/developers/applications + + + https://arkservermanager.freeforums.net/thread/8764/get-own-discord-bot + + + False + + + True + + + True + + + True + + + True + + + True + + + True + \ No newline at end of file diff --git a/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs b/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs new file mode 100644 index 00000000..c7ab834a --- /dev/null +++ b/src/ARKServerManager/Delegates/ServerStatusChangeDelegate.cs @@ -0,0 +1,6 @@ +using ServerManagerTool.Enums; + +namespace ServerManagerTool.Delegates +{ + public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus); +} diff --git a/src/ARKServerManager/Enums/AvailabilityStatus.cs b/src/ARKServerManager/Enums/AvailabilityStatus.cs index f2588330..81846709 100644 --- a/src/ARKServerManager/Enums/AvailabilityStatus.cs +++ b/src/ARKServerManager/Enums/AvailabilityStatus.cs @@ -3,9 +3,9 @@ public enum AvailabilityStatus { Unknown, - NeedPublicIP, + SetPublicIP, Unavailable, - WaitingForPublication, + Waiting, Available } } diff --git a/src/ARKServerManager/Enums/ServerProcessType.cs b/src/ARKServerManager/Enums/ServerProcessType.cs index 861e2081..7298dfd9 100644 --- a/src/ARKServerManager/Enums/ServerProcessType.cs +++ b/src/ARKServerManager/Enums/ServerProcessType.cs @@ -10,5 +10,6 @@ Backup, Shutdown, Restart, + Update, } } diff --git a/src/ARKServerManager/Globalization/en-US/en-US.xaml b/src/ARKServerManager/Globalization/en-US/en-US.xaml index 49ab73d1..ff889d9f 100644 --- a/src/ARKServerManager/Globalization/en-US/en-US.xaml +++ b/src/ARKServerManager/Globalization/en-US/en-US.xaml @@ -610,7 +610,24 @@ This message will be displayed when the server shutdown has been cancelled. Show shutdown reason with ALL shutdown messages If enabled, the shutdown reason will be shown with all shutdown message; otherwise it will only be shown at the start of the server shutdown. - + + Enable Discord Bot + You will need to restart the server manager if you change any settings for the Discord Bot. + Token: + The token associated with the discord bot. + Server Id: + The id of the discord server the bot will listen to. + Prefix: + The prefix that must be used when sending a command via discord. + Get Token... + Help... + If enabled, the backup command can be sent from discord. + If enabled, the restart command can be sent from discord. + If enabled, the shutdown command can be sent from discord. + If enabled, the start command can be sent from discord. + If enabled, the stop command can be sent from discord. + If enabled, the update command can be sent from discord. + SMTP Email Settings Host: The name or IP address of the host used for SMTP transmissions. @@ -775,6 +792,9 @@ Reinstall SteamCMD Error An error occured while trying to reinstall SteamCMD. This has left SteamCmd in an unstable state, try reinstalling again or please report this.\r\nException: {0} + Discord Bot Running Commands + The discord bot has one or more running commands, do you want to continue shutting down the server manager? + Start Server Confirmation You are about to start the server, do you want to continue? Shutdown Server Confirmation @@ -1179,6 +1199,24 @@ If enabled, the server will be restarted even if shutdown for Auto-Restarts and Auto-Updates. + + Discord Bot Details + Channel Id: + The id of the discord server channel this profile will listen to. + Allow Backup + If enabled, the profile will listen for backup commands from discord. + Allow Restart + If enabled, the profile will listen for restart commands from discord. + Allow Shutdown + If enabled, the profile will listen for shutdown commands from discord. + Allow Start + If enabled, the profile will listen for start commands from discord. + Allow Stop + If enabled, the profile will listen for stop commands from discord. + Allow Update + If enabled, the profile will listen for update commands from discord. + + Rules Enable Hardcore Mode @@ -5523,4 +5561,32 @@ There was a problem while performing the server update. This may leave your server in a incomplete state.\r\n\r\nDo you want to continue with the server start, this could cause problems? + + Discord Bot Error + The discord bot requires a valid token so it can log into the discord server\r\nThis can be set in the global settings. + The discord bot prefix contains invalid characters. Only letters and numbers are allowed. + + Command '{0}' has not been enabled. + Unknown command '{0}'. + Another command is currently being processed. + Another command '{0}' is currently running against profile '{1}'. + Command '{0}' has been disabled for profile '{1}'. + + The '{0}' command requires a profile id. + Profile '{0}' was not found or is not associated with the channel. + 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: + Map: + + \ No newline at end of file diff --git a/src/ARKServerManager/Lib/ServerBranchSnapshot.cs b/src/ARKServerManager/Lib/BranchSnapshot.cs similarity index 52% rename from src/ARKServerManager/Lib/ServerBranchSnapshot.cs rename to src/ARKServerManager/Lib/BranchSnapshot.cs index b9f34073..c3b0283d 100644 --- a/src/ARKServerManager/Lib/ServerBranchSnapshot.cs +++ b/src/ARKServerManager/Lib/BranchSnapshot.cs @@ -3,15 +3,37 @@ using System.Collections.Generic; namespace ServerManagerTool.Lib { - public class ServerBranchSnapshot + public class BranchSnapshot { + private BranchSnapshot() + { + } + public string BranchName = string.Empty; public string BranchPassword = string.Empty; + + public static BranchSnapshot Create(ServerProfile profile) + { + return new BranchSnapshot + { + BranchName = profile.BranchName, + BranchPassword = profile.BranchPassword + }; + } + + public static BranchSnapshot Create(ServerProfileSnapshot profile) + { + return new BranchSnapshot + { + BranchName = profile.BranchName, + BranchPassword = profile.BranchPassword + }; + } } - public class ServerBranchSnapshotComparer : IEqualityComparer + public class BranchSnapshotComparer : IEqualityComparer { - public bool Equals(ServerBranchSnapshot x, ServerBranchSnapshot y) + public bool Equals(BranchSnapshot x, BranchSnapshot y) { //Check whether the compared objects reference the same data. if (Object.ReferenceEquals(x, y)) return true; @@ -24,7 +46,7 @@ namespace ServerManagerTool.Lib return x.BranchName == y.BranchName; } - public int GetHashCode(ServerBranchSnapshot snapshot) + public int GetHashCode(BranchSnapshot snapshot) { //Check whether the object is null if (snapshot is null) return 0; diff --git a/src/ARKServerManager/Lib/Server.cs b/src/ARKServerManager/Lib/Server.cs index bb934d88..06a7fcf9 100644 --- a/src/ARKServerManager/Lib/Server.cs +++ b/src/ARKServerManager/Lib/Server.cs @@ -94,7 +94,7 @@ namespace ServerManagerTool.Lib await this.Runtime.StopAsync(); } - public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback) + public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback) { await this.Runtime.AttachToProfile(this.Profile); var success = await this.Runtime.UpgradeAsync(cancellationToken, updateServer, branch, validate, updateMods, progressCallback); diff --git a/src/ARKServerManager/Lib/ServerApp.cs b/src/ARKServerManager/Lib/ServerApp.cs index 718376d3..0f433ad8 100644 --- a/src/ARKServerManager/Lib/ServerApp.cs +++ b/src/ARKServerManager/Lib/ServerApp.cs @@ -2,6 +2,7 @@ using ServerManagerTool.Common.Lib; using ServerManagerTool.Common.Model; using ServerManagerTool.Common.Utils; +using ServerManagerTool.Delegates; using ServerManagerTool.Enums; using ServerManagerTool.Plugin.Common; using ServerManagerTool.Utils; @@ -105,6 +106,7 @@ namespace ServerManagerTool.Lib public int ShutdownInterval = Config.Default.ServerShutdown_GracePeriod; public ProgressDelegate ProgressCallback = null; public ProcessWindowStyle SteamCMDProcessWindowStyle = ProcessWindowStyle.Minimized; + public ServerStatusChangeDelegate ServerStatusChangeCallback = null; public ServerApp(bool resetStartTime = false) { @@ -112,7 +114,7 @@ namespace ServerManagerTool.Lib _startTime = DateTime.Now; } - private void BackupServer() + private void BackupServer(CancellationToken cancellationToken) { if (_profile == null || _profile.SotFEnabled) { @@ -140,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) { @@ -264,7 +266,15 @@ namespace ServerManagerTool.Lib if (updateServer) { - UpgradeLocal(true, cancellationToken, true); + try + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); + UpgradeLocal(true, true, cancellationToken); + } + finally + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Stopped); + } } if (ExitCode != EXITCODE_NORMALEXIT) @@ -300,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. @@ -394,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 @@ -407,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) @@ -422,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; @@ -452,7 +461,8 @@ namespace ServerManagerTool.Lib } else { - Debug.WriteLine($"CheckForOnlinePlayers disabled"); + Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled."); + break; } var message = string.Empty; @@ -526,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) @@ -551,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; @@ -577,8 +587,6 @@ namespace ServerManagerTool.Lib } finally { - CloseRconConsole(); - gameServer?.Dispose(); gameServer = null; } @@ -590,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; } @@ -643,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(); @@ -711,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) { @@ -1240,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*)."); } @@ -1766,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 @@ -2568,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; } @@ -2662,6 +2658,17 @@ namespace ServerManagerTool.Lib } } + private void CloseRconConsole() + { + if (_rconConsole != null) + { + _rconConsole.Dispose(); + _rconConsole = null; + + Task.Delay(1000).Wait(); + } + } + private void SetupRconConsole() { CloseRconConsole(); @@ -2671,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) { @@ -2708,7 +2715,7 @@ namespace ServerManagerTool.Lib } } - public int PerformProfileBackup(ServerProfileSnapshot profile) + public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken) { _profile = profile; @@ -2733,7 +2740,7 @@ namespace ServerManagerTool.Lib // check if the mutex was established if (createdNew) { - BackupServer(); + BackupServer(cancellationToken); if (ExitCode != EXITCODE_NORMALEXIT) { @@ -2869,7 +2876,7 @@ namespace ServerManagerTool.Lib return ExitCode; } - public int PerformProfileUpdate(ServerBranchSnapshot branch, ServerProfileSnapshot profile) + public int PerformProfileUpdate(BranchSnapshot branch, ServerProfileSnapshot profile) { _profile = profile; @@ -2945,7 +2952,7 @@ namespace ServerManagerTool.Lib return ExitCode; } - public int PerformServerBranchUpdate(ServerBranchSnapshot branch) + public int PerformServerBranchUpdate(BranchSnapshot branch) { if (branch == null) return EXITCODE_NORMALEXIT; @@ -3089,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) @@ -3233,8 +3240,8 @@ namespace ServerManagerTool.Lib if (exitCode == EXITCODE_NORMALEXIT) { - var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => new ServerBranchSnapshot() { BranchName = p.BranchName, BranchPassword = p.BranchPassword}).Distinct(new ServerBranchSnapshotComparer()).ToArray(); - var exitCodes = new ConcurrentDictionary(); + var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => BranchSnapshot.Create(p)).Distinct(new BranchSnapshotComparer()).ToArray(); + var exitCodes = new ConcurrentDictionary(); // update the server cache for each branch if (Config.Default.AutoUpdate_ParallelUpdate) diff --git a/src/ARKServerManager/Lib/ServerProfile.cs b/src/ARKServerManager/Lib/ServerProfile.cs index dfac1b1a..d8dc7a6d 100644 --- a/src/ARKServerManager/Lib/ServerProfile.cs +++ b/src/ARKServerManager/Lib/ServerProfile.cs @@ -957,6 +957,64 @@ namespace ServerManagerTool.Lib } #endregion + #region Discord Bot + public static readonly DependencyProperty DiscordChannelIdProperty = DependencyProperty.Register(nameof(DiscordChannelId), typeof(string), typeof(ServerProfile), new PropertyMetadata(String.Empty)); + [DataMember] + public string DiscordChannelId + { + get { return (string)GetValue(DiscordChannelIdProperty); } + set { SetValue(DiscordChannelIdProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordBackupProperty = DependencyProperty.Register(nameof(AllowDiscordBackup), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordBackup + { + get { return (bool)GetValue(AllowDiscordBackupProperty); } + set { SetValue(AllowDiscordBackupProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordRestartProperty = DependencyProperty.Register(nameof(AllowDiscordRestart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordRestart + { + get { return (bool)GetValue(AllowDiscordRestartProperty); } + set { SetValue(AllowDiscordRestartProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordShutdownProperty = DependencyProperty.Register(nameof(AllowDiscordShutdown), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordShutdown + { + get { return (bool)GetValue(AllowDiscordShutdownProperty); } + set { SetValue(AllowDiscordShutdownProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordStartProperty = DependencyProperty.Register(nameof(AllowDiscordStart), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordStart + { + get { return (bool)GetValue(AllowDiscordStartProperty); } + set { SetValue(AllowDiscordStartProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordStopProperty = DependencyProperty.Register(nameof(AllowDiscordStop), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordStop + { + get { return (bool)GetValue(AllowDiscordStopProperty); } + set { SetValue(AllowDiscordStopProperty, value); } + } + + public static readonly DependencyProperty AllowDiscordUpdateProperty = DependencyProperty.Register(nameof(AllowDiscordUpdate), typeof(bool), typeof(ServerProfile), new PropertyMetadata(true)); + [DataMember] + public bool AllowDiscordUpdate + { + get { return (bool)GetValue(AllowDiscordUpdateProperty); } + set { SetValue(AllowDiscordUpdateProperty, value); } + } + #endregion + #region Rules public static readonly DependencyProperty EnableHardcoreProperty = DependencyProperty.Register(nameof(EnableHardcore), typeof(bool), typeof(ServerProfile), new PropertyMetadata(false)); [IniFileEntry(IniFiles.GameUserSettings, IniSections.GUS_ServerSettings, ServerProfileCategory.Rules, "ServerHardcore")] diff --git a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs index 940bf1e5..0d2238ec 100644 --- a/src/ARKServerManager/Lib/ServerProfileSnapshot.cs +++ b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs @@ -7,6 +7,10 @@ namespace ServerManagerTool.Lib { public class ServerProfileSnapshot { + private ServerProfileSnapshot() + { + } + public string ProfileId; public string ProfileName; public string InstallDirectory; @@ -16,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; @@ -67,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 420cde9f..b1276696 100644 --- a/src/ARKServerManager/Lib/ServerRuntime.cs +++ b/src/ARKServerManager/Lib/ServerRuntime.cs @@ -33,7 +33,7 @@ namespace ServerManagerTool.Lib public event EventHandler StatusUpdate; private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance; + private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance; private readonly List profileNotifiers = new List(); private Process serverProcess; private IAsyncDisposable updateRegistration; @@ -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 @@ -281,13 +275,13 @@ namespace ServerManagerTool.Lib case WatcherServerStatus.RunningLocalCheck: if (oldStatus != ServerStatus.Stopping) - UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.WaitingForPublication : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown); + UpdateServerStatus(ServerStatus.Running, this.Availability != AvailabilityStatus.Available ? AvailabilityStatus.Waiting : this.Availability, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown); if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start(); break; case WatcherServerStatus.RunningExternalCheck: if (oldStatus != ServerStatus.Stopping) - UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.WaitingForPublication, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown); + UpdateServerStatus(ServerStatus.Running, AvailabilityStatus.Waiting, oldStatus != ServerStatus.Running && oldStatus != ServerStatus.Unknown); if (this.ProfileSnapshot.MOTDIntervalEnabled && this.motdIntervalTimer != null && !this.motdIntervalTimer.Enabled) this.motdIntervalTimer.Start(); break; @@ -439,7 +433,7 @@ namespace ServerManagerTool.Lib } CheckServerWorldFileExists(); - UpdateServerStatus(ServerStatus.Initializing, this.Availability, false); + UpdateServerStatus(ServerStatus.Initializing, false); try { @@ -498,12 +492,12 @@ namespace ServerManagerTool.Lib } } - public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback) + public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, ProgressDelegate progressCallback) { return await UpgradeAsync(cancellationToken, updateServer, branch, validate, updateMods, null, progressCallback); } - public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, ServerBranchSnapshot branch, bool validate, bool updateMods, string[] updateModIds, ProgressDelegate progressCallback) + public async Task UpgradeAsync(CancellationToken cancellationToken, bool updateServer, BranchSnapshot branch, bool validate, bool updateMods, string[] updateModIds, ProgressDelegate progressCallback) { if (updateServer && !Environment.Is64BitOperatingSystem) { @@ -520,7 +514,7 @@ namespace ServerManagerTool.Lib bool isNewInstallation = this.Status == ServerStatus.Uninstalled; - UpdateServerStatus(ServerStatus.Updating, Availability, false); + UpdateServerStatus(ServerStatus.Updating, false); // Run the SteamCMD to install the server var steamCmdFile = SteamCmdUpdater.GetSteamCmdFile(Config.Default.DataDir); @@ -957,7 +951,7 @@ namespace ServerManagerTool.Lib finally { this.lastModStatusQuery = DateTime.MinValue; - UpdateServerStatus(ServerStatus.Stopped, Availability, false); + UpdateServerStatus(ServerStatus.Stopped, false); } } @@ -993,6 +987,11 @@ namespace ServerManagerTool.Lib this.lastModStatusQuery = DateTime.MinValue; } + public void UpdateServerStatus(ServerStatus serverStatus, bool sendAlert) + { + UpdateServerStatus(serverStatus, Availability, sendAlert); + } + public void UpdateServerStatus(ServerStatus serverStatus, AvailabilityStatus availabilityStatus, bool sendAlert) { this.Status = serverStatus; @@ -1006,32 +1005,29 @@ namespace ServerManagerTool.Lib public void UpdateServerStatusString() { - switch (Status) + StatusString = GetServerStatusString(Status); + } + + public static string GetServerStatusString(ServerStatus status) + { + switch (status) { case ServerStatus.Initializing: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusInitializingLabel"); case ServerStatus.Running: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusRunningLabel"); case ServerStatus.Stopped: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppedLabel"); case ServerStatus.Stopping: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusStoppingLabel"); case ServerStatus.Uninstalled: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUninstalledLabel"); case ServerStatus.Unknown: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel"); case ServerStatus.Updating: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUpdatingLabel"); default: - StatusString = _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel"); - break; + return _globalizer.GetResourceString("ServerSettings_RuntimeStatusUnknownLabel"); } } @@ -1091,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/Styles/Default.xaml b/src/ARKServerManager/Styles/Default.xaml index e2b09de0..9fecf6e6 100644 --- a/src/ARKServerManager/Styles/Default.xaml +++ b/src/ARKServerManager/Styles/Default.xaml @@ -713,5 +713,15 @@