From 320d4e2c5e2f26d715709c37faa5aea52ce00da6 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Tue, 1 Oct 2024 17:56:58 -0500 Subject: [PATCH] Allow uploading of archive files via CLI Implements #111 --- .../CommandLineOptions.cs | 25 ++++++++-- .../CommandLineService.cs | 40 ++++++++++++++- LANCommander.SDK/GameService.cs | 17 +++++++ .../Models/UploadArchiveRequest.cs | 12 +++++ LANCommander.SDK/RedistributableService.cs | 22 +++++++++ .../Controllers/Api/GamesController.cs | 46 +++++++++++++++++ .../Api/RedistributablesController.cs | 49 ++++++++++++++++++- 7 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 LANCommander.SDK/Models/UploadArchiveRequest.cs diff --git a/LANCommander.Launcher.Models/CommandLineOptions.cs b/LANCommander.Launcher.Models/CommandLineOptions.cs index 76491872..04b4336d 100644 --- a/LANCommander.Launcher.Models/CommandLineOptions.cs +++ b/LANCommander.Launcher.Models/CommandLineOptions.cs @@ -86,8 +86,27 @@ namespace LANCommander.Launcher.Models [Option("Id", HelpText = "The ID of the entity to export", Required = true)] public Guid Id { get; set; } - [Option("Type", HelpText = "The type of archive to export", Required = true)] - public ImportArchiveType Type { get; set; } + [Option("Type", HelpText = "The type of export file", Required = true)] + public ArchiveType Type { get; set; } + } + + [Verb("Upload", HelpText = "Upload an archive to the server (Admin Only)")] + public class UploadCommandLineOptions + { + [Option("Path", HelpText = "Path to the archive file")] + public string Path { get; set; } + + [Option("Id", HelpText = "The ID of the entity associated with the archive")] + public Guid Id { get; set; } + + [Option("Version", HelpText = "The version of the archive")] + public string Version { get; set; } + + [Option("Changelog", HelpText = "Any changes introduces with the archive")] + public string Changelog { get; set; } + + [Option("Type", HelpText = "The type of entity associated with the archive")] + public ArchiveType Type { get; set; } } [Verb("Login", HelpText = "Login to a LANCommander server")] @@ -112,4 +131,4 @@ namespace LANCommander.Launcher.Models [Option("Alias", Required = true)] public string Alias { get; set; } } -} +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/CommandLineService.cs b/LANCommander.Launcher.Services/CommandLineService.cs index e8e0a48d..9e6b8a26 100644 --- a/LANCommander.Launcher.Services/CommandLineService.cs +++ b/LANCommander.Launcher.Services/CommandLineService.cs @@ -46,6 +46,7 @@ namespace LANCommander.Launcher.Services SyncCommandLineOptions, ImportCommandLineOptions, ExportCommandLineOptions, + UploadCommandLineOptions, LoginCommandLineOptions, LogoutCommandLineOptions, ChangeAliasCommandLineOptions @@ -58,6 +59,7 @@ namespace LANCommander.Launcher.Services await result.WithParsedAsync(Sync); await result.WithParsedAsync(Import); await result.WithParsedAsync(Export); + await result.WithParsedAsync(Upload); await result.WithParsedAsync(Login); await result.WithParsedAsync(Logout); await result.WithParsedAsync(ChangeAlias); @@ -212,16 +214,52 @@ namespace LANCommander.Launcher.Services switch (options.Type) { - case ImportArchiveType.Game: + case ArchiveType.Game: Logger.LogInformation("Exporting game from server..."); await Client.Games.ExportAsync(options.Path, options.Id); break; + + case ArchiveType.Redistributable: + Logger.LogInformation("Exporting redistributable from server..."); + + await Client.Redistributables.ExportAsync(options.Path, options.Id); + break; + + case ArchiveType.Server: + Logger.LogInformation("Exporting server from server..."); + + await Client.Servers.ExportAsync(options.Path, options.Id); + break; } Logger.LogInformation($"Successfully exported game to {options.Path}"); } + private async Task Upload(UploadCommandLineOptions options) + { + if (!File.Exists(options.Path)) + { + Logger.LogInformation("File not found! Check your path!"); + return; + } + + switch (options.Type) + { + case ArchiveType.Game: + Logger.LogInformation("Uploading game archive to server..."); + + await Client.Games.UploadArchiveAsync(options.Path, options.Id, options.Version, options.Changelog); + break; + + case ArchiveType.Redistributable: + Logger.LogInformation("Uploading redistributable archive to server..."); + + await Client.Redistributables.UploadArchiveAsync(options.Path, options.Id, options.Version, options.Changelog); + break; + } + } + private async Task Login(LoginCommandLineOptions options) { try diff --git a/LANCommander.SDK/GameService.cs b/LANCommander.SDK/GameService.cs index 79d6b73d..481135a1 100644 --- a/LANCommander.SDK/GameService.cs +++ b/LANCommander.SDK/GameService.cs @@ -881,6 +881,23 @@ namespace LANCommander.SDK await Client.DownloadRequestAsync($"/Games/{gameId}/Export/Full", destinationPath); } + public async Task UploadArchiveAsync(string archivePath, Guid gameId, 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/Games/UploadArchive", new UploadArchiveRequest + { + Id = gameId, + ObjectKey = objectKey, + Version = version, + Changelog = changelog, + }); + } + } + public static string GetMetadataDirectoryPath(string installDirectory, Guid gameId) { if (String.IsNullOrWhiteSpace(installDirectory)) diff --git a/LANCommander.SDK/Models/UploadArchiveRequest.cs b/LANCommander.SDK/Models/UploadArchiveRequest.cs new file mode 100644 index 00000000..064b831f --- /dev/null +++ b/LANCommander.SDK/Models/UploadArchiveRequest.cs @@ -0,0 +1,12 @@ +using System; + +namespace LANCommander.SDK.Models +{ + public class UploadArchiveRequest + { + public Guid Id { get; set; } + public Guid ObjectKey { get; set; } + public string Version { get; set; } + public string Changelog { get; set; } + } +} diff --git a/LANCommander.SDK/RedistributableService.cs b/LANCommander.SDK/RedistributableService.cs index d6dbe026..a923782f 100644 --- a/LANCommander.SDK/RedistributableService.cs +++ b/LANCommander.SDK/RedistributableService.cs @@ -182,5 +182,27 @@ namespace LANCommander.SDK await Client.PostRequestAsync($"/api/Redistributables/Import/{objectKey}"); } } + + public async Task ExportAsync(string destinationPath, Guid redistributableId) + { + await Client.DownloadRequestAsync($"/Redistributables/{redistributableId}/Export/Full", destinationPath); + } + + public async Task UploadArchiveAsync(string archivePath, Guid redistributableId, 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/Redistributables/UploadArchive", new UploadArchiveRequest + { + Id = redistributableId, + ObjectKey = objectKey, + Version = version, + Changelog = changelog, + }); + } + } } } diff --git a/LANCommander.Server/Controllers/Api/GamesController.cs b/LANCommander.Server/Controllers/Api/GamesController.cs index 75763bcf..5c80545b 100644 --- a/LANCommander.Server/Controllers/Api/GamesController.cs +++ b/LANCommander.Server/Controllers/Api/GamesController.cs @@ -19,6 +19,7 @@ namespace LANCommander.Server.Controllers.Api { private readonly IMapper Mapper; private readonly GameService GameService; + private readonly ArchiveService ArchiveService; private readonly UserManager UserManager; private readonly RoleManager RoleManager; private readonly IFusionCache Cache; @@ -28,11 +29,13 @@ namespace LANCommander.Server.Controllers.Api IMapper mapper, IFusionCache cache, GameService gameService, + ArchiveService archiveService, UserManager userManager, RoleManager roleManager) : base(logger) { Mapper = mapper; GameService = gameService; + ArchiveService = archiveService; UserManager = userManager; RoleManager = roleManager; Cache = cache; @@ -145,5 +148,48 @@ namespace LANCommander.Server.Controllers.Api return BadRequest(ex.Message); } } + + [Authorize(Roles = "Administrator")] + [HttpPost("UploadArchive")] + public async Task UploadArchive(SDK.Models.UploadArchiveRequest request) + { + try + { + var archive = await ArchiveService.Get(a => a.GameId == request.Id && a.Version == request.Version).FirstOrDefaultAsync(); + var archivePath = ArchiveService.GetArchiveFileLocation(archive.ObjectKey); + + if (archive != null) + { + var existingArchivePath = ArchiveService.GetArchiveFileLocation(archive.ObjectKey); + + System.IO.File.Delete(existingArchivePath); + + archive.ObjectKey = request.ObjectKey.ToString(); + archive.Changelog = request.Changelog; + archive.CompressedSize = new System.IO.FileInfo(archivePath).Length; + + archive = await ArchiveService.Update(archive); + } + else + { + archive = new Archive() + { + ObjectKey = request.ObjectKey.ToString(), + Changelog = request.Changelog, + GameId = request.Id, + CompressedSize = new System.IO.FileInfo(archivePath).Length, + }; + + await ArchiveService.Add(archive); + } + + return Ok(); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not upload game archive"); + return BadRequest(ex.Message); + } + } } } diff --git a/LANCommander.Server/Controllers/Api/RedistributablesController.cs b/LANCommander.Server/Controllers/Api/RedistributablesController.cs index 9f5b0e6e..44196c1f 100644 --- a/LANCommander.Server/Controllers/Api/RedistributablesController.cs +++ b/LANCommander.Server/Controllers/Api/RedistributablesController.cs @@ -5,6 +5,7 @@ using LANCommander.Server.Models; using LANCommander.Server.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace LANCommander.Server.Controllers.Api { @@ -15,14 +16,17 @@ namespace LANCommander.Server.Controllers.Api { private readonly IMapper Mapper; private readonly RedistributableService RedistributableService; + private readonly ArchiveService ArchiveService; public RedistributablesController( ILogger logger, IMapper mapper, - RedistributableService redistributableService) : base(logger) + RedistributableService redistributableService, + ArchiveService archiveService) : base(logger) { Mapper = mapper; RedistributableService = redistributableService; + ArchiveService = archiveService; } [HttpGet] @@ -79,5 +83,48 @@ namespace LANCommander.Server.Controllers.Api return BadRequest(ex.Message); } } + + [Authorize(Roles = "Administrator")] + [HttpPost("UploadArchive")] + public async Task UploadArchive(SDK.Models.UploadArchiveRequest request) + { + try + { + var archive = await ArchiveService.Get(a => a.RedistributableId == request.Id && a.Version == request.Version).FirstOrDefaultAsync(); + var archivePath = ArchiveService.GetArchiveFileLocation(archive.ObjectKey); + + if (archive != null) + { + var existingArchivePath = ArchiveService.GetArchiveFileLocation(archive.ObjectKey); + + System.IO.File.Delete(existingArchivePath); + + archive.ObjectKey = request.ObjectKey.ToString(); + archive.Changelog = request.Changelog; + archive.CompressedSize = new System.IO.FileInfo(archivePath).Length; + + archive = await ArchiveService.Update(archive); + } + else + { + archive = new Archive() + { + ObjectKey = request.ObjectKey.ToString(), + Changelog = request.Changelog, + RedistributableId = request.Id, + CompressedSize = new System.IO.FileInfo(archivePath).Length, + }; + + await ArchiveService.Add(archive); + } + + return Ok(); + } + catch (Exception ex) + { + Logger?.LogError(ex, "Could not upload redistributable archive"); + return BadRequest(ex.Message); + } + } } }