Finished the remaing bot commands

This commit is contained in:
Brett Hewitson 2021-12-05 15:08:37 +10:00
parent 0f3c6e6be9
commit aa62646f0f
23 changed files with 1041 additions and 287 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -5555,11 +5555,15 @@
<sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String> <sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String>
<sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String> <sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String>
<sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' must be '{1}' to perform the command.</sys:String> <sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' is in a state '{1}' that cannot run this command.</sys:String>
<sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String> <sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String>
<sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String> <sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String>
<sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String> <sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_RestartRequested">A restart request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_ShutdownRequested">A shutdown request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StartRequested">A start request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StopRequested">A stop request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String> <sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String> <sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String>

View file

@ -114,7 +114,7 @@ namespace ServerManagerTool.Lib
_startTime = DateTime.Now; _startTime = DateTime.Now;
} }
private void BackupServer() private void BackupServer(CancellationToken cancellationToken)
{ {
if (_profile == null || _profile.SotFEnabled) if (_profile == null || _profile.SotFEnabled)
{ {
@ -142,65 +142,65 @@ namespace ServerManagerTool.Lib
if (_serverRunning) if (_serverRunning)
{ {
// check if RCON is enabled try
if (_profile.RCONEnabled)
{ {
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(); emailMessage.AppendLine("sent server save message.");
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}");
} }
} }
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) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
// make a backup of the current profile and config files. // make a backup of the current profile and config files.
CreateProfileBackupArchiveFile(_profile); CreateProfileBackupArchiveFile(_profile);
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
// make a backup of the current world file. // make a backup of the current world file.
CreateServerBackupArchiveFile(emailMessage, _profile); CreateServerBackupArchiveFile(emailMessage, _profile);
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
if (Config.Default.EmailNotify_AutoBackup) if (Config.Default.EmailNotify_AutoBackup)
{ {
@ -269,7 +269,7 @@ namespace ServerManagerTool.Lib
try try
{ {
ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); ServerStatusChangeCallback?.Invoke(ServerStatus.Updating);
UpgradeLocal(true, cancellationToken, true); UpgradeLocal(true, true, cancellationToken);
} }
finally finally
{ {
@ -310,17 +310,20 @@ namespace ServerManagerTool.Lib
return; return;
} }
// check if the server was previously running before the update. // check if the server was previously running.
if (!_serverRunning && !_profile.AutoRestartIfShutdown) 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; ExitCode = EXITCODE_NORMALEXIT;
return; return;
} }
if (!_serverRunning && _profile.AutoRestartIfShutdown)
{
LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
} }
// Find the server process. // Find the server process.
@ -404,7 +407,7 @@ namespace ServerManagerTool.Lib
try try
{ {
// create a connection to the server // 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); gameServer = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint);
// check if there is a shutdown reason // check if there is a shutdown reason
@ -417,10 +420,6 @@ namespace ServerManagerTool.Lib
} }
LogProfileMessage("Starting shutdown timer..."); LogProfileMessage("Starting shutdown timer...");
if (!CheckForOnlinePlayers)
{
LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check.");
}
var minutesLeft = ShutdownInterval; var minutesLeft = ShutdownInterval;
while (minutesLeft > 0) while (minutesLeft > 0)
@ -432,7 +431,7 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{ {
ProcessAlert(AlertType.Shutdown, 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; ExitCode = EXITCODE_CANCELLED;
@ -462,7 +461,8 @@ namespace ServerManagerTool.Lib
} }
else else
{ {
Debug.WriteLine($"CheckForOnlinePlayers disabled"); Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled.");
break;
} }
var message = string.Empty; var message = string.Empty;
@ -536,7 +536,7 @@ namespace ServerManagerTool.Lib
{ {
LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage); LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage);
ProcessAlert(AlertType.ShutdownMessage, 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) if (SendCommandAsync(Config.Default.ServerSaveCommand, false).Result)
@ -561,7 +561,7 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{ {
ProcessAlert(AlertType.Shutdown, 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; ExitCode = EXITCODE_CANCELLED;
@ -587,8 +587,6 @@ namespace ServerManagerTool.Lib
} }
finally finally
{ {
CloseRconConsole();
gameServer?.Dispose(); gameServer?.Dispose();
gameServer = null; gameServer = null;
} }
@ -600,11 +598,9 @@ namespace ServerManagerTool.Lib
if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage)) if (!string.IsNullOrWhiteSpace(Config.Default.ServerShutdown_CancelMessage))
{ {
ProcessAlert(AlertType.Shutdown, 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; ExitCode = EXITCODE_CANCELLED;
return; return;
} }
@ -653,8 +649,6 @@ namespace ServerManagerTool.Lib
LogProfileMessage("Exiting server timed out, attempting to close the server."); LogProfileMessage("Exiting server timed out, attempting to close the server.");
} }
CloseRconConsole();
// Method 2 - Close the process // Method 2 - Close the process
sent = process.CloseMainWindow(); sent = process.CloseMainWindow();
@ -721,7 +715,7 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_SHUTDOWN_TIMEOUT; 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) if (_profile == null)
{ {
@ -1250,7 +1244,7 @@ namespace ServerManagerTool.Lib
{ {
// perform a steamcmd validate to confirm all the files // perform a steamcmd validate to confirm all the files
LogProfileMessage("Validating server files (*new*)."); LogProfileMessage("Validating server files (*new*).");
UpgradeLocal(true, CancellationToken.None, false); UpgradeLocal(true, false, CancellationToken.None);
LogProfileMessage("Validated server files (*new*)."); LogProfileMessage("Validated server files (*new*).");
} }
@ -1776,17 +1770,6 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_NORMALEXIT; ExitCode = EXITCODE_NORMALEXIT;
} }
private void CloseRconConsole()
{
if (_rconConsole != null)
{
_rconConsole.Dispose();
_rconConsole = null;
Task.Delay(1000).Wait();
}
}
public void CheckServerWorldFileExists(ServerProfileSnapshot profile = null) public void CheckServerWorldFileExists(ServerProfileSnapshot profile = null)
{ {
// do nothing if profile is null or SotF // do nothing if profile is null or SotF
@ -2578,39 +2561,42 @@ namespace ServerManagerTool.Lib
int rconRetries = 0; int rconRetries = 0;
int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1; int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1;
while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) try
{ {
SetupRconConsole(); while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
if (_rconConsole == null)
{ {
LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); SetupRconConsole();
#if DEBUG
LogProfileMessage("RCON connection not created.", false);
#endif
rconRetries++;
}
else
{
rconRetries = 0;
try
{
_rconConsole.SendCommand(command);
LogProfileMessage($"RCON> {command}");
return true; if (_rconConsole == null)
}
catch (Exception ex)
{ {
LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
#if DEBUG LogProfileMessage("RCON connection not created.", false);
LogProfileMessage($"{ex.Message}", false); rconRetries++;
#endif
} }
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; 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() private void SetupRconConsole()
{ {
CloseRconConsole(); CloseRconConsole();
@ -2681,7 +2678,7 @@ namespace ServerManagerTool.Lib
try 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); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null) if (server == null)
{ {
@ -2718,7 +2715,7 @@ namespace ServerManagerTool.Lib
} }
} }
public int PerformProfileBackup(ServerProfileSnapshot profile) public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken)
{ {
_profile = profile; _profile = profile;
@ -2743,7 +2740,7 @@ namespace ServerManagerTool.Lib
// check if the mutex was established // check if the mutex was established
if (createdNew) if (createdNew)
{ {
BackupServer(); BackupServer(cancellationToken);
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
{ {
@ -3099,7 +3096,7 @@ namespace ServerManagerTool.Lib
SendEmails = true, SendEmails = true,
ServerProcess = ServerProcessType.AutoBackup ServerProcess = ServerProcessType.AutoBackup
}; };
exitCodes.TryAdd(profile, app.PerformProfileBackup(profile)); exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None));
}); });
foreach (var profile in _profiles.Keys) foreach (var profile in _profiles.Keys)

