Add server scripts to execute on game start/stop, refactor how play sessions are recorded
This commit is contained in:
parent
369f856a5c
commit
d38432f675
8 changed files with 269 additions and 35 deletions
|
|
@ -40,7 +40,7 @@ namespace LANCommander.Launcher.Services
|
|||
await AddAsync(session);
|
||||
|
||||
if (Client.IsConnected())
|
||||
await Client.Games.StartPlaySessionAsync(gameId);
|
||||
await Client.Games.StartedAsync(gameId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -67,7 +67,7 @@ namespace LANCommander.Launcher.Services
|
|||
}
|
||||
finally
|
||||
{
|
||||
await Client.Games.EndPlaySessionAsync(gameId);
|
||||
await Client.Games.StoppedAsync(gameId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ namespace LANCommander.SDK.Enums
|
|||
BeforeStart,
|
||||
[Display(Name = "After Stop")]
|
||||
AfterStop,
|
||||
[Display(Name = "Game Started")]
|
||||
GameStarted,
|
||||
[Display(Name = "Game Ended")]
|
||||
GameStopped,
|
||||
[Display(Name = "User Registration")]
|
||||
UserRegistration,
|
||||
[Display(Name = "User Login")]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,57 @@ namespace LANCommander.SDK.Helpers
|
|||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
internal static void RetryOnException(int maxAttempts, TimeSpan delay, Action action)
|
||||
{
|
||||
int attempts = 0;
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||
|
||||
attempts++;
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, $"Attempt failed!");
|
||||
|
||||
if (attempts >= maxAttempts)
|
||||
return;
|
||||
|
||||
Task.Delay(delay).Wait();
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
internal static async Task RetryOnExceptionAsync(int maxAttempts, TimeSpan delay, Func<Task> action)
|
||||
{
|
||||
int attempts = 0;
|
||||
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||
|
||||
attempts++;
|
||||
|
||||
await action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, $"Attempt failed!");
|
||||
|
||||
if (attempts >= maxAttempts)
|
||||
return;
|
||||
|
||||
Task.Delay(delay).Wait();
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
internal static async Task<T> RetryOnExceptionAsync<T>(int maxAttempts, TimeSpan delay, T @default, Func<Task<T>> action)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -202,24 +202,37 @@ namespace LANCommander.SDK.Services
|
|||
return Client.StreamRequest($"/api/Games/{id}/Download");
|
||||
}
|
||||
|
||||
public async Task StartPlaySessionAsync(Guid id)
|
||||
public async Task StartedAsync(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Starting a game session...");
|
||||
|
||||
await Client.PostRequestAsync<object>($"/api/PlaySessions/Start/{id}");
|
||||
}
|
||||
|
||||
public async Task EndPlaySessionAsync(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Ending a game session...");
|
||||
Logger?.LogTrace("Signaling to the server that we started the game...");
|
||||
|
||||
try
|
||||
{
|
||||
await Client.PostRequestAsync<object>($"/api/PlaySessions/End/{id}");
|
||||
await RetryHelper.RetryOnExceptionAsync(10, TimeSpan.FromMilliseconds(500), async () =>
|
||||
{
|
||||
await Client.PostRequestAsync<object>($"/api/Game/{id}/Started");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Failed sending end session request to server");
|
||||
Logger?.LogError(ex, "Failed sending start request to server");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StoppedAsync(Guid id)
|
||||
{
|
||||
Logger?.LogTrace("Signaling to the server that we stopped the game...");
|
||||
|
||||
try
|
||||
{
|
||||
await RetryHelper.RetryOnExceptionAsync(10, TimeSpan.FromMilliseconds(500), async () =>
|
||||
{
|
||||
await Client.PostRequestAsync<object>($"/api/Game/{id}/Stopped");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Failed sending stop request to server");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,13 +51,6 @@ namespace LANCommander.Server.Services
|
|||
};
|
||||
|
||||
await AddAsync(session);
|
||||
|
||||
var servers = await serverService.GetAsync(s => s.GameId == gameId && s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnPlayerActivity);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
serverProcessService.StartServerAsync(server.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task EndSessionAsync(Guid gameId, Guid userId)
|
||||
|
|
@ -70,18 +63,6 @@ namespace LANCommander.Server.Services
|
|||
|
||||
await UpdateAsync(existingSession);
|
||||
}
|
||||
|
||||
var activeSessions = (await GetAsync(ps => ps.GameId == gameId && ps.End == null)).Any();
|
||||
|
||||
if (!activeSessions)
|
||||
{
|
||||
var servers = await serverService.GetAsync(s => s.GameId == gameId && s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnPlayerActivity);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
serverProcessService.StopServerAsync(server.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using LANCommander.Server.Data;
|
||||
using AutoMapper;
|
||||
using LANCommander.SDK;
|
||||
using LANCommander.SDK.Enums;
|
||||
using LANCommander.SDK.PowerShell;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LANCommander.Server.Services.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -13,7 +16,8 @@ namespace LANCommander.Server.Services
|
|||
IFusionCache cache,
|
||||
IMapper mapper,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IDbContextFactory<DatabaseContext> contextFactory) : BaseDatabaseService<Data.Models.Server>(logger, cache, mapper, httpContextAccessor, contextFactory)
|
||||
IDbContextFactory<DatabaseContext> contextFactory,
|
||||
UserService userService) : BaseDatabaseService<Data.Models.Server>(logger, cache, mapper, httpContextAccessor, contextFactory)
|
||||
{
|
||||
public override async Task<Data.Models.Server> AddAsync(Data.Models.Server entity)
|
||||
{
|
||||
|
|
@ -44,5 +48,71 @@ namespace LANCommander.Server.Services
|
|||
await context.UpdateRelationshipAsync(s => s.ServerConsoles);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task RunGameStartedScriptsAsync(Guid serverId, Guid userId)
|
||||
{
|
||||
var user = await userService.GetAsync(userId);
|
||||
var server = await
|
||||
Include(s => s.Game)
|
||||
.Include(s => s.Scripts)
|
||||
.FirstOrDefaultAsync(s => s.Id == serverId);
|
||||
|
||||
foreach (var script in server.Scripts.Where(s => s.Type == ScriptType.GameStarted))
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptContext = new PowerShellScript(ScriptType.GameStarted);
|
||||
|
||||
scriptContext.AddVariable("Server", mapper.Map<SDK.Models.Server>(server));
|
||||
scriptContext.AddVariable("Game", mapper.Map<SDK.Models.Game>(server.Game));
|
||||
scriptContext.AddVariable("User", mapper.Map<SDK.Models.User>(user));
|
||||
|
||||
scriptContext.UseWorkingDirectory(server.WorkingDirectory);
|
||||
scriptContext.UseInline(script.Contents);
|
||||
scriptContext.UseShellExecute();
|
||||
|
||||
_logger?.LogInformation("Executing script \"{ScriptName}\"", script.Name);
|
||||
|
||||
await scriptContext.ExecuteAsync<int>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", script.Name, server.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunGameStoppedScriptsAsync(Guid serverId, Guid userId)
|
||||
{
|
||||
var user = await userService.GetAsync(userId);
|
||||
var server = await
|
||||
Include(s => s.Game)
|
||||
.Include(s => s.Scripts)
|
||||
.FirstOrDefaultAsync(s => s.Id == serverId);
|
||||
|
||||
foreach (var script in server.Scripts.Where(s => s.Type == ScriptType.GameStopped))
|
||||
{
|
||||
try
|
||||
{
|
||||
var scriptContext = new PowerShellScript(ScriptType.GameStopped);
|
||||
|
||||
scriptContext.AddVariable("Server", mapper.Map<SDK.Models.Server>(server));
|
||||
scriptContext.AddVariable("Game", mapper.Map<SDK.Models.Game>(server.Game));
|
||||
scriptContext.AddVariable("User", mapper.Map<SDK.Models.User>(user));
|
||||
|
||||
scriptContext.UseWorkingDirectory(server.WorkingDirectory);
|
||||
scriptContext.UseInline(script.Contents);
|
||||
scriptContext.UseShellExecute();
|
||||
|
||||
_logger?.LogInformation("Executing script \"{ScriptName}\"", script.Name);
|
||||
|
||||
await scriptContext.ExecuteAsync<int>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogError(ex, "Error running script \"{ScriptName}\" for server \"{ServerName}\"", script.Name, server.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ namespace LANCommander.Server.Controllers.Api
|
|||
private readonly StorageLocationService StorageLocationService;
|
||||
private readonly ArchiveService ArchiveService;
|
||||
private readonly UserService UserService;
|
||||
private readonly PlaySessionService PlaySessionService;
|
||||
private readonly ServerService ServerService;
|
||||
private readonly ServerProcessService ServerProcessService;
|
||||
private readonly IFusionCache Cache;
|
||||
private readonly IMapper Mapper;
|
||||
|
||||
|
|
@ -36,7 +39,10 @@ namespace LANCommander.Server.Controllers.Api
|
|||
LibraryService libraryService,
|
||||
StorageLocationService storageLocationService,
|
||||
ArchiveService archiveService,
|
||||
UserService userService) : base(logger)
|
||||
UserService userService,
|
||||
PlaySessionService playSessionService,
|
||||
ServerService serverService,
|
||||
ServerProcessService serverProcessService) : base(logger)
|
||||
{
|
||||
GameService = gameService;
|
||||
ImportService = importService;
|
||||
|
|
@ -44,6 +50,9 @@ namespace LANCommander.Server.Controllers.Api
|
|||
StorageLocationService = storageLocationService;
|
||||
ArchiveService = archiveService;
|
||||
UserService = userService;
|
||||
PlaySessionService = playSessionService;
|
||||
ServerService = serverService;
|
||||
ServerProcessService = serverProcessService;
|
||||
Cache = cache;
|
||||
Mapper = mapper;
|
||||
}
|
||||
|
|
@ -177,6 +186,112 @@ namespace LANCommander.Server.Controllers.Api
|
|||
return addons;
|
||||
}
|
||||
|
||||
[HttpGet("{id}/Started")]
|
||||
public async Task<IActionResult> StartedAsync(Guid id)
|
||||
{
|
||||
var user = await UserService.GetAsync(User?.Identity?.Name);
|
||||
var game = await GameService.GetAsync(id);
|
||||
|
||||
if (game == null || user == null)
|
||||
return BadRequest();
|
||||
|
||||
#region Start recording play session
|
||||
var activeSessions = await PlaySessionService
|
||||
.Include(ps => ps.Game)
|
||||
.GetAsync(ps => ps.UserId == user.Id && ps.End == null);
|
||||
|
||||
foreach (var activeSession in activeSessions)
|
||||
await PlaySessionService.EndSessionAsync(game.Id, activeSession.UserId);
|
||||
|
||||
await PlaySessionService.StartSessionAsync(game.Id, user.Id);
|
||||
#endregion
|
||||
|
||||
#region Autostart Servers
|
||||
try
|
||||
{
|
||||
var servers = await ServerService.GetAsync(s =>
|
||||
s.GameId == game.Id && s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnPlayerActivity);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
ServerProcessService.StartServerAsync(server.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Servers could not be autostarted");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Run server scripts
|
||||
|
||||
try
|
||||
{
|
||||
var servers = await ServerService
|
||||
.GetAsync(s => s.GameId == game.Id);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
await ServerService.RunGameStartedScriptsAsync(server.Id, user.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Server scripts could not run");
|
||||
}
|
||||
#endregion
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("{id}/Stopped")]
|
||||
public async Task<IActionResult> StoppedAsync(Guid id)
|
||||
{
|
||||
var user = await UserService.GetAsync(User?.Identity?.Name);
|
||||
var game = await GameService.GetAsync(id);
|
||||
|
||||
if (game == null || user == null)
|
||||
return BadRequest();
|
||||
|
||||
await PlaySessionService.EndSessionAsync(game.Id, user.Id);
|
||||
|
||||
#region Autostart Servers
|
||||
try
|
||||
{
|
||||
var servers = await ServerService.GetAsync(s =>
|
||||
s.GameId == game.Id && s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnPlayerActivity);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
ServerProcessService.StartServerAsync(server.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Servers could not be autostarted");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Run server scripts
|
||||
try
|
||||
{
|
||||
var servers = await ServerService
|
||||
.GetAsync(s => s.GameId == game.Id);
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
await ServerService.RunGameStoppedScriptsAsync(server.Id, user.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError(ex, "Server scripts could not run");
|
||||
}
|
||||
#endregion
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("{id}/CheckForUpdate")]
|
||||
public async Task<bool> CheckForUpdateAsync(Guid id, string version)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</TitleExtraTemplate>
|
||||
|
||||
<ChildContent>
|
||||
<ScriptEditor ServerId="@context.Id" ArchiveId="@context.Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Id" AllowedTypes="new[] { ScriptType.BeforeStart, ScriptType.AfterStop }" />
|
||||
<ScriptEditor ServerId="@context.Id" ArchiveId="@context.Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Id" AllowedTypes="new[] { ScriptType.BeforeStart, ScriptType.AfterStop, ScriptType.GameStarted, ScriptType.GameStopped }" />
|
||||
</ChildContent>
|
||||
</ServerEditView>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue