diff --git a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj index bbd6a13..e8bed07 100644 --- a/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj +++ b/LANCommander.Playnite.Extension/LANCommander.PlaynitePlugin.csproj @@ -42,6 +42,9 @@ + + ..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll @@ -85,10 +88,13 @@ + + ..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll + - + diff --git a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs index fee02ea..a7da90f 100644 --- a/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs +++ b/LANCommander.Playnite.Extension/LANCommanderLibraryPlugin.cs @@ -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 GetTopPanelItems() diff --git a/LANCommander.Playnite.Extension/SaveController.cs b/LANCommander.Playnite.Extension/SaveController.cs new file mode 100644 index 0000000..557ee2e --- /dev/null +++ b/LANCommander.Playnite.Extension/SaveController.cs @@ -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); + } + } +} diff --git a/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs b/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs index 6b3db4f..0e6869a 100644 --- a/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs +++ b/LANCommander.Playnite.Extension/Views/Authentication.xaml.cs @@ -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); diff --git a/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs b/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs index 2388840..44526a0 100644 --- a/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs +++ b/LANCommander.Playnite.Extension/Views/LANCommanderSettingsView.xaml.cs @@ -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); diff --git a/LANCommander.SDK/Client.cs b/LANCommander.SDK/Client.cs index 958e8ca..805b0b4 100644 --- a/LANCommander.SDK/Client.cs +++ b/LANCommander.SDK/Client.cs @@ -107,7 +107,7 @@ namespace LANCommander.SDK } } - public async Task RegisterAsync(string username, string password) + public async Task RegisterAsync(string username, string password) { var response = await ApiClient.ExecuteAsync(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() diff --git a/LANCommander.Playnite.Extension/Services/GameSaveService.cs b/LANCommander.SDK/GameSaveManager.cs similarity index 52% rename from LANCommander.Playnite.Extension/Services/GameSaveService.cs rename to LANCommander.SDK/GameSaveManager.cs index 4513443..a7ffc1b 100644 --- a/LANCommander.Playnite.Extension/Services/GameSaveService.cs +++ b/LANCommander.SDK/GameSaveManager.cs @@ -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(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(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 tempRegFiles = new List(); - - 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 tempRegFiles = new List(); + + 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()); + } } } } diff --git a/LANCommander.SDK/Helpers/ManifestHelper.cs b/LANCommander.SDK/Helpers/ManifestHelper.cs index 7e82586..f4e8ed7 100644 --- a/LANCommander.SDK/Helpers/ManifestHelper.cs +++ b/LANCommander.SDK/Helpers/ManifestHelper.cs @@ -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); + } } } diff --git a/LANCommander.SDK/LANCommander.SDK.csproj b/LANCommander.SDK/LANCommander.SDK.csproj index 11a21d2..7130409 100644 --- a/LANCommander.SDK/LANCommander.SDK.csproj +++ b/LANCommander.SDK/LANCommander.SDK.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -8,7 +8,7 @@ - +