View file

@ -20,7 +20,7 @@ namespace ServerManagerTool.Lib
public string AdminPassword; public string AdminPassword;
public string ServerName; public string ServerName;
public string ServerArgs; public string ServerArgs;
public string ServerIP; public IPAddress ServerIPAddress;
public int ServerPort; public int ServerPort;
public int ServerPeerPort; public int ServerPeerPort;
public int QueryPort; public int QueryPort;
@ -71,7 +71,7 @@ namespace ServerManagerTool.Lib
AdminPassword = profile.AdminPassword, AdminPassword = profile.AdminPassword,
ServerName = profile.ServerName, ServerName = profile.ServerName,
ServerArgs = profile.GetServerArgs(), 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, ServerPort = profile.ServerPort,
ServerPeerPort = profile.ServerPeerPort, ServerPeerPort = profile.ServerPeerPort,
QueryPort = profile.QueryPort, QueryPort = profile.QueryPort,

View file

@ -169,6 +169,9 @@ namespace ServerManagerTool.Lib
ServerProfile.ServerIPProperty, ServerProfile.ServerIPProperty,
ServerProfile.MaxPlayersProperty, ServerProfile.MaxPlayersProperty,
ServerProfile.ServerPasswordProperty,
ServerProfile.AdminPasswordProperty,
ServerProfile.ServerMapProperty, ServerProfile.ServerMapProperty,
ServerProfile.ServerModIdsProperty, ServerProfile.ServerModIdsProperty,
ServerProfile.TotalConversionModIdProperty, ServerProfile.TotalConversionModIdProperty,
@ -177,7 +180,7 @@ namespace ServerManagerTool.Lib
}, },
(s, p) => (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); AttachToProfileCore(profile);
} }
@ -198,16 +201,7 @@ namespace ServerManagerTool.Lib
return; return;
} }
if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress)) localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
{
// 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));
}
// //
// Get the public endpoint for querying Steam // Get the public endpoint for querying Steam
@ -1093,7 +1087,7 @@ namespace ServerManagerTool.Lib
try 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); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null) if (server == null)

View file

