Files
ss14-wl/Content.Server/ServerUpdates/ServerUpdateManager.cs
2024-11-06 16:46:02 +05:00

196 lines
6.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// Responsible for restarting the server periodically or for update, when not disruptive.
/// </summary>
/// <remarks>
/// This was originally only designed for restarting on *update*,
/// but now also handles periodic restarting to keep server uptime via <see cref="CCVars.ServerUptimeRestartMinutes"/>.
/// </remarks>
public sealed class ServerUpdateManager : IPostInjectInit
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IWatchdogApi _watchdog = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IBaseServer _server = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
//WL-Chages-start
[Dependency] private readonly 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");
}
}
}
/// <summary>
/// Notify that the round just ended, which is a great time to restart if necessary!
/// </summary>
/// <returns>True if the server is going to restart.</returns>
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
/// <summary>
/// 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.
/// </summary>
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");
}
}