From 3d9749a9dbbb32bb0ea70f72513c245519e2ae9f Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Tue, 1 Oct 2024 17:56:06 -0500 Subject: [PATCH] Add importing/exporting of redistributable and server to CLI --- .../CommandLineOptions.cs | 13 ++-- .../CommandLineService.cs | 14 +++- LANCommander.SDK/Client.cs | 3 + LANCommander.SDK/RedistributableService.cs | 11 +++ LANCommander.SDK/ServerService.cs | 73 +++++++++++++++++++ .../Api/RedistributablesController.cs | 17 +++++ .../Controllers/Api/ServersController.cs | 63 ++++++++++++++++ 7 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 LANCommander.SDK/ServerService.cs create mode 100644 LANCommander.Server/Controllers/Api/ServersController.cs diff --git a/LANCommander.Launcher.Models/CommandLineOptions.cs b/LANCommander.Launcher.Models/CommandLineOptions.cs index a9cf52a1..76491872 100644 --- a/LANCommander.Launcher.Models/CommandLineOptions.cs +++ b/LANCommander.Launcher.Models/CommandLineOptions.cs @@ -8,7 +8,8 @@ using System.Threading.Tasks; namespace LANCommander.Launcher.Models { - public enum ImportArchiveType { + public enum ArchiveType + { Game, Redistributable, Server @@ -66,17 +67,17 @@ namespace LANCommander.Launcher.Models [Verb("Sync", HelpText = "Sync library items from the server")] public class SyncCommandLineOptions { } - [Verb("Import", HelpText = "Upload and import an archive to the server (Admin Only)")] + [Verb("Import", HelpText = "Upload and import a game/redistributable/server to the server (Admin Only)")] public class ImportCommandLineOptions { - [Option("Path", HelpText = "Path to the archive file", Required = true)] + [Option("Path", HelpText = "Path to the import file", Required = true)] public string Path { get; set; } - [Option("Type", HelpText = "The type of archive to import", Required = true)] - public ImportArchiveType Type { get; set; } + [Option("Type", HelpText = "The type of import file", Required = true)] + public ArchiveType Type { get; set; } } - [Verb("Export", HelpText = "Export an archive from the server (Admin Only)")] + [Verb("Export", HelpText = "Export a game/redistributable/server from the server (Admin Only)")] public class ExportCommandLineOptions { [Option("Path", HelpText = "The destination path for the LCX export file", Required = true)] diff --git a/LANCommander.Launcher.Services/CommandLineService.cs b/LANCommander.Launcher.Services/CommandLineService.cs index 4dcd2107..e8e0a48d 100644 --- a/LANCommander.Launcher.Services/CommandLineService.cs +++ b/LANCommander.Launcher.Services/CommandLineService.cs @@ -180,11 +180,23 @@ namespace LANCommander.Launcher.Services switch (options.Type) { - case ImportArchiveType.Game: + case ArchiveType.Game: Logger.LogInformation("Uploading game import file to server..."); await Client.Games.ImportAsync(options.Path); break; + + case ArchiveType.Redistributable: + Logger.LogInformation("Uploading redistributable archive file to server..."); + + await Client.Redistributables.ImportAsync(options.Path); + break; + + case ArchiveType.Server: + Logger.LogInformation("Uploading server archive file to server..."); + + await Client.Servers.ImportAsync(options.Path); + break; } Logger.LogInformation("Import complete!"); diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 90cc0944..37861785 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -41,6 +41,7 @@ namespace LANCommander.SDK public readonly LauncherService Launcher; public readonly IssueService Issues; public readonly LobbyService Lobbies; + public readonly ServerService Servers; private Settings _Settings { get; set; } public Settings Settings @@ -67,6 +68,7 @@ namespace LANCommander.SDK Launcher = new LauncherService(this); Issues = new IssueService(this); Lobbies = new LobbyService(this); + Servers = new ServerService(this); BaseCmdlet.Client = this; @@ -88,6 +90,7 @@ namespace LANCommander.SDK Launcher = new LauncherService(this); Issues = new IssueService(this); Lobbies = new LobbyService(this, logger); + Servers = new ServerService(this, logger); BaseCmdlet.Client = this; diff --git a/LANCommander.SDK/RedistributableService.cs b/LANCommander.SDK/RedistributableService.cs index fd89b23a..d6dbe026 100644 --- a/LANCommander.SDK/RedistributableService.cs +++ b/LANCommander.SDK/RedistributableService.cs @@ -171,5 +171,16 @@ namespace LANCommander.SDK return extractionResult; } + + public async Task ImportAsync(string archivePath) + { + using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read)) + { + var objectKey = await Client.ChunkedUploadRequestAsync("", fs); + + if (objectKey != Guid.Empty) + await Client.PostRequestAsync($"/api/Redistributables/Import/{objectKey}"); + } + } } } diff --git a/LANCommander.SDK/ServerService.cs b/LANCommander.SDK/ServerService.cs new file mode 100644 index 00000000..ccf0a833 --- /dev/null +++ b/LANCommander.SDK/ServerService.cs @@ -0,0 +1,73 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.Extensions; +using LANCommander.SDK.Helpers; +using LANCommander.SDK.Models; +using LANCommander.SDK.PowerShell; +using Microsoft.Extensions.Logging; +using SharpCompress.Common; +using SharpCompress.Readers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LANCommander.SDK +{ + public class ServerService + { + private readonly ILogger Logger; + private Client Client { get; set; } + + public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e); + public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress; + + public delegate void OnArchiveExtractionProgressHandler(long position, long length); + public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress; + + public ServerService(Client client) + { + Client = client; + } + + public ServerService(Client client, ILogger logger) + { + Client = client; + Logger = logger; + } + + public async Task ImportAsync(string archivePath) + { + using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read)) + { + var objectKey = await Client.ChunkedUploadRequestAsync("", fs); + + if (objectKey != Guid.Empty) + await Client.PostRequestAsync($"/api/Servers/Import/{objectKey}"); + } + } + + public async Task ExportAsync(string destinationPath, Guid serverId) + { + await Client.DownloadRequestAsync($"/Servers/{serverId}/Export/Full", destinationPath); + } + + public async Task UploadArchiveAsync(string archivePath, Guid serverId, string version, string changelog = "") + { + using (var fs = new FileStream(archivePath, FileMode.Open, FileAccess.Read)) + { + var objectKey = await Client.ChunkedUploadRequestAsync("", fs); + + if (objectKey != Guid.Empty) + await Client.PostRequestAsync($"/api/Servers/UploadArchive", new UploadArchiveRequest + { + Id = serverId, + ObjectKey = objectKey, + Version = version, + Changelog = changelog, + }); + } + } + } +} diff --git a/LANCommander.Server/Controllers/Api/RedistributablesController.cs b/LANCommander.Server/Controllers/Api/RedistributablesController.cs index ac33820e..9f5b0e6e 100644 --- a/LANCommander.Server/Controllers/Api/RedistributablesController.cs +++ b/LANCommander.Server/Controllers/Api/RedistributablesController.cs @@ -62,5 +62,22 @@ namespace LANCommander.Server.Controllers.Api return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{redistributable.Name.SanitizeFilename()}.zip"); } + + [Authorize(Roles = "Administrator")] + [HttpPost("Import/{objectKey}")] + public async Task Import(Guid objectKey) + { + try + { + var game = await RedistributableService.Import(objectKey); + + return Ok(); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not import redistributable from upload"); + return BadRequest(ex.Message); + } + } } } diff --git a/LANCommander.Server/Controllers/Api/ServersController.cs b/LANCommander.Server/Controllers/Api/ServersController.cs new file mode 100644 index 00000000..e9119fc4 --- /dev/null +++ b/LANCommander.Server/Controllers/Api/ServersController.cs @@ -0,0 +1,63 @@ +using AutoMapper; +using LANCommander.Server.Data.Models; +using LANCommander.Server.Extensions; +using LANCommander.Server.Models; +using LANCommander.Server.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LANCommander.Server.Controllers.Api +{ + [Authorize(AuthenticationSchemes = "Bearer", Roles = "Administrator")] + [Route("api/[controller]")] + [ApiController] + public class ServersController : BaseApiController + { + private readonly IMapper Mapper; + private readonly ServerService ServerService; + + public ServersController( + ILogger logger, + IMapper mapper, + ServerService serverService, + ArchiveService archiveService) : base(logger) + { + Mapper = mapper; + ServerService = serverService; + } + + [HttpGet] + public async Task>> Get() + { + return Ok(Mapper.Map>(await ServerService.Get())); + } + + [HttpGet("{id}")] + public async Task> Get(Guid id) + { + var server = await ServerService.Get(id); + + if (server == null) + return NotFound(); + + return Ok(Mapper.Map(server)); + } + + [HttpPost("Import/{objectKey}")] + public async Task Import(Guid objectKey) + { + try + { + var game = await ServerService.Import(objectKey); + + return Ok(); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not import server from upload"); + return BadRequest(ex.Message); + } + } + } +}