@ -23,7 +23,7 @@ namespace ServerManagerTool.Utils
public static bool HasRunningCommands => _currentProfileCommands.Count > 0; public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
public static IList<string> HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) public static IList<string> HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token)
{ {
// check if incoming values are valid // check if incoming values are valid
if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId)) if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId))
@ -49,15 +49,17 @@ namespace ServerManagerTool.Utils
return GetServerStatus(channelId, profileId); return GetServerStatus(channelId, profileId);
case CommandType.Backup: case CommandType.Backup:
return BackupServer(channelId, profileId); return BackupServer(channelId, profileId, token);
case CommandType.Restart:
return RestartServer(channelId, profileId, token);
case CommandType.Shutdown: case CommandType.Shutdown:
return ShutdownServer(channelId, profileId); return ShutdownServer(channelId, profileId, token);
case CommandType.Stop: case CommandType.Stop:
return StopServer(channelId, profileId); return StopServer(channelId, profileId, token);
case CommandType.Start: case CommandType.Start:
return StartServer(channelId, profileId); return StartServer(channelId, profileId, token);
case CommandType.Update: case CommandType.Update:
return UpdateServer(channelId, profileId); return UpdateServer(channelId, profileId, token);
default: default:
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) }; return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) };
@ -108,6 +110,17 @@ namespace ServerManagerTool.Utils
throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); 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; serverName = server.Profile.ServerName;
if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP))
{ {
@ -183,7 +196,7 @@ namespace ServerManagerTool.Utils
return response; return response;
} }
private static IList<string> BackupServer(string channelId, string profileId) private static IList<string> BackupServer(string channelId, string profileId, CancellationToken token)
{ {
if (string.IsNullOrWhiteSpace(profileId)) if (string.IsNullOrWhiteSpace(profileId))
{ {
@ -211,6 +224,16 @@ namespace ServerManagerTool.Utils
throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); 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); profile = ServerProfileSnapshot.Create(server.Profile);
}).Wait(); }).Wait();
@ -223,11 +246,19 @@ namespace ServerManagerTool.Utils
SendAlerts = true, SendAlerts = true,
SendEmails = false, SendEmails = false,
ServerProcess = ServerProcessType.Backup, 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(() => task = Task.Run(() =>
{ {
app.PerformProfileBackup(profile); app.PerformProfileBackup(profile, token);
_currentProfileCommands.Remove(profileId); _currentProfileCommands.Remove(profileId);
}); });
@ -244,22 +275,337 @@ namespace ServerManagerTool.Utils
} }
} }
private static IList<string> ShutdownServer(string channelId, string profileId) private static IList<string> RestartServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Shutdown) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StopServer(string channelId, string profileId) private static IList<string> ShutdownServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Stop) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StartServer(string channelId, string profileId) private static IList<string> StopServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Start) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId) private static IList<string> StartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId, CancellationToken token)
{ {
if (string.IsNullOrWhiteSpace(profileId)) if (string.IsNullOrWhiteSpace(profileId))
{ {
@ -293,7 +639,7 @@ namespace ServerManagerTool.Utils
case ServerStatus.Initializing: case ServerStatus.Initializing:
case ServerStatus.Stopping: case ServerStatus.Stopping:
case ServerStatus.Unknown: 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: case ServerStatus.Running:
performRestart = true; performRestart = true;
@ -327,7 +673,7 @@ namespace ServerManagerTool.Utils
task = Task.Run(() => task = Task.Run(() =>
{ {
app.PerformProfileShutdown(profile, performRestart, true, false, CancellationToken.None); app.PerformProfileShutdown(profile, performRestart, true, false, token);
_currentProfileCommands.Remove(profileId); _currentProfileCommands.Remove(profileId);
}); });

View file

@ -664,7 +664,7 @@ namespace ServerManagerTool
ProfileId = server.Runtime.ProfileSnapshot.ProfileId, ProfileId = server.Runtime.ProfileSnapshot.ProfileId,
ProfileName = server.Runtime.ProfileSnapshot.ProfileName, ProfileName = server.Runtime.ProfileSnapshot.ProfileName,
MaxPlayers = server.Runtime.MaxPlayers, MaxPlayers = server.Runtime.MaxPlayers,
RCONHost = server.Runtime.ProfileSnapshot.ServerIP, RCONHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(),
RCONPort = server.Runtime.ProfileSnapshot.RCONPort, RCONPort = server.Runtime.ProfileSnapshot.RCONPort,
PGM_Enabled = server.Profile.PGM_Enabled, PGM_Enabled = server.Profile.PGM_Enabled,

View file

@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows
var profile = ServerProfileSnapshot.Create(server.Profile); 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) if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
{ {
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");

View file

@ -1099,7 +1099,7 @@ namespace ServerManagerTool
var profile = ServerProfileSnapshot.Create(Server.Profile); 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) if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");

View file

@ -1225,11 +1225,15 @@
<sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String> <sys:String x:Key="DiscordBot_ProfileMissing">The '{0}' command requires a profile id.</sys:String>
<sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String> <sys:String x:Key="DiscordBot_ProfileNotFound">Profile '{0}' was not found or is not associated with the channel.</sys:String>
<sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' must be '{1}' to perform the command.</sys:String> <sys:String x:Key="DiscordBot_ProfileBadStatus">Profile '{0}' is in a state '{1}' that cannot run this command.</sys:String>
<sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String> <sys:String x:Key="DiscordBot_ProfileUpdating">Profile '{0}' is currently being updated.</sys:String>
<sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String> <sys:String x:Key="DiscordBot_InfoFailed">Call to server '{0}' failed.</sys:String>
<sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String> <sys:String x:Key="DiscordBot_BackupRequested">A backup request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_RestartRequested">A restart request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_ShutdownRequested">A shutdown request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StartRequested">A start request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_StopRequested">A stop request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String> <sys:String x:Key="DiscordBot_UpdateRequested">An update request for server '{0}' has been sent.</sys:String>
<sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String> <sys:String x:Key="DiscordBot_CountLabel">Count:</sys:String>

View file

@ -117,7 +117,7 @@ namespace ServerManagerTool.Lib
_startTime = DateTime.Now; _startTime = DateTime.Now;
} }
private void BackupServer() private void BackupServer(CancellationToken cancellationToken)
{ {
if (_profile == null) if (_profile == null)
{ {
@ -146,58 +146,67 @@ namespace ServerManagerTool.Lib
if (_serverRunning) if (_serverRunning)
{ {
// check if RCON is enabled try
//if (_profile.RconEnabled) {
//{ emailMessage.AppendLine();
// try
// {
// emailMessage.AppendLine();
// // perform a world save var sent = false;
// 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.");
// }
// }
// sent = SendCommand(Config.Default.ServerSaveCommand, false); // perform a world save
// if (sent) if (!string.IsNullOrWhiteSpace(Config.Default.ServerBackup_WorldSaveMessage))
// { {
// emailMessage.AppendLine("sent server save command."); ProcessAlert(AlertType.Backup, Config.Default.ServerBackup_WorldSaveMessage);
// Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait(); sent = SendMessageAsync(Config.Default.ServerBackup_WorldSaveMessage, CancellationToken.None).Result;
// } if (sent)
// } {
// catch (Exception ex) emailMessage.AppendLine("sent server save message.");
// { }
// CloseRconConsole(); }
// Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}"); sent = SendCommandAsync(Config.Default.ServerSaveCommand, false).Result;
// } if (sent)
//} {
//else emailMessage.AppendLine("sent server save command.");
//{ Task.Delay(Config.Default.ServerShutdown_WorldSaveDelay * 1000).Wait();
// LogProfileMessage("RCON not enabled."); }
//} }
catch (Exception ex)
{
CloseRconConsole();
Debug.WriteLine($"RCON> {Config.Default.ServerSaveCommand} command.\r\n{ex.Message}");
}
} }
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
// make a backup of the current profile and config files. // make a backup of the current profile and config files.
CreateProfileBackupArchiveFile(); CreateProfileBackupArchiveFile();
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
// make a backup of the current world file. // make a backup of the current world file.
CreateServerBackupArchiveFile(emailMessage); CreateServerBackupArchiveFile(emailMessage);
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
return; return;
if (cancellationToken.IsCancellationRequested)
{
ExitCode = EXITCODE_CANCELLED;
return;
}
if (Config.Default.EmailNotify_AutoBackup) if (Config.Default.EmailNotify_AutoBackup)
{ {
@ -266,7 +275,7 @@ namespace ServerManagerTool.Lib
try try
{ {
ServerStatusChangeCallback?.Invoke(ServerStatus.Updating); ServerStatusChangeCallback?.Invoke(ServerStatus.Updating);
UpgradeLocal(true, steamCmdRemoveQuit, cancellationToken, true); UpgradeLocal(true, true, steamCmdRemoveQuit, cancellationToken);
} }
finally finally
{ {
@ -307,17 +316,20 @@ namespace ServerManagerTool.Lib
return; return;
} }
// check if the server was previously running before the update. // check if the server was previously running.
if (!_serverRunning && !_profile.AutoRestartIfShutdown) 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; ExitCode = EXITCODE_NORMALEXIT;
return; return;
} }
if (!_serverRunning && _profile.AutoRestartIfShutdown)
{
LogProfileMessage("Server was not running, server will be started as the setting to restart if shutdown is TRUE.");
} }
// Find the server process. // Find the server process.
@ -390,10 +402,15 @@ namespace ServerManagerTool.Lib
_serverRunning = true; _serverRunning = true;
LogProfileMessage($"Server process found PID {process.Id}."); LogProfileMessage($"Server process found PID {process.Id}.");
QueryMaster.Server gameServer = null;
bool sent = false; bool sent = false;
try 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 // check if there is a shutdown reason
if (!string.IsNullOrWhiteSpace(ShutdownReason) && !Config.Default.ServerShutdown_AllMessagesShowReason) if (!string.IsNullOrWhiteSpace(ShutdownReason) && !Config.Default.ServerShutdown_AllMessagesShowReason)
{ {
@ -404,10 +421,6 @@ namespace ServerManagerTool.Lib
} }
LogProfileMessage("Starting shutdown timer..."); LogProfileMessage("Starting shutdown timer...");
if (!CheckForOnlinePlayers)
{
LogProfileMessage("CheckForOnlinePlayers disabled, shutdown timer will not perform online player check.");
}
var minutesLeft = ShutdownInterval; var minutesLeft = ShutdownInterval;
while (minutesLeft > 0) while (minutesLeft > 0)
@ -430,8 +443,11 @@ namespace ServerManagerTool.Lib
{ {
try try
{ {
var gameFile = GetServerWorldFile(); // BH - commented out until Funcom fix the Online player status column in the world save database
var playerCount = DataContainer.GetOnlinePlayerCount(gameFile); //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 // check if anyone is logged into the server
if (playerCount <= 0) if (playerCount <= 0)
@ -449,7 +465,8 @@ namespace ServerManagerTool.Lib
} }
else else
{ {
Debug.WriteLine($"CheckForOnlinePlayers disabled"); Debug.WriteLine($"CheckForOnlinePlayers disabled, shutdown timer cancelled.");
break;
} }
var message = string.Empty; var message = string.Empty;
@ -516,7 +533,7 @@ namespace ServerManagerTool.Lib
// BH - commented out until funcom provide a way to send a save command // BH - commented out until funcom provide a way to send a save command
// check if we need to perform a world save // check if we need to perform a world save
//if (_profile.RconEnabled && Config.Default.ServerShutdown_EnableWorldSave) //if (Config.Default.ServerShutdown_EnableWorldSave)
//{ //{
// try // try
// { // {
@ -525,10 +542,10 @@ namespace ServerManagerTool.Lib
// { // {
// LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage); // LogProfileMessage(Config.Default.ServerShutdown_WorldSaveMessage);
// ProcessAlert(AlertType.ShutdownMessage, 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 // try
// { // {
@ -576,7 +593,8 @@ namespace ServerManagerTool.Lib
} }
finally finally
{ {
CloseRconConsole(); gameServer?.Dispose();
gameServer = null;
} }
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@ -589,8 +607,6 @@ namespace ServerManagerTool.Lib
SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait(); SendMessageAsync(Config.Default.ServerShutdown_CancelMessage, CancellationToken.None).Wait();
} }
CloseRconConsole();
ExitCode = EXITCODE_CANCELLED; ExitCode = EXITCODE_CANCELLED;
return; return;
} }
@ -664,7 +680,7 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_SHUTDOWN_TIMEOUT; 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) if (_profile == null)
{ {
@ -1182,7 +1198,7 @@ namespace ServerManagerTool.Lib
{ {
// perform a steamcmd validate to confirm all the files // perform a steamcmd validate to confirm all the files
LogProfileMessage("Validating server files (*new*)."); LogProfileMessage("Validating server files (*new*).");
UpgradeLocal(true, false, CancellationToken.None, false); UpgradeLocal(true, false, false, CancellationToken.None);
LogProfileMessage("Validated server files (*new*)."); LogProfileMessage("Validated server files (*new*).");
} }
@ -1703,17 +1719,6 @@ namespace ServerManagerTool.Lib
ExitCode = EXITCODE_NORMALEXIT; ExitCode = EXITCODE_NORMALEXIT;
} }
private void CloseRconConsole()
{
if (_rconConsole != null)
{
_rconConsole.Dispose();
_rconConsole = null;
Task.Delay(1000).Wait();
}
}
public void CreateProfileBackupArchiveFile(ServerProfileSnapshot profile = null) public void CreateProfileBackupArchiveFile(ServerProfileSnapshot profile = null)
{ {
var oldProfile = _profile; var oldProfile = _profile;
@ -2413,36 +2418,43 @@ namespace ServerManagerTool.Lib
int rconRetries = 0; int rconRetries = 0;
int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1; int maxRetries = retryIfFailed ? RCON_MAXRETRIES : 1;
while (retries < maxRetries && rconRetries < RCON_MAXRETRIES) try
{ {
SetupRconConsole(); while (retries < maxRetries && rconRetries < RCON_MAXRETRIES)
if (_rconConsole == null)
{ {
LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false); SetupRconConsole();
LogProfileMessage("RCON connection could not be created.", false);
rconRetries++;
}
else
{
rconRetries = 0;
try
{
_rconConsole.SendCommand(command);
LogProfileMessage($"RCON> {command}");
return true; if (_rconConsole == null)
}
catch (Exception ex)
{ {
LogProfileMessage($"RCON> {command} - attempt {retries + 1} (b).", false); LogProfileMessage($"RCON> {command} - attempt {rconRetries + 1} (a).", false);
LogProfileMessage($"{ex.Message}", false); LogProfileMessage("RCON connection could not be created.", false);
LogProfileMessage($"{ex.StackTrace}", 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; 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() private void SetupRconConsole()
{ {
CloseRconConsole(); CloseRconConsole();
@ -2513,7 +2536,7 @@ namespace ServerManagerTool.Lib
try 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); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null) if (server == null)
{ {
@ -2551,7 +2574,7 @@ namespace ServerManagerTool.Lib
} }
} }
public int PerformProfileBackup(ServerProfileSnapshot profile) public int PerformProfileBackup(ServerProfileSnapshot profile, CancellationToken cancellationToken)
{ {
_profile = profile; _profile = profile;
@ -2573,7 +2596,7 @@ namespace ServerManagerTool.Lib
// check if the mutex was established // check if the mutex was established
if (createdNew) if (createdNew)
{ {
BackupServer(); BackupServer(cancellationToken);
if (ExitCode != EXITCODE_NORMALEXIT) if (ExitCode != EXITCODE_NORMALEXIT)
{ {
@ -2926,7 +2949,7 @@ namespace ServerManagerTool.Lib
SendEmails = true, SendEmails = true,
ServerProcess = ServerProcessType.AutoBackup ServerProcess = ServerProcessType.AutoBackup
}; };
exitCodes.TryAdd(profile, app.PerformProfileBackup(profile)); exitCodes.TryAdd(profile, app.PerformProfileBackup(profile, CancellationToken.None));
}); });
foreach (var profile in _profiles.Keys) foreach (var profile in _profiles.Keys)

View file

@ -17,7 +17,7 @@ namespace ServerManagerTool.Lib
public string InstallDirectory; public string InstallDirectory;
public string GameFile; public string GameFile;
public string AdminPassword; public string AdminPassword;
public string ServerIP; public IPAddress ServerIPAddress;
public int ServerPort; public int ServerPort;
public int ServerPeerPort; public int ServerPeerPort;
public int QueryPort; public int QueryPort;
@ -60,7 +60,7 @@ namespace ServerManagerTool.Lib
InstallDirectory = profile.InstallDirectory, InstallDirectory = profile.InstallDirectory,
GameFile = profile.GetServerWorldFile(), GameFile = profile.GetServerWorldFile(),
AdminPassword = profile.AdminPassword, 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, ServerPort = profile.ServerPort,
ServerPeerPort = profile.ServerPeerPort, ServerPeerPort = profile.ServerPeerPort,
QueryPort = profile.QueryPort, QueryPort = profile.QueryPort,

View file

@ -167,13 +167,19 @@ namespace ServerManagerTool.Lib
ServerProfile.QueryPortProperty, ServerProfile.QueryPortProperty,
ServerProfile.ServerIPProperty, ServerProfile.ServerIPProperty,
ServerProfile.MaxPlayersProperty, ServerProfile.MaxPlayersProperty,
ServerProfile.ServerPasswordProperty,
ServerProfile.AdminPasswordProperty,
ServerProfile.ServerMapProperty, ServerProfile.ServerMapProperty,
ServerProfile.ServerMapSaveFileNameProperty, ServerProfile.ServerMapSaveFileNameProperty,
ServerProfile.ServerModIdsProperty, ServerProfile.ServerModIdsProperty,
ServerProfile.MOTDIntervalProperty,
}, },
(s, p) => (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); AttachToProfileCore(profile);
} }
@ -194,16 +200,7 @@ namespace ServerManagerTool.Lib
return; return;
} }
if (!String.IsNullOrWhiteSpace(this.ProfileSnapshot.ServerIP) && IPAddress.TryParse(this.ProfileSnapshot.ServerIP, out IPAddress localServerIpAddress)) localServerQueryEndPoint = new IPEndPoint(this.ProfileSnapshot.ServerIPAddress, Convert.ToUInt16(this.ProfileSnapshot.QueryPort));
{
// 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));
}
// //
// Get the public endpoint for querying Steam // Get the public endpoint for querying Steam
@ -1061,7 +1058,7 @@ namespace ServerManagerTool.Lib
try 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); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endPoint, sendTimeOut: 10000, receiveTimeOut: 10000);
if (server == null) if (server == null)

