From 4f07c6224781fdd9901ce0eefd5510f507a7a8c3 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Tue, 17 Jan 2023 17:57:12 -0600 Subject: [PATCH] Backend support for game saves. --- .../Controllers/Api/GameSavesController.cs | 96 +++++++++++++++++++ LANCommander/Controllers/HomeController.cs | 1 + .../Controllers/SettingsController.cs | 9 +- LANCommander/Data/DatabaseContext.cs | 12 +++ LANCommander/Data/Enums/ScriptType.cs | 4 +- LANCommander/Data/Models/Game.cs | 1 + LANCommander/Data/Models/GameSave.cs | 19 ++++ LANCommander/Data/Models/User.cs | 3 + .../DatabaseContextModelSnapshot.cs | 30 +++--- LANCommander/Models/DashboardViewModel.cs | 1 + LANCommander/Models/SaveUpload.cs | 8 ++ LANCommander/Models/UserViewModel.cs | 1 + LANCommander/Program.cs | 3 + LANCommander/Services/GameSaveService.cs | 49 ++++++++++ LANCommander/Views/Home/Index.cshtml | 8 +- LANCommander/Views/Settings/Users.cshtml | 4 + 16 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 LANCommander/Controllers/Api/GameSavesController.cs create mode 100644 LANCommander/Data/Models/GameSave.cs create mode 100644 LANCommander/Models/SaveUpload.cs create mode 100644 LANCommander/Services/GameSaveService.cs diff --git a/LANCommander/Controllers/Api/GameSavesController.cs b/LANCommander/Controllers/Api/GameSavesController.cs new file mode 100644 index 0000000..e9800c9 --- /dev/null +++ b/LANCommander/Controllers/Api/GameSavesController.cs @@ -0,0 +1,96 @@ +using LANCommander.Data; +using LANCommander.Data.Models; +using LANCommander.Extensions; +using LANCommander.Models; +using LANCommander.SDK; +using LANCommander.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; + +namespace LANCommander.Controllers.Api +{ + [Authorize(AuthenticationSchemes = "Bearer")] + [Route("api/[controller]")] + [ApiController] + public class GameSavesController : ControllerBase + { + private readonly GameSaveService GameSaveService; + private readonly GameService GameService; + private readonly UserManager UserManager; + + public GameSavesController(GameSaveService gameSaveService, GameService gameService, UserManager userManager) + { + GameSaveService = gameSaveService; + GameService = gameService; + UserManager = userManager; + } + + [HttpGet("{id}")] + public async Task Get(Guid id) + { + var gameSave = await GameSaveService.Get(id); + + if (gameSave == null || gameSave.User == null) + throw new FileNotFoundException(); + + if (gameSave.User.UserName != HttpContext.User.Identity.Name) + throw new UnauthorizedAccessException(); + + return await GameSaveService.Get(id); + } + + [HttpGet("{id}/Download")] + public async Task Download(Guid id) + { + var game = await GameService.Get(id); + + if (game == null) + return NotFound(); + + var user = await UserManager.GetUserAsync(User); + + if (user == null) + return NotFound(); + + var path = GameSaveService.GetSavePath(game.Id, user.Id); + + if (!System.IO.File.Exists(path)) + return NotFound(); + + return File(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{game.Id}.zip"); + } + + [HttpPost("{id}/Upload")] + public async Task Upload(Guid id, [FromForm] SaveUpload save) + { + // Arbitrary file size limit of 25MB + if (save.File.Length > (ByteSizeLib.ByteSize.BytesInMebiByte * 25)) + return BadRequest("Save file archive is too large"); + + var game = await GameService.Get(id); + + if (game == null) + return NotFound(); + + var user = await UserManager.GetUserAsync(User); + + if (user == null) + return NotFound(); + + var path = GameSaveService.GetSavePath(game.Id, user.Id); + + var fileInfo = new FileInfo(path); + + if (!Directory.Exists(fileInfo.Directory.FullName)) + Directory.CreateDirectory(fileInfo.Directory.FullName); + + using (var stream = System.IO.File.Create(path)) + { + await save.File.CopyToAsync(stream); + } + + return Ok(); + } + } +} diff --git a/LANCommander/Controllers/HomeController.cs b/LANCommander/Controllers/HomeController.cs index eb1072a..5fd1917 100644 --- a/LANCommander/Controllers/HomeController.cs +++ b/LANCommander/Controllers/HomeController.cs @@ -30,6 +30,7 @@ namespace LANCommander.Controllers model.TotalStorageSize = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.TotalSize)); model.TotalAvailableFreeSpace = ByteSize.FromBytes(drives.Where(d => d.IsReady && d.Name == root).Sum(d => d.AvailableFreeSpace)); model.TotalUploadDirectorySize = ByteSize.FromBytes(new DirectoryInfo("Upload").EnumerateFiles().Sum(f => f.Length)); + model.TotalSaveDirectorySize = ByteSize.FromBytes(new DirectoryInfo("Save").EnumerateFiles().Sum(f => f.Length)); model.GameCount = GameService.Get().Count; diff --git a/LANCommander/Controllers/SettingsController.cs b/LANCommander/Controllers/SettingsController.cs index fc41a5d..61296b0 100644 --- a/LANCommander/Controllers/SettingsController.cs +++ b/LANCommander/Controllers/SettingsController.cs @@ -45,11 +45,18 @@ namespace LANCommander.Controllers foreach (var user in UserManager.Users) { + var savePath = Path.Combine("Save", user.Id.ToString()); + long saveSize = 0; + + if (Directory.Exists(savePath)) + saveSize = new DirectoryInfo(savePath).EnumerateFiles().Sum(f => f.Length); + users.Add(new UserViewModel() { Id = user.Id, UserName = user.UserName, - Roles = await UserManager.GetRolesAsync(user) + Roles = await UserManager.GetRolesAsync(user), + SavesSize = saveSize }); } diff --git a/LANCommander/Data/DatabaseContext.cs b/LANCommander/Data/DatabaseContext.cs index 4c65f29..55abc39 100644 --- a/LANCommander/Data/DatabaseContext.cs +++ b/LANCommander/Data/DatabaseContext.cs @@ -79,6 +79,18 @@ namespace LANCommander.Data g => g.HasOne().WithMany().HasForeignKey("PublisherId"), g => g.HasOne().WithMany().HasForeignKey("GameId") ); + + builder.Entity() + .HasMany(u => u.GameSaves) + .WithOne(gs => gs.User) + .IsRequired(true) + .OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .HasMany(g => g.GameSaves) + .WithOne(gs => gs.Game) + .IsRequired(true) + .OnDelete(DeleteBehavior.NoAction); } public DbSet? Games { get; set; } diff --git a/LANCommander/Data/Enums/ScriptType.cs b/LANCommander/Data/Enums/ScriptType.cs index ba8313b..e183dfe 100644 --- a/LANCommander/Data/Enums/ScriptType.cs +++ b/LANCommander/Data/Enums/ScriptType.cs @@ -5,6 +5,8 @@ Install, Uninstall, NameChange, - KeyChange + KeyChange, + SaveUpload, + SaveDownload } } diff --git a/LANCommander/Data/Models/Game.cs b/LANCommander/Data/Models/Game.cs index c5f32f0..8dcf8d8 100644 --- a/LANCommander/Data/Models/Game.cs +++ b/LANCommander/Data/Models/Game.cs @@ -29,6 +29,7 @@ namespace LANCommander.Data.Models public virtual ICollection? Developers { get; set; } public virtual ICollection? Archives { get; set; } public virtual ICollection