using System.Linq;
using System.Threading.Tasks;
using Content.Server.Chat.Managers;
using Content.Server.Discord;
using Content.Shared.CCVar;
using Robust.Server;
using Robust.Server.Player;
using Robust.Server.ServerStatus;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.ServerUpdates;
///
/// Responsible for restarting the server periodically or for update, when not disruptive.
///
///
/// This was originally only designed for restarting on *update*,
/// but now also handles periodic restarting to keep server uptime via .
///
public sealed partial class ServerUpdateManager : IPostInjectInit
{
[Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private IWatchdogApi _watchdog = default!;
[Dependency] private IPlayerManager _playerManager = default!;
[Dependency] private IChatManager _chatManager = default!;
[Dependency] private IBaseServer _server = default!;
[Dependency] private IConfigurationManager _cfg = default!;
[Dependency] private ILogManager _logManager = default!;
//WL-Chages-start
[Dependency] private DiscordWebhook _discord = default!;
private WebhookIdentifier? _discordWebhook;
//WL-Changes-end
private ISawmill _sawmill = default!;
[ViewVariables]
private bool _updateOnRoundEnd;
private TimeSpan? _restartTime;
private TimeSpan _uptimeRestart;
public void Initialize()
{
_watchdog.UpdateReceived += WatchdogOnUpdateReceived;
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
//WL-Changes-start
var url = _cfg.GetCVar(CCVars.DiscordRoundUpdateWebhook);
if (string.IsNullOrEmpty(url))
return;
_discord.GetWebhook(url, webhookData => _discordWebhook = webhookData.ToIdentifier());
//WL-Changes-end
_cfg.OnValueChanged(
CCVars.ServerUptimeRestartMinutes,
minutes => _uptimeRestart = TimeSpan.FromMinutes(minutes),
true);
}
public void Update()
{
if (_restartTime != null)
{
if (_restartTime < _gameTiming.RealTime)
{
DoShutdown();
}
}
else
{
if (ShouldShutdownDueToUptime())
{
ServerEmptyUpdateRestartCheck("uptime");
}
}
}
///
/// Notify that the round just ended, which is a great time to restart if necessary!
///
/// True if the server is going to restart.
public bool RoundEnded()
{
if (_updateOnRoundEnd || ShouldShutdownDueToUptime())
{
DoShutdown();
return true;
}
return false;
}
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
switch (e.NewStatus)
{
case SessionStatus.Connected:
if (_restartTime != null)
_sawmill.Debug("Aborting server restart timer due to player connection");
_restartTime = null;
break;
case SessionStatus.Disconnected:
ServerEmptyUpdateRestartCheck("last player disconnect");
break;
}
}
private async void WatchdogOnUpdateReceived()
{
_chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received"));
_updateOnRoundEnd = true;
//WL-Changes-start
await SendDiscordNotify();
//WL-Changes-end
ServerEmptyUpdateRestartCheck("update notification");
}
//WL-Changes-start
private async Task SendDiscordNotify()
{
try
{
if (_discordWebhook == null)
return;
var payload = new WebhookPayload()
{
Content = "Сервер получил обновление и будет перезапущен в конце текущего раунда."
};
await _discord.CreateMessage(_discordWebhook.Value, payload);
}
catch (Exception exc)
{
_sawmill.Error($"Вызвано исключение во время отправки дискорд-оповещения об обновлении сервера: {exc.ToStringBetter()}");
}
}
//WL-Changes-end
///
/// Checks whether there are still players on the server,
/// and if not starts a timer to automatically reboot the server if an update is available.
///
private void ServerEmptyUpdateRestartCheck(string reason)
{
// Can't simple check the current connected player count since that doesn't update
// before PlayerStatusChanged gets fired.
// So in the disconnect handler we'd still see a single player otherwise.
var playersOnline = _playerManager.Sessions.Any(p => p.Status != SessionStatus.Disconnected);
if (playersOnline || !(_updateOnRoundEnd || ShouldShutdownDueToUptime()))
{
// Still somebody online.
return;
}
if (_restartTime != null)
{
// Do nothing because we already have a timer running.
return;
}
var restartDelay = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.UpdateRestartDelay));
_restartTime = restartDelay + _gameTiming.RealTime;
_sawmill.Debug("Started server-empty restart timer due to {Reason}", reason);
}
private void DoShutdown()
{
_sawmill.Debug($"Shutting down via {nameof(ServerUpdateManager)}!");
var reason = _updateOnRoundEnd ? "server-updates-shutdown" : "server-updates-shutdown-uptime";
_server.Shutdown(Loc.GetString(reason));
}
private bool ShouldShutdownDueToUptime()
{
return _uptimeRestart != TimeSpan.Zero && _gameTiming.RealTime > _uptimeRestart;
}
void IPostInjectInit.PostInject()
{
_sawmill = _logManager.GetSawmill("restart");
}
}