View file

@ -23,7 +23,7 @@ namespace ServerManagerTool.Utils
public static bool HasRunningCommands => _currentProfileCommands.Count > 0; public static bool HasRunningCommands => _currentProfileCommands.Count > 0;
public static IList<string> HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId) public static IList<string> HandleDiscordCommand(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token)
{ {
// check if incoming values are valid // check if incoming values are valid
if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId)) if (string.IsNullOrWhiteSpace(serverId) || string.IsNullOrWhiteSpace(channelId))
@ -49,15 +49,17 @@ namespace ServerManagerTool.Utils
return GetServerStatus(channelId, profileId); return GetServerStatus(channelId, profileId);
case CommandType.Backup: case CommandType.Backup:
return BackupServer(channelId, profileId); return BackupServer(channelId, profileId, token);
case CommandType.Restart:
return RestartServer(channelId, profileId, token);
case CommandType.Shutdown: case CommandType.Shutdown:
return ShutdownServer(channelId, profileId); return ShutdownServer(channelId, profileId, token);
case CommandType.Stop: case CommandType.Stop:
return StopServer(channelId, profileId); return StopServer(channelId, profileId, token);
case CommandType.Start: case CommandType.Start:
return StartServer(channelId, profileId); return StartServer(channelId, profileId, token);
case CommandType.Update: case CommandType.Update:
return UpdateServer(channelId, profileId); return UpdateServer(channelId, profileId, token);
default: default:
return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) }; return new List<string> { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), commandType) };
@ -108,6 +110,17 @@ namespace ServerManagerTool.Utils
throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); 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; serverName = server.Profile.ServerName;
if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP)) if (!string.IsNullOrWhiteSpace(server.Profile.ServerIP))
{ {
@ -183,7 +196,7 @@ namespace ServerManagerTool.Utils
return response; return response;
} }
private static IList<string> BackupServer(string channelId, string profileId) private static IList<string> BackupServer(string channelId, string profileId, CancellationToken token)
{ {
if (string.IsNullOrWhiteSpace(profileId)) if (string.IsNullOrWhiteSpace(profileId))
{ {
@ -211,6 +224,16 @@ namespace ServerManagerTool.Utils
throw new Exception(string.Format(_globalizer.GetResourceString("DiscordBot_ProfileNotFound"), profileId)); 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); profile = ServerProfileSnapshot.Create(server.Profile);
}).Wait(); }).Wait();
@ -223,11 +246,19 @@ namespace ServerManagerTool.Utils
SendAlerts = true, SendAlerts = true,
SendEmails = false, SendEmails = false,
ServerProcess = ServerProcessType.Backup, 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(() => task = Task.Run(() =>
{ {
app.PerformProfileBackup(profile); app.PerformProfileBackup(profile, token);
_currentProfileCommands.Remove(profileId); _currentProfileCommands.Remove(profileId);
}); });
@ -244,22 +275,337 @@ namespace ServerManagerTool.Utils
} }
} }
private static IList<string> ShutdownServer(string channelId, string profileId) private static IList<string> RestartServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Shutdown) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StopServer(string channelId, string profileId) private static IList<string> ShutdownServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Stop) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> StartServer(string channelId, string profileId) private static IList<string> StopServer(string channelId, string profileId, CancellationToken token)
{ {
return new List<string>() { string.Format(_globalizer.GetResourceString("DiscordBot_CommandUnknown"), CommandType.Start) }; if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId) private static IList<string> StartServer(string channelId, string profileId, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(profileId))
{
return new List<string> { 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> { 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<string> response = new List<string>();
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<string> UpdateServer(string channelId, string profileId, CancellationToken token)
{ {
if (string.IsNullOrWhiteSpace(profileId)) if (string.IsNullOrWhiteSpace(profileId))
{ {
@ -293,7 +639,7 @@ namespace ServerManagerTool.Utils
case ServerStatus.Initializing: case ServerStatus.Initializing:
case ServerStatus.Stopping: case ServerStatus.Stopping:
case ServerStatus.Unknown: 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: case ServerStatus.Running:
performRestart = true; performRestart = true;
@ -327,7 +673,7 @@ namespace ServerManagerTool.Utils
task = Task.Run(() => task = Task.Run(() =>
{ {
app.PerformProfileShutdown(profile, performRestart, true, false, false, CancellationToken.None); app.PerformProfileShutdown(profile, performRestart, true, false, false, token);
_currentProfileCommands.Remove(profileId); _currentProfileCommands.Remove(profileId);
}); });

View file

@ -542,7 +542,7 @@ namespace ServerManagerTool
ProfileId = server.Runtime.ProfileSnapshot.ProfileId, ProfileId = server.Runtime.ProfileSnapshot.ProfileId,
ProfileName = server.Runtime.ProfileSnapshot.ProfileName, ProfileName = server.Runtime.ProfileSnapshot.ProfileName,
MaxPlayers = server.Runtime.MaxPlayers, MaxPlayers = server.Runtime.MaxPlayers,
RconHost = server.Runtime.ProfileSnapshot.ServerIP, RconHost = server.Runtime.ProfileSnapshot.ServerIPAddress.ToString(),
RconPort = server.Runtime.ProfileSnapshot.RconPort, RconPort = server.Runtime.ProfileSnapshot.RconPort,
RconPassword = server.Runtime.ProfileSnapshot.RconPassword, RconPassword = server.Runtime.ProfileSnapshot.RconPassword,
}); });

View file

@ -182,7 +182,7 @@ namespace ServerManagerTool.Windows
var profile = ServerProfileSnapshot.Create(server.Profile); 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) if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
{ {
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");

View file

@ -834,7 +834,7 @@ namespace ServerManagerTool
var profile = ServerProfileSnapshot.Create(Server.Profile); 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) if (exitCode != ServerApp.EXITCODE_NORMALEXIT && exitCode != ServerApp.EXITCODE_CANCELLED)
throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}"); throw new ApplicationException($"An error occured during the backup process - ExitCode: {exitCode}");

View file

@ -1,7 +1,8 @@
using ServerManagerTool.DiscordBot.Enums; using ServerManagerTool.DiscordBot.Enums;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace ServerManagerTool.DiscordBot.Delegates namespace ServerManagerTool.DiscordBot.Delegates
{ {
public delegate IList<string> HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId); public delegate IList<string> HandleCommandDelegate(CommandType commandType, string serverId, string channelId, string profileId, CancellationToken token);
} }

View file

@ -7,6 +7,7 @@
Status, Status,
Backup, Backup,
Restart,
Shutdown, Shutdown,
Start, Start,
Stop, Stop,

View file

@ -6,6 +6,8 @@ namespace ServerManagerTool.DiscordBot.Interfaces
{ {
public interface IServerManagerBot public interface IServerManagerBot
{ {
CancellationToken Token { get; }
Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token); Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token);
} }
} }

