Refactor GameSaveService into GameSaveManager and SaveController. Update Playnite addon authentication dialogs to use new client.

pull/32/head
Pat Hartl 2023-11-10 01:32:30 -06:00
parent 39f2d4b212
commit 73b542856a
9 changed files with 201 additions and 153 deletions

View File

@ -42,6 +42,9 @@
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="SharpCompress, Version=0.34.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
@ -85,10 +88,13 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
<Reference Include="ZstdSharp, Version=0.7.2.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
<HintPath>..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
<Compile Include="Services\GameSaveService.cs" />
<Compile Include="SaveController.cs" />
<Compile Include="UninstallController.cs" />
<Compile Include="InstallController.cs" />
<Compile Include="LANCommanderLibraryPlugin.cs" />

View File

@ -1,5 +1,4 @@
using LANCommander.PlaynitePlugin.Extensions;
using LANCommander.PlaynitePlugin.Services;
using Playnite.SDK;
using Playnite.SDK.Events;
using Playnite.SDK.Models;
@ -22,7 +21,7 @@ namespace LANCommander.PlaynitePlugin
public static readonly ILogger Logger = LogManager.GetLogger();
internal LANCommanderSettingsViewModel Settings { get; set; }
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
internal GameSaveService GameSaveService { get; set; }
internal LANCommanderSaveController SaveController { get; set; }
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
public override string Name => "LANCommander";
@ -333,12 +332,12 @@ namespace LANCommander.PlaynitePlugin
public override void OnGameStarting(OnGameStartingEventArgs args)
{
GameSaveService.DownloadSave(args.Game);
SaveController.Download(args.Game);
}
public override void OnGameStopped(OnGameStoppedEventArgs args)
{
GameSaveService.UploadSave(args.Game);
SaveController.Upload(args.Game);
}
public override IEnumerable<TopPanelItem> GetTopPanelItems()

View File

@ -0,0 +1,61 @@
using LANCommander.SDK;
using Playnite.SDK;
using Playnite.SDK.Models;
using Playnite.SDK.Plugins;
namespace LANCommander.PlaynitePlugin
{
public class LANCommanderSaveController : ControllerBase
{
private static readonly ILogger Logger;
private LANCommanderLibraryPlugin Plugin;
public LANCommanderSaveController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
{
Name = "Download save using LANCommander";
Plugin = plugin;
}
public void Download(Game game)
{
if (game != null)
{
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
{
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
saveManager.OnDownloadProgress += (downloadProgress) =>
{
progress.CurrentProgressValue = downloadProgress.ProgressPercentage;
};
saveManager.OnDownloadComplete += (downloadComplete) =>
{
progress.CurrentProgressValue = 100;
};
saveManager.Download(game.InstallDirectory);
// Lock the thread until the download is done
while (progress.CurrentProgressValue != 100) { }
},
new GlobalProgressOptions("Downloading latest save...")
{
IsIndeterminate = false,
Cancelable = false
});
}
}
public void Upload(Game game)
{
var saveManager = new GameSaveManager(Plugin.LANCommanderClient);
saveManager.Upload(game.InstallDirectory);
}
}
}

View File

@ -100,24 +100,16 @@ namespace LANCommander.PlaynitePlugin.Views
LoginButton.Content = "Logging in...";
}));
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
else
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
if (Plugin.LANCommanderClient == null)
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
var response = await Plugin.LANCommander.AuthenticateAsync(Context.UserName, Context.Password);
var response = await Plugin.LANCommanderClient.AuthenticateAsync(Context.UserName, Context.Password);
Plugin.Settings.ServerAddress = Context.ServerAddress;
Plugin.Settings.AccessToken = response.AccessToken;
Plugin.Settings.RefreshToken = response.RefreshToken;
Plugin.LANCommander.Token = new AuthToken()
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken,
};
var profile = Plugin.LANCommander.GetProfile();
var profile = Plugin.LANCommanderClient.GetProfile();
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
@ -148,24 +140,16 @@ namespace LANCommander.PlaynitePlugin.Views
RegisterButton.IsEnabled = false;
RegisterButton.Content = "Working...";
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
else
Plugin.LANCommander.Client.BaseUrl = new Uri(Context.ServerAddress);
if (Plugin.LANCommanderClient == null)
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
var response = await Plugin.LANCommander.RegisterAsync(Context.UserName, Context.Password);
var response = await Plugin.LANCommanderClient.RegisterAsync(Context.UserName, Context.Password);
Plugin.Settings.ServerAddress = Context.ServerAddress;
Plugin.Settings.AccessToken = response.AccessToken;
Plugin.Settings.RefreshToken = response.RefreshToken;
Plugin.Settings.PlayerName = Context.UserName;
Plugin.LANCommander.Token = new AuthToken()
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken,
};
Context.Password = String.Empty;
Plugin.SavePluginSettings(Plugin.Settings);

