LANCommander/LANCommander.Server.Services/ServerProcessService.cs

282 lines
10 KiB
C#
Raw Normal View History

using AutoMapper;
using CoreRCON;
2024-08-04 18:44:33 -05:00
using LANCommander.Server.Data.Models;
2024-07-04 16:34:30 -05:00
using LANCommander.SDK.Enums;
using LANCommander.SDK.PowerShell;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
using System.Net;
2024-08-16 18:36:17 -05:00
using LANCommander.SDK;
using LANCommander.Server.Services.Enums;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Services.Utilities;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
2024-08-04 18:44:33 -05:00
namespace LANCommander.Server.Services
{
2023-04-20 00:24:46 -05:00
public class ServerProcessService : BaseService
{
public Dictionary<Guid, CancellationTokenSource> Running { get; set; } = new();
public Dictionary<Guid, LogFileMonitor> LogFileMonitors { get; set; } = new();
private Dictionary<Guid, RCON> RconConnections { get; set; } = new();
2025-03-16 14:01:33 -05:00
private Dictionary<Guid, ServerProcessStatus> Status { get; set; } = new();
2023-08-15 20:15:53 -05:00
public delegate void OnLogHandler(object sender, ServerLogEventArgs e);
public event OnLogHandler OnLog;
2025-03-16 14:01:33 -05:00
public event EventHandler<ServerStatusUpdateEventArgs> OnStatusUpdate;
private readonly IServiceProvider ServiceProvider;
2024-08-16 18:36:17 -05:00
private readonly SDK.Client Client;
private readonly IMapper Mapper;
2023-08-15 20:15:53 -05:00
public ServerProcessService(
ILogger<ServerProcessService> logger,
IServiceProvider serviceProvider,
SDK.Client client,
IMapper mapper) : base(logger)
2023-08-15 20:15:53 -05:00
{
ServiceProvider = serviceProvider;
2024-08-16 18:36:17 -05:00
Client = client;
Mapper = mapper;
2023-08-15 20:15:53 -05:00
}
public async Task StartServerAsync(Guid serverId)
{
2024-08-04 18:44:33 -05:00
Data.Models.Server server;
using (var scope = ServiceProvider.CreateScope())
{
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
2025-01-25 02:04:27 -06:00
server = await serverService
.Query(q =>
{
return q
.Include(s => s.Scripts)
.Include(s => s.Game)
.Include(s => s.ServerConsoles);
}).GetAsync(serverId);
// Don't start the server if it's already started
if (GetStatus(server) != ServerProcessStatus.Stopped)
return;
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Starting);
_logger?.LogInformation("Starting server \"{ServerName}\" for game {GameName}", server.Name, server.Game?.Title);
2024-08-12 00:15:16 -05:00
foreach (var serverScript in server.Scripts.Where(s => s.Type == ScriptType.BeforeStart))
{
2024-08-11 14:48:24 -05:00
try
{
2024-08-11 14:48:24 -05:00
var script = new PowerShellScript(SDK.Enums.ScriptType.BeforeStart);
script.AddVariable("Server", Mapper.Map<SDK.Models.Server>(server));
2024-08-11 14:48:24 -05:00
script.UseWorkingDirectory(server.WorkingDirectory);
script.UseInline(serverScript.Contents);
script.UseShellExecute();
_logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
2024-08-16 18:36:17 -05:00
if (Client.Scripts.Debug)
script.EnableDebug();
await script.ExecuteAsync<int>();
}
2024-08-11 14:48:24 -05:00
catch (Exception ex)
{
_logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", serverScript.Name, server.Name);
2024-08-11 14:48:24 -05:00
}
}
using (var executionContext = new ProcessExecutionContext(Client, _logger))
{
try
2023-12-27 20:13:25 -06:00
{
executionContext.AddVariable("ServerId", server.Id.ToString());
executionContext.AddVariable("ServerName", server.Name);
executionContext.AddVariable("ServerHost", server.Host);
executionContext.AddVariable("ServerPort", server.Port.ToString());
2023-08-31 21:00:47 -05:00
if (server.Game != null)
{
executionContext.AddVariable("GameTitle", server.Game?.Title);
executionContext.AddVariable("GameId", server.Game?.Id.ToString());
}
foreach (var logFile in server.ServerConsoles.Where(sc => sc.Type == ServerConsoleType.LogFile))
{
StartMonitoringLog(logFile, server);
}
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Running);
var cancellationTokenSource = new CancellationTokenSource();
Running[server.Id] = cancellationTokenSource;
await executionContext.ExecuteServerAsync(Mapper.Map<SDK.Models.Server>(server), cancellationTokenSource);
if (Running.ContainsKey(server.Id))
Running.Remove(server.Id);
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Stopped);
2023-12-27 20:13:25 -06:00
}
catch (Exception ex)
2023-12-27 20:13:25 -06:00
{
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Error, ex);
2023-08-31 21:00:47 -05:00
_logger?.LogError(ex, "Could not start server {ServerName} ({ServerId})", server.Name, server.Id);
}
2023-08-15 20:15:53 -05:00
2023-12-27 20:13:25 -06:00
}
2023-08-31 21:00:47 -05:00
}
}
public async void StopServerAsync(Guid serverId)
{
using (var scope = ServiceProvider.CreateScope())
{
var serverService = scope.ServiceProvider.GetRequiredService<ServerService>();
2025-01-25 02:04:27 -06:00
var server = await serverService
.Query(q =>
{
return q
.Include(s => s.Scripts)
.Include(s => s.Game)
.Include(s => s.ServerConsoles);
}).GetAsync(serverId);
2023-08-31 21:00:47 -05:00
_logger?.LogInformation("Stopping server \"{ServerName}\" for game {GameName}", server.Name, server.Game?.Title);
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Stopping);
if (Running.ContainsKey(server.Id))
{
await Running[server.Id].CancelAsync();
Running.Remove(server.Id);
}
if (LogFileMonitors.ContainsKey(server.Id))
{
LogFileMonitors[server.Id].Dispose();
LogFileMonitors.Remove(server.Id);
}
2024-08-12 00:15:16 -05:00
foreach (var serverScript in server.Scripts.Where(s => s.Type == ScriptType.AfterStop))
{
2024-08-11 14:48:24 -05:00
try
{
2024-08-11 14:48:24 -05:00
var script = new PowerShellScript(SDK.Enums.ScriptType.AfterStop);
script.AddVariable("Server", Mapper.Map<SDK.Models.Server>(server));
2024-08-11 14:48:24 -05:00
script.UseWorkingDirectory(server.WorkingDirectory);
script.UseInline(serverScript.Contents);
script.UseShellExecute();
_logger?.LogInformation("Executing script \"{ScriptName}\"", serverScript.Name);
2024-08-16 18:36:17 -05:00
if (Client.Scripts.Debug)
script.EnableDebug();
await script.ExecuteAsync<int>();
}
2024-08-11 14:48:24 -05:00
catch (Exception ex)
{
_logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", serverScript.Name, server.Name);
2024-08-11 14:48:24 -05:00
}
}
2025-03-16 14:01:33 -05:00
UpdateStatus(server, ServerProcessStatus.Stopped);
}
}
2023-04-15 16:44:26 -05:00
2024-08-04 18:44:33 -05:00
private void StartMonitoringLog(ServerConsole log, Data.Models.Server server)
2023-08-15 20:15:53 -05:00
{
if (!LogFileMonitors.ContainsKey(server.Id))
2023-08-15 20:15:53 -05:00
{
LogFileMonitors[server.Id] = new LogFileMonitor(server, log);
2023-08-15 20:15:53 -05:00
}
}
public RCON RconConnect(ServerConsole console)
{
if (!RconConnections.ContainsKey(console.Id))
{
var rcon = new RCON(new IPEndPoint(IPAddress.Parse(console.Host), console.Port.GetValueOrDefault()), console.Password);
RconConnections[console.Id] = rcon;
return rcon;
}
else
return RconConnections[console.Id];
}
public async Task<string> RconSendCommandAsync(string command, ServerConsole console)
{
if (RconConnections.ContainsKey(console.Id))
{
return await RconConnections[console.Id].SendCommandAsync(command);
}
else
return "";
}
2025-03-16 14:01:33 -05:00
private void UpdateStatus(Data.Models.Server server, ServerProcessStatus status, Exception ex = null)
{
if (ex != null)
{
Status[server.Id] = ServerProcessStatus.Error;
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, ServerProcessStatus.Error, ex));
}
else if (!Status.ContainsKey(server.Id))
{
Status[server.Id] = status;
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, status));
}
else if (Status[server.Id] != status)
{
Status[server.Id] = status;
OnStatusUpdate?.Invoke(this, new ServerStatusUpdateEventArgs(server, status));
}
}
2024-08-04 18:44:33 -05:00
public ServerProcessStatus GetStatus(Data.Models.Server server)
2023-04-15 16:44:26 -05:00
{
2023-08-30 19:45:42 -05:00
if (server == null)
return ServerProcessStatus.Stopped;
return GetStatus(server.Id);
}
public ServerProcessStatus GetStatus(Guid serverId)
{
if (Running.ContainsKey(serverId) && Running[serverId].IsCancellationRequested)
return ServerProcessStatus.Stopping;
if (Running.ContainsKey(serverId) && !Running[serverId].IsCancellationRequested)
2023-04-15 16:44:26 -05:00
return ServerProcessStatus.Running;
return ServerProcessStatus.Stopped;
}
}
}