View file

@ -4,7 +4,9 @@ using Discord.Commands;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using ServerManagerTool.DiscordBot.Delegates; using ServerManagerTool.DiscordBot.Delegates;
using ServerManagerTool.DiscordBot.Enums; using ServerManagerTool.DiscordBot.Enums;
using ServerManagerTool.DiscordBot.Interfaces;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ServerManagerTool.DiscordBot.Modules namespace ServerManagerTool.DiscordBot.Modules
@ -12,19 +14,21 @@ namespace ServerManagerTool.DiscordBot.Modules
[Name("Server Commands")] [Name("Server Commands")]
public sealed class ServerCommandModule : InteractiveBase public sealed class ServerCommandModule : InteractiveBase
{ {
private readonly IServerManagerBot _serverManagerBot;
private readonly CommandService _service; private readonly CommandService _service;
private readonly HandleCommandDelegate _handleCommandCallback; private readonly HandleCommandDelegate _handleCommandCallback;
private readonly IConfigurationRoot _config; 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; _service = service;
_handleCommandCallback = handleCommandCallback; _handleCommandCallback = handleCommandCallback;
_config = config; _config = config;
} }
[Command("backup", RunMode = RunMode.Async)] [Command("backup", RunMode = RunMode.Async)]
[Summary("Perform a backup of the server")] [Summary("Backup the server")]
[Remarks("backup profileId")] [Remarks("backup profileId")]
[RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)] [RequireBotPermission(ChannelPermission.ViewChannel | ChannelPermission.SendMessages)]
public async Task BackupServerAsync(string profileId) public async Task BackupServerAsync(string profileId)
@ -34,7 +38,38 @@ namespace ServerManagerTool.DiscordBot.Modules
var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); await ReplyAsync("No servers associated with this channel.");