View File

@ -47,7 +47,7 @@ namespace LANCommander.PlaynitePlugin
RefreshToken = Settings.RefreshToken,
};
var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token))
var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token))
.ContinueWith(antecedent =>
{
try
@ -90,7 +90,7 @@ namespace LANCommander.PlaynitePlugin
{
Plugin.Settings.AccessToken = String.Empty;
Plugin.Settings.RefreshToken = String.Empty;
Plugin.LANCommander.Token = null;
Plugin.LANCommanderClient.UseToken(null);
Plugin.SavePluginSettings(Plugin.Settings);

View File

@ -107,7 +107,7 @@ namespace LANCommander.SDK
}
}
public async Task<AuthResponse> RegisterAsync(string username, string password)
public async Task<AuthToken> RegisterAsync(string username, string password)
{
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
{
@ -118,7 +118,14 @@ namespace LANCommander.SDK
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
case HttpStatusCode.BadRequest:
case HttpStatusCode.Forbidden:
@ -137,7 +144,7 @@ namespace LANCommander.SDK
return response.StatusCode == HttpStatusCode.OK;
}
public AuthResponse RefreshToken(AuthToken token)
public AuthToken RefreshToken(AuthToken token)
{
Logger.LogTrace("Refreshing token...");
@ -149,7 +156,14 @@ namespace LANCommander.SDK
if (response.StatusCode != HttpStatusCode.OK)
throw new WebException(response.ErrorMessage);
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
}
public bool ValidateToken()

View File

@ -1,64 +1,55 @@
using LANCommander.SDK;
using Playnite.SDK;
using Playnite.SDK.Models;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Models;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Readers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.PlaynitePlugin.Services
namespace LANCommander.SDK
{
internal class GameSaveService
public class GameSaveManager
{
private readonly LANCommander.SDK.Client LANCommander;
private readonly IPlayniteAPI PlayniteApi;
private readonly Client Client;
internal GameSaveService(LANCommander.SDK.Client lanCommander, IPlayniteAPI playniteApi)
public delegate void OnDownloadProgressHandler(DownloadProgressChangedEventArgs e);
public event OnDownloadProgressHandler OnDownloadProgress;
public delegate void OnDownloadCompleteHandler(AsyncCompletedEventArgs e);
public event OnDownloadCompleteHandler OnDownloadComplete;
public GameSaveManager(Client client)
{
LANCommander = lanCommander;
PlayniteApi = playniteApi;
Client = client;
}
internal void DownloadSave(Game game)
public void Download(string installDirectory)
{
var manifest = ManifestHelper.Read(installDirectory);
string tempFile = String.Empty;
if (game != null)
if (manifest != null)
{
PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
var destination = Client.DownloadLatestSave(manifest.Id, (changed) =>
{
progress.ProgressMaxValue = 100;
progress.CurrentProgressValue = 0;
var destination = LANCommander.DownloadLatestSave(Guid.Parse(game.GameId), (changed) =>
{
progress.CurrentProgressValue = changed.ProgressPercentage;
}, (complete) =>
{
progress.CurrentProgressValue = 100;
});
// Lock the thread until download is done
while (progress.CurrentProgressValue != 100)
{
}
tempFile = destination;
},
new GlobalProgressOptions("Downloading latest save...")
OnDownloadProgress?.Invoke(changed);
}, (complete) =>
{
IsIndeterminate = false,
Cancelable = false
OnDownloadComplete?.Invoke(complete);
});
tempFile = destination;
// Go into the archive and extract the files to the correct locations
try
{
@ -72,10 +63,6 @@ namespace LANCommander.PlaynitePlugin.Services
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifestContents = File.ReadAllText(Path.Combine(tempLocation, "_manifest.yml"));
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
#region Move files
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
@ -84,7 +71,7 @@ namespace LANCommander.PlaynitePlugin.Services
var tempSavePathFile = Path.Combine(tempSavePath, savePath.Path.Replace('/', '\\').Replace("{InstallDir}\\", ""));
var destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
destination = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory));
if (File.Exists(tempSavePathFile))
{
@ -105,7 +92,7 @@ namespace LANCommander.PlaynitePlugin.Services
if (inInstallDir)
{
// Files are in the game's install directory. Move them there from the save path.
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", game.InstallDirectory));
destination = file.Replace(tempSavePath, savePath.Path.Replace('/', '\\').TrimEnd('\\').Replace("{InstallDir}", installDirectory));
if (File.Exists(destination))
File.Delete(destination);
@ -153,97 +140,89 @@ namespace LANCommander.PlaynitePlugin.Services
}
}
internal void UploadSave(Game game)
public void Upload(string installDirectory)
{
var manifestPath = Path.Combine(game.InstallDirectory, "_manifest.yml");
var manifest = ManifestHelper.Read(installDirectory);
if (File.Exists(manifestPath))
var temp = Path.GetTempFileName();
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
{
var deserializer = new DeserializerBuilder()
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
var temp = Path.GetTempFileName();
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
using (var archive = ZipArchive.Create())
{
using (var archive = ZipArchive.Create())
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
archive.DeflateCompressionLevel = SharpCompress.Compressors.Deflate.CompressionLevel.BestCompression;
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory));
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
if (Directory.Exists(localPath))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
if (Directory.Exists(localPath))
{
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
else if (File.Exists(localPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
#endregion
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
else if (File.Exists(localPath))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
if (Directory.Exists(localPath))
{
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
else if (File.Exists(localPath))
{
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
#endregion
}
#endregion
#region Export registry keys
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
#region Add files from defined paths
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
{
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory));
if (Directory.Exists(localPath))
{
List<string> tempRegFiles = new List<string>();
var exportCommand = new StringBuilder();
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
{
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
tempRegFiles.Add(tempRegFile);
}
PowerShellRuntime.RunCommand(exportCommand.ToString());
var exportFile = new StringBuilder();
foreach (var tempRegFile in tempRegFiles)
{
exportFile.AppendLine(File.ReadAllText(tempRegFile));
File.Delete(tempRegFile);
}
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
AddDirectoryToZip(archive, localPath, localPath, savePath.Id);
}
#endregion
archive.AddEntry("_manifest.yml", manifestPath);
using (var ms = new MemoryStream())
else if (File.Exists(localPath))
{
archive.SaveTo(ms);
ms.Seek(0, SeekOrigin.Begin);
var save = LANCommander.UploadSave(game.GameId, ms.ToArray());
archive.AddEntry(Path.Combine(savePath.Id.ToString(), savePath.Path.Replace("{InstallDir}/", "")), localPath);
}
}
#endregion
#region Export registry keys
if (manifest.SavePaths.Any(sp => sp.Type == "Registry"))
{
List<string> tempRegFiles = new List<string>();
var exportCommand = new StringBuilder();
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "Registry"))
{
var tempRegFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".reg");
exportCommand.AppendLine($"reg.exe export \"{savePath.Path.Replace(":\\", "\\")}\" \"{tempRegFile}\"");
tempRegFiles.Add(tempRegFile);
}
PowerShellRuntime.RunCommand(exportCommand.ToString());
var exportFile = new StringBuilder();
foreach (var tempRegFile in tempRegFiles)
{
exportFile.AppendLine(File.ReadAllText(tempRegFile));
File.Delete(tempRegFile);
}
archive.AddEntry("_registry.reg", new MemoryStream(Encoding.UTF8.GetBytes(exportFile.ToString())), true);
}
#endregion
archive.AddEntry("_manifest.yml", ManifestHelper.GetPath(installDirectory));
using (var ms = new MemoryStream())
{
archive.SaveTo(ms);
ms.Seek(0, SeekOrigin.Begin);
var save = Client.UploadSave(manifest.Id.ToString(), ms.ToArray());
}
}
}
}

View File

@ -16,7 +16,7 @@ namespace LANCommander.SDK.Helpers
public static GameManifest Read(string installDirectory)
{
var source = Path.Combine(installDirectory, ManifestFilename);
var source = GetPath(installDirectory);
var yaml = File.ReadAllText(source);
var deserializer = new DeserializerBuilder()
@ -32,7 +32,7 @@ namespace LANCommander.SDK.Helpers
public static void Write(GameManifest manifest, string installDirectory)
{
var destination = Path.Combine(installDirectory, ManifestFilename);
var destination = GetPath(installDirectory);
Logger.LogTrace("Attempting to write manifest to path {Destination}", destination);
@ -48,5 +48,10 @@ namespace LANCommander.SDK.Helpers
File.WriteAllText(destination, yaml);
}
public static string GetPath(string installDirectory)
{
return Path.Combine(installDirectory, ManifestFilename);
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@ -8,7 +8,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="RestSharp" Version="106.15.0" />
<PackageReference Include="SharpCompress" Version="0.34.1" />
<PackageReference Include="YamlDotNet" Version="13.7.1" />
<PackageReference Include="YamlDotNet" Version="13.3.1" />
</ItemGroup>
</Project>