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