Added uploading of saves

pull/19/head
Pat Hartl 2023-03-28 21:30:29 -05:00
parent 676fa8e48b
commit 32d6e109df
16 changed files with 309 additions and 14 deletions

1
.gitignore vendored
View File

@ -351,3 +351,4 @@ MigrationBackup/
Upload/
LANCommander/Icon/
LANCommander/Settings.yml
LANCommander/Saves/

View File

@ -12,6 +12,7 @@ using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Converters;
using YamlDotNet.Core;
namespace LANCommander.PlaynitePlugin
{
@ -170,6 +171,28 @@ namespace LANCommander.PlaynitePlugin
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
}
public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
{
return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler);
}
public string DownloadLatestSave(Guid gameId, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
{
return DownloadRequest($"/api/Saves/DownloadLatest/{gameId}", progressHandler, completeHandler);
}
public GameSave UploadSave(string gameId, byte[] data)
{
var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
request.AddFile(gameId, data, gameId);
var response = Client.Post<GameSave>(request);
return response.Data;
}
public string GetKey(Guid id)
{
var macAddress = GetMacAddress();

View File

@ -1,4 +1,5 @@
using LANCommander.PlaynitePlugin.Extensions;
using ICSharpCode.SharpZipLib.Zip;
using LANCommander.PlaynitePlugin.Extensions;
using LANCommander.SDK;
using Playnite.SDK;
using Playnite.SDK.Events;
@ -175,6 +176,7 @@ namespace LANCommander.PlaynitePlugin
{
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install);
if (File.Exists(nameChangeScriptPath))
yield return new GameMenuItem
@ -215,6 +217,25 @@ namespace LANCommander.PlaynitePlugin
}
}
};
if (File.Exists(installScriptPath))
yield return new GameMenuItem
{
Description = "Run Install Script",
Action = (installArgs) =>
{
Guid gameId;
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
{
PowerShellRuntime.RunScript(installArgs.Games.First(), SDK.Enums.ScriptType.Install);
}
else
{
PlayniteApi.Dialogs.ShowErrorMessage("This game could not be found on the server. Your game may be corrupted.");
}
}
};
}
}
@ -246,6 +267,77 @@ namespace LANCommander.PlaynitePlugin
};
}
public override void OnGameStopped(OnGameStoppedEventArgs args)
{
var manifestPath = Path.Combine(args.Game.InstallDirectory, "_manifest.yml");
if (File.Exists(manifestPath))
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
var temp = Path.GetTempFileName();
using (ZipOutputStream zipStream = new ZipOutputStream(File.Create(temp)))
{
zipStream.SetLevel(5);
foreach (var savePath in manifest.SavePaths)
{
savePath.Path = savePath.Path.Replace('/', '\\').Replace("{InstallDir}", args.Game.InstallDirectory);
if (Directory.Exists(savePath.Path))
{
AddDirectoryToZip(zipStream, savePath.Path);
}
else if (File.Exists(savePath.Path))
{
var entry = new ZipEntry(Path.Combine(savePath.Id.ToString(), Path.GetFileName(savePath.Path)));
zipStream.PutNextEntry(entry);
byte[] buffer = File.ReadAllBytes(savePath.Path);
zipStream.Write(buffer, 0, buffer.Length);
zipStream.CloseEntry();
}
}
}
var save = LANCommander.UploadSave(args.Game.GameId, File.ReadAllBytes(temp));
File.Delete(temp);
}
}
private void AddDirectoryToZip(ZipOutputStream zipStream, string path)
{
foreach (var file in Directory.GetFiles(path))
{
var entry = new ZipEntry(Path.GetFileName(file));
zipStream.PutNextEntry(entry);
byte[] buffer = File.ReadAllBytes(file);
zipStream.Write(buffer, 0, buffer.Length);
zipStream.CloseEntry();
}
foreach (var child in Directory.GetDirectories(path))
{
ZipEntry entry = new ZipEntry(Path.GetFileName(path));
zipStream.PutNextEntry(entry);
zipStream.CloseEntry();
AddDirectoryToZip(zipStream, child);
}
}
public override IEnumerable<TopPanelItem> GetTopPanelItems()
{
yield return new TopPanelItem

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK.Enums
{
public enum SavePathType
{
File,
Registry
}
}

View File

@ -1,4 +1,5 @@
using System;
using LANCommander.SDK.Enums;
using System;
using System.Collections.Generic;
namespace LANCommander.SDK
@ -20,6 +21,7 @@ namespace LANCommander.SDK
public MultiplayerInfo LocalMultiplayer { get; set; }
public MultiplayerInfo LanMultiplayer { get; set; }
public MultiplayerInfo OnlineMultiplayer { get; set; }
public IEnumerable<SavePath> SavePaths { get; set; }
public GameManifest() { }
}
@ -39,4 +41,11 @@ namespace LANCommander.SDK
public int MinPlayers { get; set; }
public int MaxPlayers { get; set; }
}
public class SavePath
{
public Guid Id { get; set; }
public string Type { get; set; }
public string Path { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK.Models
{
public class GameSave : BaseModel
{
public Guid GameId { get; set; }
public virtual Game Game { get; set; }
public Guid UserId { get; set; }
public virtual User User { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using LANCommander.SDK.Enums;
using System;
using System.Collections.Generic;
using System.Text;
namespace LANCommander.SDK.Models
{
public class SavePath : BaseModel
{
public SavePathType Type { get; set; }
public string Path { get; set; }
public virtual Game Game { get; set; }
}
}

View File

@ -1,4 +1,5 @@
@using LANCommander.Data.Enums
@using LANCommander.SDK.Enums;
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
<SpaceItem>

View File

@ -0,0 +1,115 @@
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;
using Microsoft.EntityFrameworkCore;
using System.Runtime.Intrinsics.X86;
namespace LANCommander.Controllers.Api
{
[Authorize(AuthenticationSchemes = "Bearer")]
[Route("api/[controller]")]
[ApiController]
public class SavesController : ControllerBase
{
private readonly GameService GameService;
private readonly GameSaveService GameSaveService;
private readonly UserManager<User> UserManager;
public SavesController(GameService gameService, GameSaveService gameSaveService, UserManager<User> userManager)
{
GameService = gameService;
GameSaveService = gameSaveService;
UserManager = userManager;
}
[HttpGet]
public IEnumerable<GameSave> Get()
{
return GameSaveService.Get();
}
[HttpGet("{id}")]
public async Task<GameSave> Get(Guid id)
{
return await GameSaveService.Get(id);
}
[HttpGet("DownloadLatest/{gameId}")]
public async Task<IActionResult> DownloadLatest(Guid gameId)
{
var user = await UserManager.FindByNameAsync(User.Identity.Name);
if (user == null)
return NotFound();
var save = await GameSaveService
.Get(gs => gs.GameId == gameId && gs.UserId == user.Id)
.OrderByDescending(gs => gs.CreatedOn)
.FirstOrDefaultAsync();
if (save == null)
return NotFound();
var filename = save.GetUploadPath();
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{save.Id.ToString().SanitizeFilename()}.zip");
}
[HttpGet("Download/{id}")]
public async Task<IActionResult> Download(Guid id)
{
var save = await GameSaveService.Get(id);
if (save == null)
return NotFound();
var filename = save.GetUploadPath();
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{save.Id.ToString().SanitizeFilename()}.zip");
}
[HttpPost("Upload/{id}")]
public async Task<IActionResult> Upload(Guid id)
{
var file = Request.Form.Files.First();
var user = await UserManager.FindByNameAsync(User.Identity.Name);
var game = await GameService.Get(id);
if (game == null)
return NotFound();
var save = new GameSave()
{
GameId = id,
UserId = user.Id
};
save = await GameSaveService.Add(save);
var saveUploadPath = Path.GetDirectoryName(save.GetUploadPath());
if (!Directory.Exists(saveUploadPath))
Directory.CreateDirectory(saveUploadPath);
using (var stream = System.IO.File.Create(save.GetUploadPath()))
{
await file.CopyToAsync(stream);
}
return Ok(save);
}
}
}

View File

@ -1,8 +0,0 @@
namespace LANCommander.Data.Enums
{
public enum SavePathType
{
File,
Registry
}
}

View File

@ -16,5 +16,10 @@ namespace LANCommander.Data.Models
[ForeignKey(nameof(UserId))]
[InverseProperty("GameSaves")]
public virtual User? User { get; set; }
public string GetUploadPath()
{
return Path.Combine("Saves", UserId.ToString(), GameId.ToString(), Id.ToString());
}
}
}

View File

@ -1,4 +1,5 @@
using LANCommander.Data.Enums;
using LANCommander.SDK.Enums;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

View File

@ -44,5 +44,10 @@ namespace LANCommander.Data.Models
[JsonIgnore]
public virtual ICollection<GameSave>? GameSaves { get; set; }
public string GetGameSaveUploadPath()
{
return Path.Combine("Saves", Id.ToString());
}
}
}

View File

@ -55,12 +55,12 @@
foreach (var user in UserManager.Users)
{
var savePath = Path.Combine("Save", user.Id.ToString());
var savePath = user.GetGameSaveUploadPath();
long saveSize = 0;
if (Directory.Exists(savePath))
saveSize = new DirectoryInfo(savePath).EnumerateFiles().Sum(f => f.Length);
saveSize = new DirectoryInfo(savePath).EnumerateFiles("*", SearchOption.AllDirectories).Sum(f => f.Length);
UserList.Add(new UserViewModel()
{

View File

@ -114,6 +114,7 @@ builder.Services.AddScoped<TagService>();
builder.Services.AddScoped<CompanyService>();
builder.Services.AddScoped<IGDBService>();
builder.Services.AddScoped<ServerService>();
builder.Services.AddScoped<GameSaveService>();
builder.Services.AddSingleton<ServerProcessService>();
@ -161,8 +162,8 @@ if (!Directory.Exists("Upload"))
if (!Directory.Exists("Icon"))
Directory.CreateDirectory("Icon");
if (!Directory.Exists("Save"))
Directory.CreateDirectory("Save");
if (!Directory.Exists("Saves"))
Directory.CreateDirectory("Saves");
if (!Directory.Exists("Snippets"))
Directory.CreateDirectory("Snippets");

View File

@ -99,6 +99,16 @@ namespace LANCommander.Services
};
}
if (game.SavePaths != null && game.SavePaths.Count > 0)
{
manifest.SavePaths = game.SavePaths.Select(p => new SDK.SavePath()
{
Id = p.Id,
Path = p.Path,
Type = p.Type.ToString()
});
}
return manifest;
}