View file

@ -4,7 +4,9 @@ using Discord.Commands;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using ServerManagerTool.DiscordBot.Delegates; using ServerManagerTool.DiscordBot.Delegates;
using ServerManagerTool.DiscordBot.Enums; using ServerManagerTool.DiscordBot.Enums;
using ServerManagerTool.DiscordBot.Interfaces;
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace ServerManagerTool.DiscordBot.Modules namespace ServerManagerTool.DiscordBot.Modules
@ -12,12 +14,14 @@ namespace ServerManagerTool.DiscordBot.Modules
[Name("Server Query")] [Name("Server Query")]
public sealed class ServerQueryModule : InteractiveBase public sealed class ServerQueryModule : InteractiveBase
{ {
private readonly IServerManagerBot _serverManagerBot;
private readonly CommandService _service; private readonly CommandService _service;
private readonly HandleCommandDelegate _handleCommandCallback; private readonly HandleCommandDelegate _handleCommandCallback;
private readonly IConfigurationRoot _config; 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; _service = service;
_handleCommandCallback = handleCommandCallback; _handleCommandCallback = handleCommandCallback;
_config = config; _config = config;
@ -43,7 +47,7 @@ namespace ServerManagerTool.DiscordBot.Modules
var serverId = Context?.Guild?.Id.ToString() ?? string.Empty; var serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); 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 serverId = Context?.Guild?.Id.ToString() ?? string.Empty;
var channelId = Context?.Channel?.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) if (response is null)
{ {
await ReplyAsync("No servers associated with this channel."); await ReplyAsync("No servers associated with this channel.");

View file

@ -24,11 +24,8 @@ namespace ServerManagerTool.DiscordBot
Started = false; Started = false;
} }
private bool Started public CancellationToken Token { get; private set; }
{ public bool Started { get; private set; }
get;
set;
}
public async Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token) public async Task StartAsync(string discordToken, string commandPrefix, string dataDirectory, HandleCommandDelegate handleCommandCallback, HandleTranslationDelegate handleTranslationCallback, CancellationToken token)
{ {
@ -43,6 +40,8 @@ namespace ServerManagerTool.DiscordBot
return; return;
} }
Token = token;
if (commandPrefix.Any(c => !char.IsLetterOrDigit(c))) if (commandPrefix.Any(c => !char.IsLetterOrDigit(c)))
{ {
throw new Exception("#DiscordBot_InvalidPrefixError"); throw new Exception("#DiscordBot_InvalidPrefixError");
@ -57,7 +56,7 @@ namespace ServerManagerTool.DiscordBot
{ {
{ "DiscordSettings:Token", discordToken }, { "DiscordSettings:Token", discordToken },
{ "DiscordSettings:Prefix", commandPrefix }, { "DiscordSettings:Prefix", commandPrefix },
{ "ServerManager:DataDirectory", dataDirectory } { "ServerManager:DataDirectory", dataDirectory },
}; };
// Begin building the configuration file // Begin building the configuration file
@ -107,7 +106,8 @@ namespace ServerManagerTool.DiscordBot
.AddSingleton<Random>() .AddSingleton<Random>()
.AddSingleton(config) .AddSingleton(config)
.AddSingleton(handleCommandCallback) .AddSingleton(handleCommandCallback)
.AddSingleton(handleTranslationCallback); .AddSingleton(handleTranslationCallback)
.AddSingleton<IServerManagerBot>(this);
// Create the service provider // Create the service provider
using (var provider = services.BuildServiceProvider()) using (var provider = services.BuildServiceProvider())