Added uploading of saves
This commit is contained in:
parent
676fa8e48b
commit
32d6e109df
16 changed files with 309 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -351,3 +351,4 @@ MigrationBackup/
|
||||||
Upload/
|
Upload/
|
||||||
LANCommander/Icon/
|
LANCommander/Icon/
|
||||||
LANCommander/Settings.yml
|
LANCommander/Settings.yml
|
||||||
|
LANCommander/Saves/
|
||||||
|
|
|
@ -12,6 +12,7 @@ using System.Net.NetworkInformation;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Media.Converters;
|
using System.Windows.Media.Converters;
|
||||||
|
using YamlDotNet.Core;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
|
@ -170,6 +171,28 @@ namespace LANCommander.PlaynitePlugin
|
||||||
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
|
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)
|
public string GetKey(Guid id)
|
||||||
{
|
{
|
||||||
var macAddress = GetMacAddress();
|
var macAddress = GetMacAddress();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using LANCommander.PlaynitePlugin.Extensions;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using LANCommander.PlaynitePlugin.Extensions;
|
||||||
using LANCommander.SDK;
|
using LANCommander.SDK;
|
||||||
using Playnite.SDK;
|
using Playnite.SDK;
|
||||||
using Playnite.SDK.Events;
|
using Playnite.SDK.Events;
|
||||||
|
@ -175,6 +176,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
||||||
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
|
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))
|
if (File.Exists(nameChangeScriptPath))
|
||||||
yield return new GameMenuItem
|
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()
|
public override IEnumerable<TopPanelItem> GetTopPanelItems()
|
||||||
{
|
{
|
||||||
yield return new TopPanelItem
|
yield return new TopPanelItem
|
||||||
|
|
12
LANCommander.SDK/Enums/SavePathType.cs
Normal file
12
LANCommander.SDK/Enums/SavePathType.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK.Enums
|
||||||
|
{
|
||||||
|
public enum SavePathType
|
||||||
|
{
|
||||||
|
File,
|
||||||
|
Registry
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using LANCommander.SDK.Enums;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace LANCommander.SDK
|
namespace LANCommander.SDK
|
||||||
|
@ -20,6 +21,7 @@ namespace LANCommander.SDK
|
||||||
public MultiplayerInfo LocalMultiplayer { get; set; }
|
public MultiplayerInfo LocalMultiplayer { get; set; }
|
||||||
public MultiplayerInfo LanMultiplayer { get; set; }
|
public MultiplayerInfo LanMultiplayer { get; set; }
|
||||||
public MultiplayerInfo OnlineMultiplayer { get; set; }
|
public MultiplayerInfo OnlineMultiplayer { get; set; }
|
||||||
|
public IEnumerable<SavePath> SavePaths { get; set; }
|
||||||
|
|
||||||
public GameManifest() { }
|
public GameManifest() { }
|
||||||
}
|
}
|
||||||
|
@ -39,4 +41,11 @@ namespace LANCommander.SDK
|
||||||
public int MinPlayers { get; set; }
|
public int MinPlayers { get; set; }
|
||||||
public int MaxPlayers { 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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
LANCommander.SDK/Models/GameSave.cs
Normal file
14
LANCommander.SDK/Models/GameSave.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
14
LANCommander.SDK/Models/SavePath.cs
Normal file
14
LANCommander.SDK/Models/SavePath.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@using LANCommander.Data.Enums
|
@using LANCommander.Data.Enums
|
||||||
|
@using LANCommander.SDK.Enums;
|
||||||
|
|
||||||
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
|
<Space Direction="DirectionVHType.Vertical" Size="@("large")" Style="width: 100%">
|
||||||
<SpaceItem>
|
<SpaceItem>
|
||||||
|
|
115
LANCommander/Controllers/Api/SavesController.cs
Normal file
115
LANCommander/Controllers/Api/SavesController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
namespace LANCommander.Data.Enums
|
|
||||||
{
|
|
||||||
public enum SavePathType
|
|
||||||
{
|
|
||||||
File,
|
|
||||||
Registry
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,5 +16,10 @@ namespace LANCommander.Data.Models
|
||||||
[ForeignKey(nameof(UserId))]
|
[ForeignKey(nameof(UserId))]
|
||||||
[InverseProperty("GameSaves")]
|
[InverseProperty("GameSaves")]
|
||||||
public virtual User? User { get; set; }
|
public virtual User? User { get; set; }
|
||||||
|
|
||||||
|
public string GetUploadPath()
|
||||||
|
{
|
||||||
|
return Path.Combine("Saves", UserId.ToString(), GameId.ToString(), Id.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using LANCommander.Data.Enums;
|
using LANCommander.Data.Enums;
|
||||||
|
using LANCommander.SDK.Enums;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
|
|
@ -44,5 +44,10 @@ namespace LANCommander.Data.Models
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||||
|
|
||||||
|
public string GetGameSaveUploadPath()
|
||||||
|
{
|
||||||
|
return Path.Combine("Saves", Id.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,12 +55,12 @@
|
||||||
|
|
||||||
foreach (var user in UserManager.Users)
|
foreach (var user in UserManager.Users)
|
||||||
{
|
{
|
||||||
var savePath = Path.Combine("Save", user.Id.ToString());
|
var savePath = user.GetGameSaveUploadPath();
|
||||||
|
|
||||||
long saveSize = 0;
|
long saveSize = 0;
|
||||||
|
|
||||||
if (Directory.Exists(savePath))
|
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()
|
UserList.Add(new UserViewModel()
|
||||||
{
|
{
|
||||||
|
|
|
@ -114,6 +114,7 @@ builder.Services.AddScoped<TagService>();
|
||||||
builder.Services.AddScoped<CompanyService>();
|
builder.Services.AddScoped<CompanyService>();
|
||||||
builder.Services.AddScoped<IGDBService>();
|
builder.Services.AddScoped<IGDBService>();
|
||||||
builder.Services.AddScoped<ServerService>();
|
builder.Services.AddScoped<ServerService>();
|
||||||
|
builder.Services.AddScoped<GameSaveService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<ServerProcessService>();
|
builder.Services.AddSingleton<ServerProcessService>();
|
||||||
|
|
||||||
|
@ -161,8 +162,8 @@ if (!Directory.Exists("Upload"))
|
||||||
if (!Directory.Exists("Icon"))
|
if (!Directory.Exists("Icon"))
|
||||||
Directory.CreateDirectory("Icon");
|
Directory.CreateDirectory("Icon");
|
||||||
|
|
||||||
if (!Directory.Exists("Save"))
|
if (!Directory.Exists("Saves"))
|
||||||
Directory.CreateDirectory("Save");
|
Directory.CreateDirectory("Saves");
|
||||||
|
|
||||||
if (!Directory.Exists("Snippets"))
|
if (!Directory.Exists("Snippets"))
|
||||||
Directory.CreateDirectory("Snippets");
|
Directory.CreateDirectory("Snippets");
|
||||||
|
|
|
@ -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;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue