diff --git a/src/ARKServerManager/ARKServerManager.csproj b/src/ARKServerManager/ARKServerManager.csproj index 3d5408c4..908bfddf 100644 --- a/src/ARKServerManager/ARKServerManager.csproj +++ b/src/ARKServerManager/ARKServerManager.csproj @@ -173,6 +173,7 @@ MSBuild:Compile Designer + @@ -196,7 +197,7 @@ - + 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/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 fd970206..3329e06d 100644 --- a/src/ARKServerManager/Globalization/en-US/en-US.xaml +++ b/src/ARKServerManager/Globalization/en-US/en-US.xaml @@ -786,6 +786,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 @@ -5547,12 +5550,17 @@ Command '{0}' has not been implemented. Unknown command '{0}'. - Another command is currently running. - - The command requires a profile id. - Profile id {0} not found or is not associated with the channel. + Another command is currently being processed. + Another command '{0}' is currently running against profile '{1}'. - Call to the server '{0}' failed. + 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 currently being updated. + + Call to server '{0}' failed. + A backup request for server '{0}' has been sent. + An update request for server '{0}' has been sent. Count: Map: 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..58f637fd 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) { @@ -264,7 +266,15 @@ namespace ServerManagerTool.Lib if (updateServer) { - UpgradeLocal(true, cancellationToken, true); + try + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); + UpgradeLocal(true, cancellationToken, true); + } + finally + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Stopped); + } } if (ExitCode != EXITCODE_NORMALEXIT) @@ -2869,7 +2879,7 @@ namespace ServerManagerTool.Lib return ExitCode; } - public int PerformProfileUpdate(ServerBranchSnapshot branch, ServerProfileSnapshot profile) + public int PerformProfileUpdate(BranchSnapshot branch, ServerProfileSnapshot profile) { _profile = profile; @@ -2945,7 +2955,7 @@ namespace ServerManagerTool.Lib return ExitCode; } - public int PerformServerBranchUpdate(ServerBranchSnapshot branch) + public int PerformServerBranchUpdate(BranchSnapshot branch) { if (branch == null) return EXITCODE_NORMALEXIT; @@ -3233,8 +3243,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/ServerProfileSnapshot.cs b/src/ARKServerManager/Lib/ServerProfileSnapshot.cs index 940bf1e5..97367600 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; diff --git a/src/ARKServerManager/Lib/ServerRuntime.cs b/src/ARKServerManager/Lib/ServerRuntime.cs index b3d9f374..e69b802e 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; @@ -439,7 +439,7 @@ namespace ServerManagerTool.Lib } CheckServerWorldFileExists(); - UpdateServerStatus(ServerStatus.Initializing, this.Availability, false); + UpdateServerStatus(ServerStatus.Initializing, false); try { @@ -498,12 +498,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 +520,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 +957,7 @@ namespace ServerManagerTool.Lib finally { this.lastModStatusQuery = DateTime.MinValue; - UpdateServerStatus(ServerStatus.Stopped, Availability, false); + UpdateServerStatus(ServerStatus.Stopped, false); } } @@ -993,6 +993,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 +1011,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"); } } diff --git a/src/ARKServerManager/Utils/DiscordBotHelper.cs b/src/ARKServerManager/Utils/DiscordBotHelper.cs index 7c5b9f55..90fa05ea 100644 --- a/src/ARKServerManager/Utils/DiscordBotHelper.cs +++ b/src/ARKServerManager/Utils/DiscordBotHelper.cs @@ -1,11 +1,15 @@ using QueryMaster; using ServerManagerTool.Common.Utils; using ServerManagerTool.DiscordBot.Enums; +using ServerManagerTool.Enums; using ServerManagerTool.Lib; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; +using System.Threading; +using System.Threading.Tasks; using WPFSharp.Globalizer; namespace ServerManagerTool.Utils @@ -15,6 +19,10 @@ namespace ServerManagerTool.Utils private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance; private static bool _runningCommand = false; + private static readonly Dictionary _currentProfileCommands = new Dictionary(); + + public static bool HasRunningCommands => _currentProfileCommands.Count > 0; + public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) { // check if incoming values are valid @@ -75,54 +83,68 @@ namespace ServerManagerTool.Utils { if (string.IsNullOrWhiteSpace(profileId)) { - return new List { _globalizer.GetResourceString("DiscordBot_CommandProfileMissing") }; + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Info) }; } - var serverName = string.Empty; - var serverIp = IPAddress.Loopback; - var queryPort = 0; - - TaskUtils.RunOnUIThreadAsync(() => + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) { - var server = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)).FirstOrDefault(); - - if (server is null) - { - throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandProfileNotFound"), profileId)); - } - - serverName = server.Profile.ServerName; - if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) - { - IPAddress.TryParse(server.Profile.ServerIP, out serverIp); - } - queryPort = server.Profile.QueryPort; - }).Wait(); - - List response = new List(); + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Info); try { - using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort))) + var serverName = string.Empty; + var serverIp = IPAddress.Loopback; + var queryPort = 0; + + TaskUtils.RunOnUIThreadAsync(() => { - var info = gameServer?.GetInfo(); - if (info is null) + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) { - response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_CommandInfoFailed"), serverName)); + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } - else + + serverName = server.Profile.ServerName; + if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) { - var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map; - response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```"); + IPAddress.TryParse(server.Profile.ServerIP, out serverIp); + } + queryPort = server.Profile.QueryPort; + }).Wait(); + + List response = new List(); + + try + { + using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort))) + { + var info = gameServer?.GetInfo(); + if (info is null) + { + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName)); + } + else + { + var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map; + response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```"); + } } } - } - catch (Exception) - { - response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_CommandInfoFailed"), serverName)); - } + catch (Exception) + { + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName)); + } - return response; + return response; + } + finally + { + _currentProfileCommands.Remove(profileId); + } } private static IList GetServerList(string channelId) @@ -163,7 +185,63 @@ namespace ServerManagerTool.Utils private static IList BackupServer(string channelId, string profileId) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Backup) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Backup) }; + } + + // 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.Backup); + + 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)); + } + + 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.Backup, + }; + + task = Task.Run(() => + { + app.PerformProfileBackup(profile); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_BackupRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } private static IList ShutdownServer(string channelId, string profileId) @@ -183,7 +261,87 @@ namespace ServerManagerTool.Utils private static IList UpdateServer(string channelId, string profileId) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Update) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Update) }; + } + + // 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.Update); + + ServerProfileSnapshot profile = null; + bool performRestart = false; + 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.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, ServerRuntime.GetServerStatusString(ServerStatus.Stopped))); + + case ServerStatus.Running: + performRestart = true; + break; + + 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.Update, + 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, performRestart, true, false, CancellationToken.None); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_UpdateRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } } } diff --git a/src/ARKServerManager/Windows/MainWindow.xaml b/src/ARKServerManager/Windows/MainWindow.xaml index 422c1c1c..2c7bd445 100644 --- a/src/ARKServerManager/Windows/MainWindow.xaml +++ b/src/ARKServerManager/Windows/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:com="clr-namespace:ServerManagerTool.Common;assembly=ServerManager.Common" xmlns:enum="clr-namespace:ServerManagerTool.Enums" MinWidth="900" MinHeight="600" Width="1100" Height="900" Left="50" Top="50" WindowState="Normal" - Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" LocationChanged="Window_LocationChanged" + Loaded="MainWindow_Loaded" SizeChanged="MainWindow_SizeChanged" StateChanged="MainWindow_StateChanged" LocationChanged="MainWindow_LocationChanged" Name="Main" Icon="../Art/favicon.ico" Title="{DynamicResource MainWindow_Title}"> diff --git a/src/ARKServerManager/Windows/MainWindow.xaml.cs b/src/ARKServerManager/Windows/MainWindow.xaml.cs index d4ea4f77..352049ae 100644 --- a/src/ARKServerManager/Windows/MainWindow.xaml.cs +++ b/src/ARKServerManager/Windows/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using ServerManagerTool.Common.Utils; using ServerManagerTool.Enums; using ServerManagerTool.Lib; using ServerManagerTool.Plugin.Common; +using ServerManagerTool.Utils; using ServerManagerTool.Windows; using System; using System.Diagnostics; @@ -169,7 +170,7 @@ namespace ServerManagerTool GlobalizedApplication.Instance.GlobalizationManager.ResourceDictionaryChangedEvent += ResourceDictionaryChangedEvent; } - private void Window_Loaded(object sender, RoutedEventArgs e) + private void MainWindow_Loaded(object sender, RoutedEventArgs e) { // // Kick off the initialization. @@ -199,15 +200,7 @@ namespace ServerManagerTool this.scheduledTaskChecker.PostAction(CheckForScheduledTasks).DoNotWait(); } - private void Window_Closed(object sender, EventArgs e) - { - if (sender is Window window) - window.Closed -= Window_Closed; - - this.Activate(); - } - - private void Window_LocationChanged(object sender, EventArgs e) + private void MainWindow_LocationChanged(object sender, EventArgs e) { if (this.WindowState == WindowState.Normal) { @@ -216,7 +209,7 @@ namespace ServerManagerTool } } - private void Window_SizeChanged(object sender, SizeChangedEventArgs e) + private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) { if (this.WindowState == WindowState.Normal) { @@ -225,7 +218,7 @@ namespace ServerManagerTool } } - private void Window_StateChanged(object sender, EventArgs e) + private void MainWindow_StateChanged(object sender, EventArgs e) { if (Config.Default.MainWindow_MinimizeToTray && this.WindowState == WindowState.Minimized) { @@ -233,8 +226,26 @@ namespace ServerManagerTool } } + private void Window_Closed(object sender, EventArgs e) + { + if (sender is Window window) + window.Closed -= Window_Closed; + + this.Activate(); + } + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { + if (DiscordBotHelper.HasRunningCommands) + { + var result = MessageBox.Show(_globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsLabel"), _globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question); + if (result == MessageBoxResult.No) + { + e.Cancel = true; + return; + } + } + base.OnClosing(e); RCONWindow.CloseAllWindows(); PlayerListWindow.CloseAllWindows(); diff --git a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs index 6e141131..35f78c99 100644 --- a/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs +++ b/src/ARKServerManager/Windows/ServerMonitorWindow.xaml.cs @@ -623,7 +623,7 @@ namespace ServerManagerTool.Windows await Task.Delay(1000); - var branch = new ServerBranchSnapshot() { BranchName = serverProfile.BranchName, BranchPassword = serverProfile.BranchPassword }; + var branch = BranchSnapshot.Create(serverProfile); return await server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); }); } else diff --git a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs index 9b53372e..f78a1888 100644 --- a/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs +++ b/src/ARKServerManager/Windows/ServerSettingsControl.xaml.cs @@ -4266,7 +4266,7 @@ namespace ServerManagerTool await Task.Delay(1000); - var branch = new ServerBranchSnapshot() { BranchName = this.Server.Profile.BranchName, BranchPassword = this.Server.Profile.BranchPassword }; + var branch = BranchSnapshot.Create(this.Server.Profile); return await this.Server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); }); } else diff --git a/src/ConanServerManager/ConanServerManager.csproj b/src/ConanServerManager/ConanServerManager.csproj index 463cdd2a..313ddc73 100644 --- a/src/ConanServerManager/ConanServerManager.csproj +++ b/src/ConanServerManager/ConanServerManager.csproj @@ -160,6 +160,7 @@ MSBuild:Compile Designer + diff --git a/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs b/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs new file mode 100644 index 00000000..c7ab834a --- /dev/null +++ b/src/ConanServerManager/Delegates/ServerStatusChangeDelegate.cs @@ -0,0 +1,6 @@ +using ServerManagerTool.Enums; + +namespace ServerManagerTool.Delegates +{ + public delegate void ServerStatusChangeDelegate(ServerStatus serverStatus); +} diff --git a/src/ConanServerManager/Enums/ServerProcessType.cs b/src/ConanServerManager/Enums/ServerProcessType.cs index 861e2081..7298dfd9 100644 --- a/src/ConanServerManager/Enums/ServerProcessType.cs +++ b/src/ConanServerManager/Enums/ServerProcessType.cs @@ -10,5 +10,6 @@ Backup, Shutdown, Restart, + Update, } } diff --git a/src/ConanServerManager/Globalization/en-US/en-US.xaml b/src/ConanServerManager/Globalization/en-US/en-US.xaml index c56e4c92..831ab091 100644 --- a/src/ConanServerManager/Globalization/en-US/en-US.xaml +++ b/src/ConanServerManager/Globalization/en-US/en-US.xaml @@ -628,6 +628,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 @@ -1217,12 +1220,17 @@ Command '{0}' has not been implemented. Unknown command '{0}'. - Another command is currently running. + Another command is currently being processed. + Another command '{0}' is currently running against profile '{1}'. - The command requires a profile id. - Profile id {0} not found or is not associated with the channel. + 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 currently being updated. - Call to the server '{0}' failed. + Call to server '{0}' failed. + A backup request for server '{0}' has been sent. + An update request for server '{0}' has been sent. Count: Map: diff --git a/src/ConanServerManager/Lib/BranchSnapshot.cs b/src/ConanServerManager/Lib/BranchSnapshot.cs index 2b81437d..c3b0283d 100644 --- a/src/ConanServerManager/Lib/BranchSnapshot.cs +++ b/src/ConanServerManager/Lib/BranchSnapshot.cs @@ -5,8 +5,30 @@ namespace ServerManagerTool.Lib { 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 BranchSnapshotComparer : IEqualityComparer diff --git a/src/ConanServerManager/Lib/ServerApp.cs b/src/ConanServerManager/Lib/ServerApp.cs index 7d745a88..cc50048a 100644 --- a/src/ConanServerManager/Lib/ServerApp.cs +++ b/src/ConanServerManager/Lib/ServerApp.cs @@ -3,6 +3,7 @@ using ServerManagerTool.Common; 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; @@ -108,6 +109,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) { @@ -261,7 +263,15 @@ namespace ServerManagerTool.Lib if (updateServer) { - UpgradeLocal(true, steamCmdRemoveQuit, cancellationToken, true); + try + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); + UpgradeLocal(true, steamCmdRemoveQuit, cancellationToken, true); + } + finally + { + ServerStatusChangeCallback?.Invoke(ServerStatus.Stopped); + } } if (ExitCode != EXITCODE_NORMALEXIT) @@ -3060,7 +3070,7 @@ namespace ServerManagerTool.Lib if (exitCode == EXITCODE_NORMALEXIT) { - var branches = _profiles.Keys.Where(p => p.EnableAutoUpdate).Select(p => new BranchSnapshot() { BranchName = p.BranchName, BranchPassword = p.BranchPassword }).Distinct(new BranchSnapshotComparer()).ToArray(); + 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 diff --git a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs index 543aa4c2..cd341e57 100644 --- a/src/ConanServerManager/Lib/ServerProfileSnapshot.cs +++ b/src/ConanServerManager/Lib/ServerProfileSnapshot.cs @@ -7,6 +7,10 @@ namespace ServerManagerTool.Lib { public class ServerProfileSnapshot { + private ServerProfileSnapshot() + { + } + public string ProfileId; public string ProfileName; public string ServerName; diff --git a/src/ConanServerManager/Lib/ServerRuntime.cs b/src/ConanServerManager/Lib/ServerRuntime.cs index 479e14e5..0d9e20da 100644 --- a/src/ConanServerManager/Lib/ServerRuntime.cs +++ b/src/ConanServerManager/Lib/ServerRuntime.cs @@ -32,7 +32,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; @@ -442,7 +442,7 @@ namespace ServerManagerTool.Lib } } - UpdateServerStatus(ServerStatus.Initializing, this.Availability, false); + UpdateServerStatus(ServerStatus.Initializing, false); try { @@ -516,7 +516,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.DataPath); @@ -938,7 +938,7 @@ namespace ServerManagerTool.Lib finally { this.lastModStatusQuery = DateTime.MinValue; - UpdateServerStatus(ServerStatus.Stopped, Availability, false); + UpdateServerStatus(ServerStatus.Stopped, false); } } @@ -961,6 +961,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; @@ -974,32 +979,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"); } } diff --git a/src/ConanServerManager/Utils/DiscordBotHelper.cs b/src/ConanServerManager/Utils/DiscordBotHelper.cs index fc793e95..b1f16177 100644 --- a/src/ConanServerManager/Utils/DiscordBotHelper.cs +++ b/src/ConanServerManager/Utils/DiscordBotHelper.cs @@ -1,11 +1,15 @@ using QueryMaster; using ServerManagerTool.Common.Utils; using ServerManagerTool.DiscordBot.Enums; +using ServerManagerTool.Enums; using ServerManagerTool.Lib; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; +using System.Threading; +using System.Threading.Tasks; using WPFSharp.Globalizer; namespace ServerManagerTool.Utils @@ -15,6 +19,10 @@ namespace ServerManagerTool.Utils private static readonly GlobalizedApplication _globalizer = GlobalizedApplication.Instance; private static bool _runningCommand = false; + private static readonly Dictionary _currentProfileCommands = new Dictionary(); + + public static bool HasRunningCommands => _currentProfileCommands.Count > 0; + public static IList HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) { // check if incoming values are valid @@ -57,7 +65,8 @@ namespace ServerManagerTool.Utils } catch (Exception ex) { - return new string[] { ex.Message }; + var message = ex.InnerException is null ? ex.Message : ex.InnerException.Message; + return new string[] { message }; } finally { @@ -74,54 +83,68 @@ namespace ServerManagerTool.Utils { if (string.IsNullOrWhiteSpace(profileId)) { - return new List { _globalizer.GetResourceString("DiscordBot_CommandProfileMissing") }; + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Info) }; } - var serverName = string.Empty; - var serverIp = IPAddress.Loopback; - var queryPort = 0; - - TaskUtils.RunOnUIThreadAsync(() => + // check if another command is being run against the profile + if (_currentProfileCommands.ContainsKey(profileId)) { - var server = ServerManager.Instance.Servers.Where(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)).FirstOrDefault(); - - if (server is null) - { - throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_CommandProfileNotFound"), profileId)); - } - - serverName = server.Profile.ServerName; - if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) - { - IPAddress.TryParse(server.Profile.ServerIP, out serverIp); - } - queryPort = server.Profile.QueryPort; - }).Wait(); - - List response = new List(); + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_CommandRunningProfile"), _currentProfileCommands[profileId], profileId) }; + } + _currentProfileCommands.Add(profileId, CommandType.Info); try { - using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort))) + var serverName = string.Empty; + var serverIp = IPAddress.Loopback; + var queryPort = 0; + + TaskUtils.RunOnUIThreadAsync(() => { - var info = gameServer?.GetInfo(); - if (info is null) + var server = ServerManager.Instance.Servers.FirstOrDefault(s => Equals(channelId, s.Profile.DiscordChannelId) && Equals(profileId, s.Profile.ProfileID)); + + if (server is null) { - response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_CommandInfoFailed"), serverName)); + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); } - else + + serverName = server.Profile.ServerName; + if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) { - var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map; - response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```"); + IPAddress.TryParse(server.Profile.ServerIP, out serverIp); + } + queryPort = server.Profile.QueryPort; + }).Wait(); + + List response = new List(); + + try + { + using (var gameServer = ServerQuery.GetServerInstance(EngineType.Source, new IPEndPoint(serverIp, queryPort))) + { + var info = gameServer?.GetInfo(); + if (info is null) + { + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName)); + } + else + { + var mapName = _globalizer.GetResourceString($"Map_{info.Map}") ?? info.Map; + response.Add($"```{info.Name}\n{_globalizer.GetResourceString("DiscordBot_MapLabel")} {mapName}\n{_globalizer.GetResourceString("ServerSettings_PlayersLabel")} {info.Players} / {info.MaxPlayers}```"); + } } } - } - catch (Exception) - { - response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_CommandInfoFailed"), serverName)); - } + catch (Exception) + { + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_InfoFailed"), serverName)); + } - return response; + return response; + } + finally + { + _currentProfileCommands.Remove(profileId); + } } private static IList GetServerList(string channelId) @@ -162,7 +185,63 @@ namespace ServerManagerTool.Utils private static IList BackupServer(string channelId, string profileId) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Backup) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Backup) }; + } + + // 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.Backup); + + 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)); + } + + 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.Backup, + }; + + task = Task.Run(() => + { + app.PerformProfileBackup(profile); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_BackupRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } private static IList ShutdownServer(string channelId, string profileId) @@ -182,7 +261,87 @@ namespace ServerManagerTool.Utils private static IList UpdateServer(string channelId, string profileId) { - return new List() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Update) }; + if (string.IsNullOrWhiteSpace(profileId)) + { + return new List { string.Format(_globalizer.GetResourceString("DiscordBot_ProfileMissing"), CommandType.Update) }; + } + + // 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.Update); + + ServerProfileSnapshot profile = null; + bool performRestart = false; + 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.Unknown: + throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileBadStatus"), profileId, ServerRuntime.GetServerStatusString(ServerStatus.Stopped))); + + case ServerStatus.Running: + performRestart = true; + break; + + 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.Update, + 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, performRestart, true, false, false, CancellationToken.None); + _currentProfileCommands.Remove(profileId); + }); + + response.Add(string.Format(_globalizer.GetResourceString("DiscordBot_UpdateRequested"), profile.ServerName)); + + return response; + } + finally + { + if (task is null) + { + _currentProfileCommands.Remove(profileId); + } + } } } } diff --git a/src/ConanServerManager/Windows/MainWindow.xaml b/src/ConanServerManager/Windows/MainWindow.xaml index e83bab9b..8a4cd1d7 100644 --- a/src/ConanServerManager/Windows/MainWindow.xaml +++ b/src/ConanServerManager/Windows/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:com="clr-namespace:ServerManagerTool.Common;assembly=ServerManager.Common" xmlns:enum="clr-namespace:ServerManagerTool.Enums" MinWidth="900" MinHeight="600" Width="1100" Height="900" Left="50" Top="50" WindowState="Normal" - Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" LocationChanged="Window_LocationChanged" + Loaded="MainWindow_Loaded" SizeChanged="MainWindow_SizeChanged" StateChanged="MainWindow_StateChanged" LocationChanged="MainWindow_LocationChanged" Name="Main" Icon="../Art/favicon.ico" Title="{DynamicResource MainWindow_Title}"> diff --git a/src/ConanServerManager/Windows/MainWindow.xaml.cs b/src/ConanServerManager/Windows/MainWindow.xaml.cs index be46d9b5..cbd30acb 100644 --- a/src/ConanServerManager/Windows/MainWindow.xaml.cs +++ b/src/ConanServerManager/Windows/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using ServerManagerTool.Common.Utils; using ServerManagerTool.Enums; using ServerManagerTool.Lib; using ServerManagerTool.Plugin.Common; +using ServerManagerTool.Utils; using ServerManagerTool.Windows; using System; using System.Diagnostics; @@ -162,7 +163,7 @@ namespace ServerManagerTool GlobalizedApplication.Instance.GlobalizationManager.ResourceDictionaryChangedEvent += ResourceDictionaryChangedEvent; } - private void Window_Loaded(object sender, RoutedEventArgs e) + private void MainWindow_Loaded(object sender, RoutedEventArgs e) { // // Kick off the initialization. @@ -192,15 +193,7 @@ namespace ServerManagerTool this.scheduledTaskChecker.PostAction(CheckForScheduledTasks).DoNotWait(); } - private void Window_Closed(object sender, EventArgs e) - { - if (sender is Window window) - window.Closed -= Window_Closed; - - this.Activate(); - } - - private void Window_LocationChanged(object sender, EventArgs e) + private void MainWindow_LocationChanged(object sender, EventArgs e) { if (this.WindowState == WindowState.Normal) { @@ -209,7 +202,7 @@ namespace ServerManagerTool } } - private void Window_SizeChanged(object sender, SizeChangedEventArgs e) + private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e) { if (this.WindowState == WindowState.Normal) { @@ -218,7 +211,7 @@ namespace ServerManagerTool } } - private void Window_StateChanged(object sender, EventArgs e) + private void MainWindow_StateChanged(object sender, EventArgs e) { if (Config.Default.MainWindow_MinimizeToTray && this.WindowState == WindowState.Minimized) { @@ -226,8 +219,26 @@ namespace ServerManagerTool } } + private void Window_Closed(object sender, EventArgs e) + { + if (sender is Window window) + window.Closed -= Window_Closed; + + this.Activate(); + } + protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { + if (DiscordBotHelper.HasRunningCommands) + { + var result = MessageBox.Show(_globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsLabel"), _globalizer.GetResourceString("MainWindow_DiscordBot_RunningCommandsTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question); + if (result == MessageBoxResult.No) + { + e.Cancel = true; + return; + } + } + base.OnClosing(e); RconWindow.CloseAllWindows(); PlayerListWindow.CloseAllWindows(); diff --git a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs index 7b7990c4..62f3b844 100644 --- a/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs +++ b/src/ConanServerManager/Windows/ServerMonitorWindow.xaml.cs @@ -620,7 +620,7 @@ namespace ServerManagerTool.Windows await Task.Delay(1000); - var branch = new BranchSnapshot() { BranchName = serverProfile.BranchName, BranchPassword = serverProfile.BranchPassword }; + var branch = BranchSnapshot.Create(serverProfile); return await server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); }); } else diff --git a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs index 24825056..55975a98 100644 --- a/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs +++ b/src/ConanServerManager/Windows/ServerSettingsControl.xaml.cs @@ -1449,7 +1449,7 @@ namespace ServerManagerTool await Task.Delay(1000); - var branch = new BranchSnapshot() { BranchName = this.Server.Profile.BranchName, BranchPassword = this.Server.Profile.BranchPassword }; + var branch = BranchSnapshot.Create(this.Server.Profile); return await this.Server.UpgradeAsync(_upgradeCancellationSource.Token, updateServer, branch, true, updateMods, (p, m, n) => { TaskUtils.RunOnUIThreadAsync(() => { window?.AddMessage(m, n); }).DoNotWait(); }); } else diff --git a/src/ServerManager.Discord/Modules/ServerCommandModule.cs b/src/ServerManager.Discord/Modules/ServerCommandModule.cs index 6948f4bc..57b772b2 100644 --- a/src/ServerManager.Discord/Modules/ServerCommandModule.cs +++ b/src/ServerManager.Discord/Modules/ServerCommandModule.cs @@ -23,15 +23,6 @@ namespace ServerManagerTool.DiscordBot.Modules _config = config; } - [Command("backup", RunMode = RunMode.Async)] - [Summary("Perform a backup of the server")] - [Remarks("backup")] - [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] - public async Task BackupServerAsync() - { - await BackupServerAsync(null); - } - [Command("backup", RunMode = RunMode.Async)] [Summary("Perform a backup of the server")] [Remarks("backup profileId")] @@ -63,15 +54,6 @@ namespace ServerManagerTool.DiscordBot.Modules } } - [Command("shutdown", RunMode = RunMode.Async)] - [Summary("Shuts down the server properly")] - [Remarks("shutdown")] - [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] - public async Task ShutdownServerAsync() - { - await ShutdownServerAsync(null); - } - [Command("shutdown", RunMode = RunMode.Async)] [Summary("Shuts down the server properly")] [Remarks("shutdown profileId")] @@ -103,15 +85,6 @@ namespace ServerManagerTool.DiscordBot.Modules } } - [Command("start", RunMode = RunMode.Async)] - [Summary("Starts the server")] - [Remarks("start")] - [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] - public async Task StartServerAsync() - { - await StartServerAsync(null); - } - [Command("start", RunMode = RunMode.Async)] [Summary("Starts the server")] [Remarks("start profileId")] @@ -143,15 +116,6 @@ namespace ServerManagerTool.DiscordBot.Modules } } - [Command("stop", RunMode = RunMode.Async)] - [Summary("Forcibly stops the server")] - [Remarks("stop")] - [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] - public async Task StopServerAsync() - { - await StopServerAsync(null); - } - [Command("stop", RunMode = RunMode.Async)] [Summary("Forcibly stops the server")] [Remarks("stop profileId")] @@ -183,15 +147,6 @@ namespace ServerManagerTool.DiscordBot.Modules } } - [Command("update", RunMode = RunMode.Async)] - [Summary("Updates the server")] - [Remarks("update")] - [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] - public async Task UpdateServerAsync() - { - await UpdateServerAsync(null); - } - [Command("update", RunMode = RunMode.Async)] [Summary("Updates the server")] [Remarks("update profileId")]