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