Merge pull request #32 from LANCommander/migrate-installation-to-sdk
Migrate LANCommander Client Code to SDK
This commit is contained in:
commit
1a0cff3914
24 changed files with 939 additions and 701 deletions
|
@ -1,17 +1,12 @@
|
||||||
using LANCommander.PlaynitePlugin.Helpers;
|
using LANCommander.SDK;
|
||||||
using LANCommander.SDK.Enums;
|
using LANCommander.SDK.Helpers;
|
||||||
using LANCommander.SDK.Extensions;
|
|
||||||
using LANCommander.SDK.Models;
|
using LANCommander.SDK.Models;
|
||||||
using Playnite.SDK;
|
using Playnite.SDK;
|
||||||
using Playnite.SDK.Models;
|
using Playnite.SDK.Models;
|
||||||
using Playnite.SDK.Plugins;
|
using Playnite.SDK.Plugins;
|
||||||
using SharpCompress.Common;
|
|
||||||
using SharpCompress.Readers;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using YamlDotNet.Serialization;
|
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
|
@ -20,15 +15,11 @@ namespace LANCommander.PlaynitePlugin
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||||
|
|
||||||
private LANCommanderLibraryPlugin Plugin;
|
private LANCommanderLibraryPlugin Plugin;
|
||||||
private PowerShellRuntime PowerShellRuntime;
|
|
||||||
private Playnite.SDK.Models.Game PlayniteGame;
|
|
||||||
|
|
||||||
public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game)
|
public LANCommanderInstallController(LANCommanderLibraryPlugin plugin, Playnite.SDK.Models.Game game) : base(game)
|
||||||
{
|
{
|
||||||
Name = "Install using LANCommander";
|
Name = "Install using LANCommander";
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
PlayniteGame = game;
|
|
||||||
PowerShellRuntime = new PowerShellRuntime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Install(InstallActionArgs args)
|
public override void Install(InstallActionArgs args)
|
||||||
|
@ -43,450 +34,101 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
var gameId = Guid.Parse(Game.GameId);
|
var gameId = Guid.Parse(Game.GameId);
|
||||||
var game = Plugin.LANCommander.GetGame(gameId);
|
|
||||||
|
|
||||||
Logger.Trace($"Installing game {game.Title} ({game.Id})...");
|
string installDirectory = null;
|
||||||
|
|
||||||
var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||||
{
|
{
|
||||||
Logger.Trace("Attempting to download and extract game...");
|
var gameManager = new GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
|
||||||
return DownloadAndExtractGame(game);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.Success && !result.Canceled)
|
Stopwatch stopwatch = new Stopwatch();
|
||||||
throw new Exception("Could not extract the install archive. Retry the install or check your connection.");
|
|
||||||
else if (result.Canceled)
|
|
||||||
throw new Exception("Install was canceled");
|
|
||||||
|
|
||||||
var installInfo = new GameInstallationData()
|
stopwatch.Start();
|
||||||
|
|
||||||
|
var lastTotalSize = 0d;
|
||||||
|
var speed = 0d;
|
||||||
|
|
||||||
|
gameManager.OnArchiveExtractionProgress += (long pos, long len) =>
|
||||||
{
|
{
|
||||||
InstallDirectory = result.Directory
|
if (stopwatch.ElapsedMilliseconds > 500)
|
||||||
|
{
|
||||||
|
var percent = Math.Ceiling((pos / (decimal)len) * 100);
|
||||||
|
|
||||||
|
progress.ProgressMaxValue = len;
|
||||||
|
progress.CurrentProgressValue = pos;
|
||||||
|
|
||||||
|
speed = (double)(progress.CurrentProgressValue - lastTotalSize) / (stopwatch.ElapsedMilliseconds / 1000d);
|
||||||
|
|
||||||
|
progress.Text = $"Downloading {Game.Name} ({percent}%) | {ByteSizeLib.ByteSize.FromBytes(speed).ToString("#.#")}/s";
|
||||||
|
|
||||||
|
lastTotalSize = pos;
|
||||||
|
|
||||||
|
stopwatch.Restart();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PlayniteGame.InstallDirectory = result.Directory;
|
gameManager.OnArchiveEntryExtractionProgress += (object sender, ArchiveEntryExtractionProgressArgs e) =>
|
||||||
|
|
||||||
SDK.GameManifest manifest = null;
|
|
||||||
|
|
||||||
var writeManifestSuccess = RetryHelper.RetryOnException(10, TimeSpan.FromSeconds(1), false, () =>
|
|
||||||
{
|
{
|
||||||
Logger.Trace("Attempting to get game manifest...");
|
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
gameManager.CancelInstall();
|
||||||
|
|
||||||
manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
progress.IsIndeterminate = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
WriteManifest(manifest, result.Directory);
|
installDirectory = gameManager.Install(gameId);
|
||||||
|
|
||||||
return true;
|
stopwatch.Stop();
|
||||||
|
},
|
||||||
|
new GlobalProgressOptions($"Preparing to download {Game.Name}")
|
||||||
|
{
|
||||||
|
IsIndeterminate = false,
|
||||||
|
Cancelable = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!writeManifestSuccess)
|
// Install any redistributables
|
||||||
throw new Exception("Could not get or write the manifest file. Retry the install or check your connection.");
|
var game = Plugin.LANCommanderClient.GetGame(gameId);
|
||||||
|
|
||||||
Logger.Trace("Saving scripts...");
|
|
||||||
|
|
||||||
SaveScript(game, result.Directory, ScriptType.Install);
|
|
||||||
SaveScript(game, result.Directory, ScriptType.Uninstall);
|
|
||||||
SaveScript(game, result.Directory, ScriptType.NameChange);
|
|
||||||
SaveScript(game, result.Directory, ScriptType.KeyChange);
|
|
||||||
|
|
||||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||||
{
|
{
|
||||||
Logger.Trace("Installing required redistributables...");
|
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||||
InstallRedistributables(game);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
|
var redistributableManager = new RedistributableManager(Plugin.LANCommanderClient);
|
||||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.NameChange, Plugin.Settings.PlayerName);
|
|
||||||
|
|
||||||
var key = Plugin.LANCommander.GetAllocatedKey(game.Id);
|
redistributableManager.Install(game);
|
||||||
|
},
|
||||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.KeyChange, $"\"{key}\"");
|
new GlobalProgressOptions("Installing redistributables...")
|
||||||
|
{
|
||||||
|
IsIndeterminate = true,
|
||||||
|
Cancelable = false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
|
|
||||||
Plugin.UpdateGame(manifest, gameId);
|
if (!result.Canceled && result.Error == null && !String.IsNullOrWhiteSpace(installDirectory))
|
||||||
|
{
|
||||||
|
var manifest = ManifestHelper.Read(installDirectory);
|
||||||
|
|
||||||
Plugin.DownloadCache.Remove(gameId);
|
Plugin.UpdateGame(manifest);
|
||||||
|
|
||||||
|
var installInfo = new GameInstallationData
|
||||||
|
{
|
||||||
|
InstallDirectory = installDirectory,
|
||||||
|
};
|
||||||
|
|
||||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||||
}
|
}
|
||||||
|
else if (result.Canceled)
|
||||||
private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game)
|
|
||||||
{
|
{
|
||||||
if (game == null)
|
var dbGame = Plugin.PlayniteApi.Database.Games.Get(Game.Id);
|
||||||
{
|
|
||||||
Logger.Trace("Game failed to download! No game was specified!");
|
|
||||||
|
|
||||||
throw new Exception("Game failed to download!");
|
dbGame.IsInstalling = false;
|
||||||
|
dbGame.IsInstalled = false;
|
||||||
|
|
||||||
|
Plugin.PlayniteApi.Database.Games.Update(dbGame);
|
||||||
}
|
}
|
||||||
|
else if (result.Error != null)
|
||||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
throw result.Error;
|
||||||
|
|
||||||
Logger.Trace($"Downloading and extracting \"{game.Title}\" to path {destination}");
|
|
||||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(destination);
|
|
||||||
progress.ProgressMaxValue = 100;
|
|
||||||
progress.CurrentProgressValue = 0;
|
|
||||||
|
|
||||||
using (var gameStream = Plugin.LANCommander.StreamGame(game.Id))
|
|
||||||
using (var reader = ReaderFactory.Open(gameStream))
|
|
||||||
{
|
|
||||||
progress.ProgressMaxValue = gameStream.Length;
|
|
||||||
|
|
||||||
gameStream.OnProgress += (pos, len) =>
|
|
||||||
{
|
|
||||||
progress.CurrentProgressValue = pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
|
||||||
{
|
|
||||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
reader.Cancel();
|
|
||||||
progress.IsIndeterminate = true;
|
|
||||||
|
|
||||||
reader.Dispose();
|
|
||||||
gameStream.Dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.Trace("User cancelled the download");
|
|
||||||
|
|
||||||
if (Directory.Exists(destination))
|
|
||||||
{
|
|
||||||
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
|
|
||||||
|
|
||||||
Directory.Delete(destination, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
|
||||||
|
|
||||||
if (Directory.Exists(destination))
|
|
||||||
{
|
|
||||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
|
||||||
|
|
||||||
Directory.Delete(destination, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("The game archive could not be extracted. Please try again or fix the archive!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new GlobalProgressOptions($"Downloading {game.Title}...")
|
|
||||||
{
|
|
||||||
IsIndeterminate = false,
|
|
||||||
Cancelable = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
var extractionResult = new ExtractionResult
|
|
||||||
{
|
|
||||||
Canceled = result.Canceled
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!result.Canceled)
|
|
||||||
{
|
|
||||||
extractionResult.Success = true;
|
|
||||||
extractionResult.Directory = destination;
|
|
||||||
Logger.Trace($"Game successfully downloaded and extracted to {destination}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractionResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InstallRedistributables(LANCommander.SDK.Models.Game game)
|
|
||||||
{
|
|
||||||
foreach (var redistributable in game.Redistributables)
|
|
||||||
{
|
|
||||||
string installScriptTempFile = null;
|
|
||||||
string detectionScriptTempFile = null;
|
|
||||||
string extractTempPath = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
|
||||||
installScriptTempFile = SaveTempScript(installScript);
|
|
||||||
|
|
||||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
|
||||||
detectionScriptTempFile = SaveTempScript(detectionScript);
|
|
||||||
|
|
||||||
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
|
||||||
|
|
||||||
// Redistributable is not installed
|
|
||||||
if (detectionResult == 0)
|
|
||||||
{
|
|
||||||
if (redistributable.Archives.Count() > 0)
|
|
||||||
{
|
|
||||||
var extractionResult = DownloadAndExtractRedistributable(redistributable);
|
|
||||||
|
|
||||||
if (extractionResult.Success)
|
|
||||||
{
|
|
||||||
extractTempPath = extractionResult.Directory;
|
|
||||||
|
|
||||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, $"Redistributable {redistributable.Name} failed to install");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (File.Exists(installScriptTempFile))
|
|
||||||
File.Delete(installScriptTempFile);
|
|
||||||
|
|
||||||
if (File.Exists(detectionScriptTempFile))
|
|
||||||
File.Delete(detectionScriptTempFile);
|
|
||||||
|
|
||||||
if (Directory.Exists(extractTempPath))
|
|
||||||
Directory.Delete(extractTempPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExtractionResult DownloadAndExtractRedistributable(LANCommander.SDK.Models.Redistributable redistributable)
|
|
||||||
{
|
|
||||||
if (redistributable == null)
|
|
||||||
{
|
|
||||||
Logger.Trace("Redistributable failed to download! No redistributable was specified!");
|
|
||||||
|
|
||||||
throw new Exception("Redistributable failed to download!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
|
||||||
|
|
||||||
Logger.Trace($"Downloading and extracting \"{redistributable.Name}\" to path {destination}");
|
|
||||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(destination);
|
|
||||||
progress.ProgressMaxValue = 100;
|
|
||||||
progress.CurrentProgressValue = 0;
|
|
||||||
|
|
||||||
using (var redistributableStream = Plugin.LANCommander.StreamRedistributable(redistributable.Id))
|
|
||||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
|
||||||
{
|
|
||||||
progress.ProgressMaxValue = redistributableStream.Length;
|
|
||||||
|
|
||||||
redistributableStream.OnProgress += (pos, len) =>
|
|
||||||
{
|
|
||||||
progress.CurrentProgressValue = pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
|
||||||
{
|
|
||||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
reader.Cancel();
|
|
||||||
progress.IsIndeterminate = true;
|
|
||||||
|
|
||||||
reader.Dispose();
|
|
||||||
redistributableStream.Dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.Trace("User cancelled the download");
|
|
||||||
|
|
||||||
if (Directory.Exists(destination))
|
|
||||||
{
|
|
||||||
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
|
|
||||||
|
|
||||||
Directory.Delete(destination, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
|
||||||
|
|
||||||
if (Directory.Exists(destination))
|
|
||||||
{
|
|
||||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
|
||||||
|
|
||||||
Directory.Delete(destination, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("The redistributable archive could not be extracted. Please try again or fix the archive!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new GlobalProgressOptions($"Downloading {redistributable.Name}...")
|
|
||||||
{
|
|
||||||
IsIndeterminate = false,
|
|
||||||
Cancelable = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
var extractionResult = new ExtractionResult
|
|
||||||
{
|
|
||||||
Canceled = result.Canceled
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!result.Canceled)
|
|
||||||
{
|
|
||||||
extractionResult.Success = true;
|
|
||||||
extractionResult.Directory = destination;
|
|
||||||
Logger.Trace($"Redistributable successfully downloaded and extracted to {destination}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractionResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Download(LANCommander.SDK.Models.Game game)
|
|
||||||
{
|
|
||||||
string tempFile = String.Empty;
|
|
||||||
|
|
||||||
if (game != null)
|
|
||||||
{
|
|
||||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
|
||||||
{
|
|
||||||
progress.ProgressMaxValue = 100;
|
|
||||||
progress.CurrentProgressValue = 0;
|
|
||||||
|
|
||||||
var destination = Plugin.LANCommander.DownloadGame(game.Id, (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 {game.Title}...")
|
|
||||||
{
|
|
||||||
IsIndeterminate = false,
|
|
||||||
Cancelable = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return tempFile;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception("Game failed to download!");
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Extract(LANCommander.SDK.Models.Game game, string archivePath)
|
|
||||||
{
|
|
||||||
var destination = Path.Combine(Plugin.Settings.InstallDirectory, game.Title.SanitizeFilename());
|
|
||||||
|
|
||||||
Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(destination);
|
|
||||||
|
|
||||||
using (var fs = File.OpenRead(archivePath))
|
|
||||||
using (var ts = new TrackableStream(fs))
|
|
||||||
using (var reader = ReaderFactory.Open(ts))
|
|
||||||
{
|
|
||||||
progress.ProgressMaxValue = ts.Length;
|
|
||||||
ts.OnProgress += (pos, len) =>
|
|
||||||
{
|
|
||||||
progress.CurrentProgressValue = pos;
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new GlobalProgressOptions($"Extracting {game.Title}...")
|
|
||||||
{
|
|
||||||
IsIndeterminate = false,
|
|
||||||
Cancelable = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
|
|
||||||
{
|
|
||||||
var destination = Path.Combine(installDirectory, "_manifest.yml");
|
|
||||||
|
|
||||||
Logger.Trace($"Attempting to write manifest to path {destination}");
|
|
||||||
|
|
||||||
var serializer = new SerializerBuilder()
|
|
||||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
Logger.Trace("Serializing manifest...");
|
|
||||||
var yaml = serializer.Serialize(manifest);
|
|
||||||
|
|
||||||
Logger.Trace("Writing manifest file...");
|
|
||||||
File.WriteAllText(destination, yaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string SaveTempScript(LANCommander.SDK.Models.Script script)
|
|
||||||
{
|
|
||||||
var tempPath = Path.GetTempFileName();
|
|
||||||
|
|
||||||
File.Move(tempPath, tempPath + ".ps1");
|
|
||||||
|
|
||||||
tempPath = tempPath + ".ps1";
|
|
||||||
|
|
||||||
Logger.Trace($"Writing script {script.Name} to {tempPath}");
|
|
||||||
|
|
||||||
File.WriteAllText(tempPath, script.Contents);
|
|
||||||
|
|
||||||
return tempPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
|
|
||||||
{
|
|
||||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
|
||||||
|
|
||||||
if (script == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (script.RequiresAdmin)
|
|
||||||
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
|
||||||
|
|
||||||
var filename = PowerShellRuntime.GetScriptFilePath(PlayniteGame, type);
|
|
||||||
|
|
||||||
if (File.Exists(filename))
|
|
||||||
File.Delete(filename);
|
|
||||||
|
|
||||||
Logger.Trace($"Writing {type} script to {filename}");
|
|
||||||
|
|
||||||
File.WriteAllText(filename, script.Contents);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
<Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="BeaconLib, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath>
|
<HintPath>..\packages\rix0rrr.BeaconLib.1.0.2\lib\net40\BeaconLib.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="ByteSize, Version=2.1.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\ByteSize.2.1.1\lib\net45\ByteSize.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@ -42,9 +45,6 @@
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
<Reference Include="RestSharp, Version=106.15.0.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\RestSharp.106.15.0\lib\net452\RestSharp.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="SharpCompress, Version=0.34.1.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="SharpCompress, Version=0.34.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll</HintPath>
|
<HintPath>..\packages\SharpCompress.0.34.1\lib\net462\SharpCompress.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@ -91,23 +91,15 @@
|
||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
<Reference Include="WindowsBase" />
|
<Reference Include="WindowsBase" />
|
||||||
<Reference Include="YamlDotNet, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\YamlDotNet.5.4.0\lib\net45\YamlDotNet.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ZstdSharp, Version=0.7.2.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
|
<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>
|
<HintPath>..\packages\ZstdSharp.Port.0.7.2\lib\net461\ZstdSharp.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ExtractionResult.cs" />
|
|
||||||
<Compile Include="TrackableStream.cs" />
|
|
||||||
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
|
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
|
||||||
<Compile Include="Helpers\RetryHelper.cs" />
|
<Compile Include="SaveController.cs" />
|
||||||
<Compile Include="PowerShellRuntime.cs" />
|
|
||||||
<Compile Include="Services\GameSaveService.cs" />
|
|
||||||
<Compile Include="UninstallController.cs" />
|
<Compile Include="UninstallController.cs" />
|
||||||
<Compile Include="InstallController.cs" />
|
<Compile Include="InstallController.cs" />
|
||||||
<Compile Include="LANCommanderClient.cs" />
|
|
||||||
<Compile Include="LANCommanderLibraryPlugin.cs" />
|
<Compile Include="LANCommanderLibraryPlugin.cs" />
|
||||||
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
|
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
|
||||||
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">
|
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using LANCommander.PlaynitePlugin.Extensions;
|
using LANCommander.PlaynitePlugin.Extensions;
|
||||||
using LANCommander.PlaynitePlugin.Services;
|
|
||||||
using Playnite.SDK;
|
using Playnite.SDK;
|
||||||
using Playnite.SDK.Events;
|
using Playnite.SDK.Events;
|
||||||
using Playnite.SDK.Models;
|
using Playnite.SDK.Models;
|
||||||
|
@ -21,9 +20,8 @@ namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||||
internal LANCommanderSettingsViewModel Settings { get; set; }
|
internal LANCommanderSettingsViewModel Settings { get; set; }
|
||||||
internal LANCommanderClient LANCommander { get; set; }
|
internal LANCommander.SDK.Client LANCommanderClient { get; set; }
|
||||||
internal PowerShellRuntime PowerShellRuntime { get; set; }
|
internal LANCommanderSaveController SaveController { get; set; }
|
||||||
internal GameSaveService GameSaveService { get; set; }
|
|
||||||
|
|
||||||
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
|
public override Guid Id { get; } = Guid.Parse("48e1bac7-e0a0-45d7-ba83-36f5e9e959fc");
|
||||||
public override string Name => "LANCommander";
|
public override string Name => "LANCommander";
|
||||||
|
@ -39,16 +37,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
Settings = new LANCommanderSettingsViewModel(this);
|
Settings = new LANCommanderSettingsViewModel(this);
|
||||||
|
|
||||||
LANCommander = new LANCommanderClient(Settings.ServerAddress);
|
LANCommanderClient = new SDK.Client(Settings.ServerAddress);
|
||||||
LANCommander.Token = new SDK.Models.AuthToken()
|
LANCommanderClient.UseToken(new SDK.Models.AuthToken()
|
||||||
{
|
{
|
||||||
AccessToken = Settings.AccessToken,
|
AccessToken = Settings.AccessToken,
|
||||||
RefreshToken = Settings.RefreshToken,
|
RefreshToken = Settings.RefreshToken,
|
||||||
};
|
});
|
||||||
|
|
||||||
PowerShellRuntime = new PowerShellRuntime();
|
// GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
||||||
|
|
||||||
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
|
|
||||||
|
|
||||||
api.UriHandler.RegisterSource("lancommander", args =>
|
api.UriHandler.RegisterSource("lancommander", args =>
|
||||||
{
|
{
|
||||||
|
@ -91,7 +87,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public bool ValidateConnection()
|
public bool ValidateConnection()
|
||||||
{
|
{
|
||||||
return LANCommander.ValidateToken(LANCommander.Token);
|
return LANCommanderClient.ValidateToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
|
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
|
||||||
|
@ -111,7 +107,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var games = LANCommander
|
var games = LANCommanderClient
|
||||||
.GetGames()
|
.GetGames()
|
||||||
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
|
||||||
|
|
||||||
|
@ -121,7 +117,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
|
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
|
||||||
|
|
||||||
var manifest = LANCommander.GetGameManifest(game.Id);
|
var manifest = LANCommanderClient.GetGameManifest(game.Id);
|
||||||
Logger.Trace("Successfully grabbed game manifest");
|
Logger.Trace("Successfully grabbed game manifest");
|
||||||
|
|
||||||
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
|
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
|
||||||
|
@ -130,7 +126,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
Logger.Trace("Game already exists in library, updating metadata...");
|
Logger.Trace("Game already exists in library, updating metadata...");
|
||||||
|
|
||||||
UpdateGame(manifest, game.Id);
|
UpdateGame(manifest);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -183,13 +179,13 @@ namespace LANCommander.PlaynitePlugin
|
||||||
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
||||||
|
|
||||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Icon))
|
||||||
metadata.Icon = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
metadata.Icon = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
|
||||||
|
|
||||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Cover))
|
||||||
metadata.CoverImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
metadata.CoverImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
|
||||||
|
|
||||||
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
|
if (game.Media.Any(m => m.Type == SDK.Enums.MediaType.Background))
|
||||||
metadata.BackgroundImage = new MetadataFile(LANCommander.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
metadata.BackgroundImage = new MetadataFile(LANCommanderClient.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
|
||||||
|
|
||||||
gameMetadata.Add(metadata);
|
gameMetadata.Add(metadata);
|
||||||
}
|
}
|
||||||
|
@ -224,9 +220,9 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
|
if (args.Games.Count == 1 && args.Games.First().IsInstalled && !String.IsNullOrWhiteSpace(args.Games.First().InstallDirectory))
|
||||||
{
|
{
|
||||||
var nameChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.NameChange);
|
var nameChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.NameChange);
|
||||||
var keyChangeScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.KeyChange);
|
var keyChangeScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange);
|
||||||
var installScriptPath = PowerShellRuntime.GetScriptFilePath(args.Games.First(), SDK.Enums.ScriptType.Install);
|
var installScriptPath = LANCommander.SDK.PowerShellRuntime.GetScriptFilePath(args.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install);
|
||||||
|
|
||||||
if (File.Exists(nameChangeScriptPath))
|
if (File.Exists(nameChangeScriptPath))
|
||||||
{
|
{
|
||||||
|
@ -243,8 +239,10 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
if (result.Result == true)
|
if (result.Result == true)
|
||||||
{
|
{
|
||||||
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
|
var game = nameChangeArgs.Games.First();
|
||||||
LANCommander.ChangeAlias(result.SelectedString);
|
|
||||||
|
LANCommander.SDK.PowerShellRuntime.RunScript(game.InstallDirectory, SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
|
||||||
|
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -264,12 +262,12 @@ namespace LANCommander.PlaynitePlugin
|
||||||
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
|
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
|
||||||
{
|
{
|
||||||
// NUKIEEEE
|
// NUKIEEEE
|
||||||
var newKey = LANCommander.GetNewKey(gameId);
|
var newKey = LANCommanderClient.GetNewKey(gameId);
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(newKey))
|
if (String.IsNullOrEmpty(newKey))
|
||||||
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
|
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
|
||||||
else
|
else
|
||||||
PowerShellRuntime.RunScript(keyChangeArgs.Games.First(), SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
LANCommander.SDK.PowerShellRuntime.RunScript(keyChangeArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.KeyChange, $@"""{newKey}""");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -292,7 +290,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
|
if (Guid.TryParse(installArgs.Games.First().GameId, out gameId))
|
||||||
{
|
{
|
||||||
PowerShellRuntime.RunScript(installArgs.Games.First(), SDK.Enums.ScriptType.Install);
|
LANCommander.SDK.PowerShellRuntime.RunScript(installArgs.Games.First().InstallDirectory, SDK.Enums.ScriptType.Install);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -334,12 +332,12 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public override void OnGameStarting(OnGameStartingEventArgs args)
|
public override void OnGameStarting(OnGameStartingEventArgs args)
|
||||||
{
|
{
|
||||||
GameSaveService.DownloadSave(args.Game);
|
SaveController.Download(args.Game);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnGameStopped(OnGameStoppedEventArgs args)
|
public override void OnGameStopped(OnGameStoppedEventArgs args)
|
||||||
{
|
{
|
||||||
GameSaveService.UploadSave(args.Game);
|
SaveController.Upload(args.Game);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<TopPanelItem> GetTopPanelItems()
|
public override IEnumerable<TopPanelItem> GetTopPanelItems()
|
||||||
|
@ -402,11 +400,11 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
|
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
|
||||||
|
|
||||||
LANCommander.ChangeAlias(result.SelectedString);
|
LANCommanderClient.ChangeAlias(result.SelectedString);
|
||||||
|
|
||||||
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
|
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");
|
||||||
|
|
||||||
PowerShellRuntime.RunScripts(games, SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
LANCommander.SDK.PowerShellRuntime.RunScripts(games.Select(g => g.InstallDirectory), SDK.Enums.ScriptType.NameChange, Settings.PlayerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -441,9 +439,9 @@ namespace LANCommander.PlaynitePlugin
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
|
public void UpdateGame(SDK.GameManifest manifest)
|
||||||
{
|
{
|
||||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
|
var game = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == manifest?.Id.ToString());
|
||||||
|
|
||||||
if (game == null)
|
if (game == null)
|
||||||
return;
|
return;
|
||||||
|
|
61
LANCommander.Playnite.Extension/SaveController.cs
Normal file
61
LANCommander.Playnite.Extension/SaveController.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,29 +12,25 @@ namespace LANCommander.PlaynitePlugin
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
public static readonly ILogger Logger = LogManager.GetLogger();
|
||||||
|
|
||||||
private LANCommanderLibraryPlugin Plugin;
|
private LANCommanderLibraryPlugin Plugin;
|
||||||
private PowerShellRuntime PowerShellRuntime;
|
|
||||||
|
|
||||||
public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
|
public LANCommanderUninstallController(LANCommanderLibraryPlugin plugin, Game game) : base(game)
|
||||||
{
|
{
|
||||||
Name = "Uninstall LANCommander Game";
|
Name = "Uninstall LANCommander Game";
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
PowerShellRuntime = new PowerShellRuntime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Uninstall(UninstallActionArgs args)
|
public override void Uninstall(UninstallActionArgs args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PowerShellRuntime.RunScript(Game, ScriptType.Uninstall);
|
var gameManager = new LANCommander.SDK.GameManager(Plugin.LANCommanderClient, Plugin.Settings.InstallDirectory);
|
||||||
|
|
||||||
|
gameManager.Uninstall(Game.InstallDirectory);
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
|
||||||
Logger.Trace("Attempting to delete install directory...");
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Game.InstallDirectory) && Directory.Exists(Game.InstallDirectory))
|
|
||||||
Directory.Delete(Game.InstallDirectory, true);
|
|
||||||
|
|
||||||
Logger.Trace("Deleted!");
|
|
||||||
|
|
||||||
InvokeOnUninstalled(new GameUninstalledEventArgs());
|
InvokeOnUninstalled(new GameUninstalledEventArgs());
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,24 +100,16 @@ namespace LANCommander.PlaynitePlugin.Views
|
||||||
LoginButton.Content = "Logging in...";
|
LoginButton.Content = "Logging in...";
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
|
if (Plugin.LANCommanderClient == null)
|
||||||
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
|
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||||
else
|
|
||||||
Plugin.LANCommander.Client.BaseUrl = new Uri(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.ServerAddress = Context.ServerAddress;
|
||||||
Plugin.Settings.AccessToken = response.AccessToken;
|
Plugin.Settings.AccessToken = response.AccessToken;
|
||||||
Plugin.Settings.RefreshToken = response.RefreshToken;
|
Plugin.Settings.RefreshToken = response.RefreshToken;
|
||||||
|
|
||||||
Plugin.LANCommander.Token = new AuthToken()
|
var profile = Plugin.LANCommanderClient.GetProfile();
|
||||||
{
|
|
||||||
AccessToken = response.AccessToken,
|
|
||||||
RefreshToken = response.RefreshToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
var profile = Plugin.LANCommander.GetProfile();
|
|
||||||
|
|
||||||
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
|
Plugin.Settings.PlayerName = String.IsNullOrWhiteSpace(profile.Alias) ? profile.UserName : profile.Alias;
|
||||||
|
|
||||||
|
@ -148,24 +140,16 @@ namespace LANCommander.PlaynitePlugin.Views
|
||||||
RegisterButton.IsEnabled = false;
|
RegisterButton.IsEnabled = false;
|
||||||
RegisterButton.Content = "Working...";
|
RegisterButton.Content = "Working...";
|
||||||
|
|
||||||
if (Plugin.LANCommander == null || Plugin.LANCommander.Client == null)
|
if (Plugin.LANCommanderClient == null)
|
||||||
Plugin.LANCommander = new LANCommanderClient(Context.ServerAddress);
|
Plugin.LANCommanderClient = new LANCommander.SDK.Client(Context.ServerAddress);
|
||||||
else
|
|
||||||
Plugin.LANCommander.Client.BaseUrl = new Uri(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.ServerAddress = Context.ServerAddress;
|
||||||
Plugin.Settings.AccessToken = response.AccessToken;
|
Plugin.Settings.AccessToken = response.AccessToken;
|
||||||
Plugin.Settings.RefreshToken = response.RefreshToken;
|
Plugin.Settings.RefreshToken = response.RefreshToken;
|
||||||
Plugin.Settings.PlayerName = Context.UserName;
|
Plugin.Settings.PlayerName = Context.UserName;
|
||||||
|
|
||||||
Plugin.LANCommander.Token = new AuthToken()
|
|
||||||
{
|
|
||||||
AccessToken = response.AccessToken,
|
|
||||||
RefreshToken = response.RefreshToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
Context.Password = String.Empty;
|
Context.Password = String.Empty;
|
||||||
|
|
||||||
Plugin.SavePluginSettings(Plugin.Settings);
|
Plugin.SavePluginSettings(Plugin.Settings);
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
RefreshToken = Settings.RefreshToken,
|
RefreshToken = Settings.RefreshToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
var task = Task.Run(() => Plugin.LANCommander.ValidateToken(token))
|
var task = Task.Run(() => Plugin.LANCommanderClient.ValidateToken(token))
|
||||||
.ContinueWith(antecedent =>
|
.ContinueWith(antecedent =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -90,7 +90,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
{
|
{
|
||||||
Plugin.Settings.AccessToken = String.Empty;
|
Plugin.Settings.AccessToken = String.Empty;
|
||||||
Plugin.Settings.RefreshToken = String.Empty;
|
Plugin.Settings.RefreshToken = String.Empty;
|
||||||
Plugin.LANCommander.Token = null;
|
Plugin.LANCommanderClient.UseToken(null);
|
||||||
|
|
||||||
Plugin.SavePluginSettings(Plugin.Settings);
|
Plugin.SavePluginSettings(Plugin.Settings);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
|
<package id="ByteSize" version="2.1.1" targetFramework="net462" />
|
||||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net462" />
|
<package id="Microsoft.Bcl.AsyncInterfaces" version="7.0.0" targetFramework="net462" />
|
||||||
<package id="NuGet.CommandLine" version="6.7.0" targetFramework="net462" developmentDependency="true" />
|
<package id="NuGet.CommandLine" version="6.7.0" targetFramework="net462" developmentDependency="true" />
|
||||||
<package id="PlayniteSDK" version="6.10.0" targetFramework="net462" />
|
<package id="PlayniteSDK" version="6.10.0" targetFramework="net462" />
|
||||||
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" />
|
<package id="PowerShellStandard.Library" version="5.1.1" targetFramework="net462" />
|
||||||
<package id="RestSharp" version="106.15.0" targetFramework="net462" />
|
|
||||||
<package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" />
|
<package id="rix0rrr.BeaconLib" version="1.0.2" targetFramework="net462" />
|
||||||
<package id="SharpCompress" version="0.34.1" targetFramework="net462" />
|
<package id="SharpCompress" version="0.34.1" targetFramework="net462" />
|
||||||
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
|
<package id="System.Buffers" version="4.5.1" targetFramework="net462" />
|
||||||
|
@ -16,6 +16,5 @@
|
||||||
<package id="System.Text.Json" version="7.0.3" targetFramework="net462" />
|
<package id="System.Text.Json" version="7.0.3" targetFramework="net462" />
|
||||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
|
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
|
||||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
|
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
|
||||||
<package id="YamlDotNet" version="5.4.0" targetFramework="net462" />
|
|
||||||
<package id="ZstdSharp.Port" version="0.7.2" targetFramework="net462" />
|
<package id="ZstdSharp.Port" version="0.7.2" targetFramework="net462" />
|
||||||
</packages>
|
</packages>
|
|
@ -1,6 +1,6 @@
|
||||||
using LANCommander.SDK;
|
using LANCommander.SDK;
|
||||||
using LANCommander.SDK.Models;
|
using LANCommander.SDK.Models;
|
||||||
using Playnite.SDK;
|
using Microsoft.Extensions.Logging;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -11,19 +11,27 @@ using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
internal class LANCommanderClient
|
public class Client
|
||||||
{
|
{
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
private readonly ILogger Logger;
|
||||||
|
|
||||||
public readonly RestClient Client;
|
private readonly RestClient ApiClient;
|
||||||
public AuthToken Token;
|
private AuthToken Token;
|
||||||
|
|
||||||
public LANCommanderClient(string baseUrl)
|
public Client(string baseUrl)
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrWhiteSpace(baseUrl))
|
if (!String.IsNullOrWhiteSpace(baseUrl))
|
||||||
Client = new RestClient(baseUrl);
|
ApiClient = new RestClient(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client(string baseUrl, ILogger logger)
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrWhiteSpace(baseUrl))
|
||||||
|
ApiClient = new RestClient(baseUrl);
|
||||||
|
|
||||||
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private T PostRequest<T>(string route, object body)
|
private T PostRequest<T>(string route, object body)
|
||||||
|
@ -32,7 +40,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
.AddJsonBody(body)
|
.AddJsonBody(body)
|
||||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||||
|
|
||||||
var response = Client.Post<T>(request);
|
var response = ApiClient.Post<T>(request);
|
||||||
|
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +50,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
var request = new RestRequest(route)
|
var request = new RestRequest(route)
|
||||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||||
|
|
||||||
var response = Client.Get<T>(request);
|
var response = ApiClient.Get<T>(request);
|
||||||
|
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +66,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
client.DownloadProgressChanged += (s, e) => progressHandler(e);
|
||||||
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
client.DownloadFileCompleted += (s, e) => completeHandler(e);
|
||||||
|
|
||||||
client.DownloadFileAsync(new Uri($"{Client.BaseUrl}{route}"), tempFile);
|
client.DownloadFileAsync(new Uri($"{ApiClient.BaseUrl}{route}"), tempFile);
|
||||||
|
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
@ -72,14 +80,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
client.Headers.Add("Authorization", $"Bearer {Token.AccessToken}");
|
||||||
|
|
||||||
var ws = client.OpenRead(new Uri($"{Client.BaseUrl}{route}"));
|
var ws = client.OpenRead(new Uri($"{ApiClient.BaseUrl}{route}"));
|
||||||
|
|
||||||
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
|
return new TrackableStream(ws, true, Convert.ToInt64(client.ResponseHeaders["Content-Length"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthResponse> AuthenticateAsync(string username, string password)
|
public async Task<AuthToken> AuthenticateAsync(string username, string password)
|
||||||
{
|
{
|
||||||
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
|
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/Auth", Method.POST).AddJsonBody(new AuthRequest()
|
||||||
{
|
{
|
||||||
UserName = username,
|
UserName = username,
|
||||||
Password = password
|
Password = password
|
||||||
|
@ -88,7 +96,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.OK:
|
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.Forbidden:
|
case HttpStatusCode.Forbidden:
|
||||||
case HttpStatusCode.BadRequest:
|
case HttpStatusCode.BadRequest:
|
||||||
|
@ -100,9 +115,9 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AuthResponse> RegisterAsync(string username, string password)
|
public async Task<AuthToken> RegisterAsync(string username, string password)
|
||||||
{
|
{
|
||||||
var response = await Client.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
|
var response = await ApiClient.ExecuteAsync<AuthResponse>(new RestRequest("/api/auth/register", Method.POST).AddJsonBody(new AuthRequest()
|
||||||
{
|
{
|
||||||
UserName = username,
|
UserName = username,
|
||||||
Password = password
|
Password = password
|
||||||
|
@ -111,7 +126,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
{
|
{
|
||||||
case HttpStatusCode.OK:
|
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.BadRequest:
|
||||||
case HttpStatusCode.Forbidden:
|
case HttpStatusCode.Forbidden:
|
||||||
|
@ -125,33 +147,45 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public async Task<bool> PingAsync()
|
public async Task<bool> PingAsync()
|
||||||
{
|
{
|
||||||
var response = await Client.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
|
var response = await ApiClient.ExecuteAsync(new RestRequest("/api/Ping", Method.GET));
|
||||||
|
|
||||||
return response.StatusCode == HttpStatusCode.OK;
|
return response.StatusCode == HttpStatusCode.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthResponse RefreshToken(AuthToken token)
|
public AuthToken RefreshToken(AuthToken token)
|
||||||
{
|
{
|
||||||
Logger.Trace("Refreshing token...");
|
Logger?.LogTrace("Refreshing token...");
|
||||||
|
|
||||||
var request = new RestRequest("/api/Auth/Refresh")
|
var request = new RestRequest("/api/Auth/Refresh")
|
||||||
.AddJsonBody(token);
|
.AddJsonBody(token);
|
||||||
|
|
||||||
var response = Client.Post<AuthResponse>(request);
|
var response = ApiClient.Post<AuthResponse>(request);
|
||||||
|
|
||||||
if (response.StatusCode != HttpStatusCode.OK)
|
if (response.StatusCode != HttpStatusCode.OK)
|
||||||
throw new WebException(response.ErrorMessage);
|
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()
|
||||||
|
{
|
||||||
|
return ValidateToken(Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValidateToken(AuthToken token)
|
public bool ValidateToken(AuthToken token)
|
||||||
{
|
{
|
||||||
Logger.Trace("Validating token...");
|
Logger?.LogTrace("Validating token...");
|
||||||
|
|
||||||
if (token == null)
|
if (token == null)
|
||||||
{
|
{
|
||||||
Logger.Trace("Token is null!");
|
Logger?.LogTrace("Token is null!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,22 +194,27 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
|
||||||
{
|
{
|
||||||
Logger.Trace("Token is empty!");
|
Logger?.LogTrace("Token is empty!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = Client.Post(request);
|
var response = ApiClient.Post(request);
|
||||||
|
|
||||||
var valid = response.StatusCode == HttpStatusCode.OK;
|
var valid = response.StatusCode == HttpStatusCode.OK;
|
||||||
|
|
||||||
if (valid)
|
if (valid)
|
||||||
Logger.Trace("Token is valid!");
|
Logger?.LogTrace("Token is valid!");
|
||||||
else
|
else
|
||||||
Logger.Trace("Token is invalid!");
|
Logger?.LogTrace("Token is invalid!");
|
||||||
|
|
||||||
return response.StatusCode == HttpStatusCode.OK;
|
return response.StatusCode == HttpStatusCode.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UseToken(AuthToken token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<Game> GetGames()
|
public IEnumerable<Game> GetGames()
|
||||||
{
|
{
|
||||||
return GetRequest<IEnumerable<Game>>("/api/Games");
|
return GetRequest<IEnumerable<Game>>("/api/Games");
|
||||||
|
@ -223,26 +262,26 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public GameSave UploadSave(string gameId, byte[] data)
|
public GameSave UploadSave(string gameId, byte[] data)
|
||||||
{
|
{
|
||||||
Logger.Trace("Uploading save...");
|
Logger?.LogTrace("Uploading save...");
|
||||||
|
|
||||||
var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST)
|
var request = new RestRequest($"/api/Saves/Upload/{gameId}", Method.POST)
|
||||||
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
|
||||||
|
|
||||||
request.AddFile(gameId, data, gameId);
|
request.AddFile(gameId, data, gameId);
|
||||||
|
|
||||||
var response = Client.Post<GameSave>(request);
|
var response = ApiClient.Post<GameSave>(request);
|
||||||
|
|
||||||
return response.Data;
|
return response.Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetMediaUrl(Media media)
|
public string GetMediaUrl(Media media)
|
||||||
{
|
{
|
||||||
return (new Uri(Client.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
|
return (new Uri(ApiClient.BaseUrl, $"/api/Media/{media.Id}/Download?fileId={media.FileId}").ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetKey(Guid id)
|
public string GetKey(Guid id)
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting key allocation...");
|
Logger?.LogTrace("Requesting key allocation...");
|
||||||
|
|
||||||
var macAddress = GetMacAddress();
|
var macAddress = GetMacAddress();
|
||||||
|
|
||||||
|
@ -261,7 +300,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public string GetAllocatedKey(Guid id)
|
public string GetAllocatedKey(Guid id)
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting allocated key...");
|
Logger?.LogTrace("Requesting allocated key...");
|
||||||
|
|
||||||
var macAddress = GetMacAddress();
|
var macAddress = GetMacAddress();
|
||||||
|
|
||||||
|
@ -283,7 +322,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public string GetNewKey(Guid id)
|
public string GetNewKey(Guid id)
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting new key allocation...");
|
Logger?.LogTrace("Requesting new key allocation...");
|
||||||
|
|
||||||
var macAddress = GetMacAddress();
|
var macAddress = GetMacAddress();
|
||||||
|
|
||||||
|
@ -305,14 +344,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
public User GetProfile()
|
public User GetProfile()
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting player's profile...");
|
Logger?.LogTrace("Requesting player's profile...");
|
||||||
|
|
||||||
return GetRequest<User>("/api/Profile");
|
return GetRequest<User>("/api/Profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ChangeAlias(string alias)
|
public string ChangeAlias(string alias)
|
||||||
{
|
{
|
||||||
Logger.Trace("Requesting to change player alias...");
|
Logger?.LogTrace("Requesting to change player alias...");
|
||||||
|
|
||||||
var response = PostRequest<object>("/api/Profile/ChangeAlias", alias);
|
var response = PostRequest<object>("/api/Profile/ChangeAlias", alias);
|
||||||
|
|
20
LANCommander.SDK/EventArgs.cs
Normal file
20
LANCommander.SDK/EventArgs.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Readers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK
|
||||||
|
{
|
||||||
|
public class ArchiveExtractionProgressArgs : EventArgs
|
||||||
|
{
|
||||||
|
public long Position { get; set; }
|
||||||
|
public long Length { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ArchiveEntryExtractionProgressArgs : EventArgs
|
||||||
|
{
|
||||||
|
public ReaderProgress Progress { get; set; }
|
||||||
|
public IEntry Entry { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
internal class ExtractionResult
|
internal class ExtractionResult
|
||||||
{
|
{
|
239
LANCommander.SDK/GameManager.cs
Normal file
239
LANCommander.SDK/GameManager.cs
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
using LANCommander.SDK.Enums;
|
||||||
|
using LANCommander.SDK.Extensions;
|
||||||
|
using LANCommander.SDK.Helpers;
|
||||||
|
using LANCommander.SDK.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Readers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK
|
||||||
|
{
|
||||||
|
public class GameManager
|
||||||
|
{
|
||||||
|
private readonly ILogger Logger;
|
||||||
|
private Client Client { get; set; }
|
||||||
|
private string DefaultInstallDirectory { get; set; }
|
||||||
|
|
||||||
|
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||||
|
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||||
|
|
||||||
|
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||||
|
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||||
|
|
||||||
|
private TrackableStream Stream;
|
||||||
|
private IReader Reader;
|
||||||
|
|
||||||
|
public GameManager(Client client, string defaultInstallDirectory)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
DefaultInstallDirectory = defaultInstallDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameManager(Client client, string defaultInstallDirectory, ILogger logger)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
DefaultInstallDirectory = DefaultInstallDirectory;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads, extracts, and runs post-install scripts for the specified game
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="game">Game to install</param>
|
||||||
|
/// <param name="maxAttempts">Maximum attempts in case of transmission error</param>
|
||||||
|
/// <returns>Final install path</returns>
|
||||||
|
/// <exception cref="Exception"></exception>
|
||||||
|
public string Install(Guid gameId, int maxAttempts = 10)
|
||||||
|
{
|
||||||
|
var game = Client.GetGame(gameId);
|
||||||
|
|
||||||
|
Logger?.LogTrace("Installing game {GameTitle} (GameId)", game.Title, game.Id);
|
||||||
|
|
||||||
|
var result = RetryHelper.RetryOnException<ExtractionResult>(maxAttempts, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Attempting to download and extract game");
|
||||||
|
|
||||||
|
return DownloadAndExtract(game, DefaultInstallDirectory);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.Success && !result.Canceled)
|
||||||
|
throw new Exception("Could not extract the installer. Retry the install or check your connection");
|
||||||
|
else if (result.Canceled)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
GameManifest manifest = null;
|
||||||
|
|
||||||
|
game.InstallDirectory = result.Directory;
|
||||||
|
|
||||||
|
var writeManifestSuccess = RetryHelper.RetryOnException(maxAttempts, TimeSpan.FromSeconds(1), false, () =>
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Attempting to get game manifest");
|
||||||
|
|
||||||
|
manifest = Client.GetGameManifest(game.Id);
|
||||||
|
|
||||||
|
ManifestHelper.Write(manifest, game.InstallDirectory);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!writeManifestSuccess)
|
||||||
|
throw new Exception("Could not grab the manifest file. Retry the install or check your connection");
|
||||||
|
|
||||||
|
Logger?.LogTrace("Saving scripts");
|
||||||
|
|
||||||
|
ScriptHelper.SaveScript(game, ScriptType.Install);
|
||||||
|
ScriptHelper.SaveScript(game, ScriptType.Uninstall);
|
||||||
|
ScriptHelper.SaveScript(game, ScriptType.NameChange);
|
||||||
|
ScriptHelper.SaveScript(game, ScriptType.KeyChange);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PowerShellRuntime.RunScript(game, ScriptType.Install);
|
||||||
|
PowerShellRuntime.RunScript(game, ScriptType.NameChange, /* Plugin.Settings.PlayerName */ "");
|
||||||
|
|
||||||
|
var key = Client.GetAllocatedKey(game.Id);
|
||||||
|
|
||||||
|
PowerShellRuntime.RunScript(game, ScriptType.KeyChange, $"\"{key}\"");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger?.LogError(ex, "Could not execute post-install scripts");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Uninstall(string installDirectory)
|
||||||
|
{
|
||||||
|
var manifest = ManifestHelper.Read(installDirectory);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Running uninstall script");
|
||||||
|
PowerShellRuntime.RunScript(installDirectory, ScriptType.Uninstall);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger?.LogError(ex, "Error running uninstall script");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger?.LogTrace("Attempting to delete the install directory");
|
||||||
|
|
||||||
|
if (Directory.Exists(installDirectory))
|
||||||
|
Directory.Delete(installDirectory, true);
|
||||||
|
|
||||||
|
Logger?.LogTrace("Deleted install directory {InstallDirectory}", installDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtractionResult DownloadAndExtract(Game game, string installDirectory = "")
|
||||||
|
{
|
||||||
|
if (game == null)
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Game failed to download, no game was specified");
|
||||||
|
|
||||||
|
throw new ArgumentNullException("No game was specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(installDirectory))
|
||||||
|
installDirectory = DefaultInstallDirectory;
|
||||||
|
|
||||||
|
var destination = Path.Combine(installDirectory, game.Title.SanitizeFilename());
|
||||||
|
|
||||||
|
Logger?.LogTrace("Downloading and extracting {Game} to path {Destination}", game.Title, destination);
|
||||||
|
|
||||||
|
var extractionResult = new ExtractionResult
|
||||||
|
{
|
||||||
|
Canceled = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destination);
|
||||||
|
|
||||||
|
Stream = Client.StreamGame(game.Id);
|
||||||
|
Reader = ReaderFactory.Open(Stream);
|
||||||
|
|
||||||
|
Stream.OnProgress += (pos, len) =>
|
||||||
|
{
|
||||||
|
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
Reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||||
|
{
|
||||||
|
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||||
|
{
|
||||||
|
Entry = e.Item,
|
||||||
|
Progress = e.ReaderProgress,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
while (Reader.MoveToNextEntry())
|
||||||
|
{
|
||||||
|
if (Reader.Cancelled)
|
||||||
|
break;
|
||||||
|
|
||||||
|
Reader.WriteEntryToDirectory(destination, new ExtractionOptions()
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true,
|
||||||
|
PreserveFileTime = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader.Dispose();
|
||||||
|
Stream.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (Reader.Cancelled)
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("User cancelled the download");
|
||||||
|
|
||||||
|
extractionResult.Canceled = true;
|
||||||
|
|
||||||
|
if (Directory.Exists(destination))
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Cleaning up orphaned files after cancelled install");
|
||||||
|
|
||||||
|
Directory.Delete(destination, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||||
|
|
||||||
|
if (Directory.Exists(destination))
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Cleaning up orphaned install files after bad install");
|
||||||
|
|
||||||
|
Directory.Delete(destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("The game archive could not be extracted, is it corrupted? Please try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extractionResult.Canceled)
|
||||||
|
{
|
||||||
|
extractionResult.Success = true;
|
||||||
|
extractionResult.Directory = destination;
|
||||||
|
|
||||||
|
Logger?.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelInstall()
|
||||||
|
{
|
||||||
|
Reader?.Cancel();
|
||||||
|
// Reader?.Dispose();
|
||||||
|
// Stream?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +1,54 @@
|
||||||
using LANCommander.SDK;
|
using LANCommander.SDK;
|
||||||
using Playnite.SDK;
|
using LANCommander.SDK.Helpers;
|
||||||
using Playnite.SDK.Models;
|
using LANCommander.SDK.Models;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Readers;
|
using SharpCompress.Readers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
using YamlDotNet.Serialization.NamingConventions;
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin.Services
|
namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
internal class GameSaveService
|
public class GameSaveManager
|
||||||
{
|
{
|
||||||
private readonly LANCommanderClient LANCommander;
|
private readonly Client Client;
|
||||||
private readonly IPlayniteAPI PlayniteApi;
|
|
||||||
private readonly PowerShellRuntime PowerShellRuntime;
|
|
||||||
|
|
||||||
internal GameSaveService(LANCommanderClient lanCommander, IPlayniteAPI playniteApi, PowerShellRuntime powerShellRuntime)
|
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;
|
Client = client;
|
||||||
PlayniteApi = playniteApi;
|
|
||||||
PowerShellRuntime = powerShellRuntime;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DownloadSave(Game game)
|
public void Download(string installDirectory)
|
||||||
{
|
{
|
||||||
|
var manifest = ManifestHelper.Read(installDirectory);
|
||||||
|
|
||||||
string tempFile = String.Empty;
|
string tempFile = String.Empty;
|
||||||
|
|
||||||
if (game != null)
|
if (manifest != null)
|
||||||
{
|
{
|
||||||
PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
var destination = Client.DownloadLatestSave(manifest.Id, (changed) =>
|
||||||
{
|
{
|
||||||
progress.ProgressMaxValue = 100;
|
OnDownloadProgress?.Invoke(changed);
|
||||||
progress.CurrentProgressValue = 0;
|
|
||||||
|
|
||||||
var destination = LANCommander.DownloadLatestSave(Guid.Parse(game.GameId), (changed) =>
|
|
||||||
{
|
|
||||||
progress.CurrentProgressValue = changed.ProgressPercentage;
|
|
||||||
}, (complete) =>
|
}, (complete) =>
|
||||||
{
|
{
|
||||||
progress.CurrentProgressValue = 100;
|
OnDownloadComplete?.Invoke(complete);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Lock the thread until download is done
|
|
||||||
while (progress.CurrentProgressValue != 100)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFile = destination;
|
tempFile = destination;
|
||||||
},
|
|
||||||
new GlobalProgressOptions("Downloading latest save...")
|
|
||||||
{
|
|
||||||
IsIndeterminate = false,
|
|
||||||
Cancelable = false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Go into the archive and extract the files to the correct locations
|
// Go into the archive and extract the files to the correct locations
|
||||||
try
|
try
|
||||||
|
@ -74,10 +63,6 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var manifestContents = File.ReadAllText(Path.Combine(tempLocation, "_manifest.yml"));
|
|
||||||
|
|
||||||
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
|
|
||||||
|
|
||||||
#region Move files
|
#region Move files
|
||||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||||
{
|
{
|
||||||
|
@ -86,7 +71,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
|
|
||||||
var tempSavePathFile = Path.Combine(tempSavePath, savePath.Path.Replace('/', '\\').Replace("{InstallDir}\\", ""));
|
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))
|
if (File.Exists(tempSavePathFile))
|
||||||
{
|
{
|
||||||
|
@ -107,7 +92,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
if (inInstallDir)
|
if (inInstallDir)
|
||||||
{
|
{
|
||||||
// Files are in the game's install directory. Move them there from the save path.
|
// 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))
|
if (File.Exists(destination))
|
||||||
File.Delete(destination);
|
File.Delete(destination);
|
||||||
|
@ -155,17 +140,10 @@ 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 deserializer = new DeserializerBuilder()
|
|
||||||
.WithNamingConvention(new PascalCaseNamingConvention())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var manifest = deserializer.Deserialize<GameManifest>(File.ReadAllText(manifestPath));
|
|
||||||
var temp = Path.GetTempFileName();
|
var temp = Path.GetTempFileName();
|
||||||
|
|
||||||
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
|
if (manifest.SavePaths != null && manifest.SavePaths.Count() > 0)
|
||||||
|
@ -177,7 +155,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
#region Add files from defined paths
|
#region Add files from defined paths
|
||||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||||
{
|
{
|
||||||
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
|
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory));
|
||||||
|
|
||||||
if (Directory.Exists(localPath))
|
if (Directory.Exists(localPath))
|
||||||
{
|
{
|
||||||
|
@ -193,7 +171,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
#region Add files from defined paths
|
#region Add files from defined paths
|
||||||
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == "File"))
|
||||||
{
|
{
|
||||||
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", game.InstallDirectory));
|
var localPath = Environment.ExpandEnvironmentVariables(savePath.Path.Replace('/', '\\').Replace("{InstallDir}", installDirectory));
|
||||||
|
|
||||||
if (Directory.Exists(localPath))
|
if (Directory.Exists(localPath))
|
||||||
{
|
{
|
||||||
|
@ -235,7 +213,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
archive.AddEntry("_manifest.yml", manifestPath);
|
archive.AddEntry("_manifest.yml", ManifestHelper.GetPath(installDirectory));
|
||||||
|
|
||||||
using (var ms = new MemoryStream())
|
using (var ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
|
@ -243,8 +221,7 @@ namespace LANCommander.PlaynitePlugin.Services
|
||||||
|
|
||||||
ms.Seek(0, SeekOrigin.Begin);
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
var save = LANCommander.UploadSave(game.GameId, ms.ToArray());
|
var save = Client.UploadSave(manifest.Id.ToString(), ms.ToArray());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
57
LANCommander.SDK/Helpers/ManifestHelper.cs
Normal file
57
LANCommander.SDK/Helpers/ManifestHelper.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK.Helpers
|
||||||
|
{
|
||||||
|
public static class ManifestHelper
|
||||||
|
{
|
||||||
|
public static readonly ILogger Logger;
|
||||||
|
|
||||||
|
public const string ManifestFilename = "_manifest.yml";
|
||||||
|
|
||||||
|
public static GameManifest Read(string installDirectory)
|
||||||
|
{
|
||||||
|
var source = GetPath(installDirectory);
|
||||||
|
var yaml = File.ReadAllText(source);
|
||||||
|
|
||||||
|
var deserializer = new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Logger?.LogTrace("Deserializing manifest");
|
||||||
|
|
||||||
|
var manifest = deserializer.Deserialize<GameManifest>(yaml);
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(GameManifest manifest, string installDirectory)
|
||||||
|
{
|
||||||
|
var destination = GetPath(installDirectory);
|
||||||
|
|
||||||
|
Logger?.LogTrace("Attempting to write manifest to path {Destination}", destination);
|
||||||
|
|
||||||
|
var serializer = new SerializerBuilder()
|
||||||
|
.WithNamingConvention(new PascalCaseNamingConvention())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
Logger?.LogTrace("Serializing manifest");
|
||||||
|
|
||||||
|
var yaml = serializer.Serialize(manifest);
|
||||||
|
|
||||||
|
Logger?.LogTrace("Writing manifest file");
|
||||||
|
|
||||||
|
File.WriteAllText(destination, yaml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetPath(string installDirectory)
|
||||||
|
{
|
||||||
|
return Path.Combine(installDirectory, ManifestFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,12 @@
|
||||||
using Playnite.SDK;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin.Helpers
|
namespace LANCommander.SDK.Helpers
|
||||||
{
|
{
|
||||||
internal static class RetryHelper
|
internal static class RetryHelper
|
||||||
{
|
{
|
||||||
internal static readonly ILogger Logger = LogManager.GetLogger();
|
internal static readonly ILogger Logger;
|
||||||
|
|
||||||
internal static T RetryOnException<T>(int maxAttempts, TimeSpan delay, T @default, Func<T> action)
|
internal static T RetryOnException<T>(int maxAttempts, TimeSpan delay, T @default, Func<T> action)
|
||||||
{
|
{
|
||||||
|
@ -19,14 +16,14 @@ namespace LANCommander.PlaynitePlugin.Helpers
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.Trace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
Logger?.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
|
||||||
|
|
||||||
attempts++;
|
attempts++;
|
||||||
return action();
|
return action();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error(ex, $"Attempt failed!");
|
Logger?.LogError(ex, $"Attempt failed!");
|
||||||
|
|
||||||
if (attempts >= maxAttempts)
|
if (attempts >= maxAttempts)
|
||||||
return @default;
|
return @default;
|
50
LANCommander.SDK/Helpers/ScriptHelper.cs
Normal file
50
LANCommander.SDK/Helpers/ScriptHelper.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using LANCommander.SDK.Enums;
|
||||||
|
using LANCommander.SDK.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK.Helpers
|
||||||
|
{
|
||||||
|
public static class ScriptHelper
|
||||||
|
{
|
||||||
|
public static readonly ILogger Logger;
|
||||||
|
|
||||||
|
public static string SaveTempScript(Script script)
|
||||||
|
{
|
||||||
|
var tempPath = Path.GetTempFileName();
|
||||||
|
|
||||||
|
// PowerShell will only run scripts with the .ps1 file extension
|
||||||
|
File.Move(tempPath, tempPath + ".ps1");
|
||||||
|
|
||||||
|
Logger?.LogTrace("Writing script {Script} to {Destination}", script.Name, tempPath);
|
||||||
|
|
||||||
|
File.WriteAllText(tempPath, script.Contents);
|
||||||
|
|
||||||
|
return tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SaveScript(Game game, ScriptType type)
|
||||||
|
{
|
||||||
|
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||||
|
|
||||||
|
if (script == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (script.RequiresAdmin)
|
||||||
|
script.Contents = "# Requires Admin" + "\r\n\r\n" + script.Contents;
|
||||||
|
|
||||||
|
var filename = PowerShellRuntime.GetScriptFilePath(game, type);
|
||||||
|
|
||||||
|
if (File.Exists(filename))
|
||||||
|
File.Delete(filename);
|
||||||
|
|
||||||
|
Logger?.LogTrace("Writing {ScriptType} script to {Destination}", type, filename);
|
||||||
|
|
||||||
|
File.WriteAllText(filename, script.Contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,14 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<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="5.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -8,5 +8,6 @@ namespace LANCommander.SDK.Models
|
||||||
{
|
{
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
|
public DateTime Expiration { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace LANCommander.SDK.Models
|
||||||
public string DirectoryName { get; set; }
|
public string DirectoryName { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public DateTime ReleasedOn { get; set; }
|
public DateTime ReleasedOn { get; set; }
|
||||||
|
public string InstallDirectory { get; set; }
|
||||||
public virtual IEnumerable<Action> Actions { get; set; }
|
public virtual IEnumerable<Action> Actions { get; set; }
|
||||||
public virtual IEnumerable<Tag> Tags { get; set; }
|
public virtual IEnumerable<Tag> Tags { get; set; }
|
||||||
public virtual Company Publisher { get; set; }
|
public virtual Company Publisher { get; set; }
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
public class GameManifest
|
public class GameManifest
|
||||||
{
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string SortTitle { get; set; }
|
public string SortTitle { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
using LANCommander.SDK.Enums;
|
using LANCommander.SDK.Enums;
|
||||||
using Playnite.SDK;
|
using LANCommander.SDK.Models;
|
||||||
using Playnite.SDK.Models;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management.Automation;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.RightsManagement;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
internal class PowerShellRuntime
|
public static class PowerShellRuntime
|
||||||
{
|
{
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger();
|
public static readonly ILogger Logger;
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
|
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
|
||||||
|
@ -24,13 +22,13 @@ namespace LANCommander.PlaynitePlugin
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr);
|
static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr);
|
||||||
|
|
||||||
public void RunCommand(string command, bool asAdmin = false)
|
public static void RunCommand(string command, bool asAdmin = false)
|
||||||
{
|
{
|
||||||
Logger.Trace($"Executing command `{command}` | Admin: {asAdmin}");
|
Logger?.LogTrace($"Executing command `{command}` | Admin: {asAdmin}");
|
||||||
|
|
||||||
var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1");
|
var tempScript = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".ps1");
|
||||||
|
|
||||||
Logger.Trace($"Creating temp script at path {tempScript}");
|
Logger?.LogTrace($"Creating temp script at path {tempScript}");
|
||||||
|
|
||||||
File.WriteAllText(tempScript, command);
|
File.WriteAllText(tempScript, command);
|
||||||
|
|
||||||
|
@ -39,9 +37,9 @@ namespace LANCommander.PlaynitePlugin
|
||||||
File.Delete(tempScript);
|
File.Delete(tempScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null)
|
public static int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null)
|
||||||
{
|
{
|
||||||
Logger.Trace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}");
|
Logger?.LogTrace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}");
|
||||||
|
|
||||||
var wow64Value = IntPtr.Zero;
|
var wow64Value = IntPtr.Zero;
|
||||||
|
|
||||||
|
@ -75,9 +73,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
return process.ExitCode;
|
return process.ExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunScript(Game game, ScriptType type, string arguments = null)
|
public static void RunScript(Game game, ScriptType type, string arguments = null)
|
||||||
{
|
{
|
||||||
var path = GetScriptFilePath(game, type);
|
RunScript(game.InstallDirectory, type, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunScript(string installDirectory, ScriptType type, string arguments = null)
|
||||||
|
{
|
||||||
|
var path = GetScriptFilePath(installDirectory, type);
|
||||||
|
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
|
@ -90,12 +93,12 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunScriptsAsAdmin(IEnumerable<string> paths, string arguments = null)
|
public static void RunScriptsAsAdmin(IEnumerable<string> paths, string arguments = null)
|
||||||
{
|
{
|
||||||
// Concatenate scripts
|
// Concatenate scripts
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
Logger.Trace("Concatenating scripts...");
|
Logger?.LogTrace("Concatenating scripts...");
|
||||||
|
|
||||||
foreach (var path in paths)
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
|
@ -103,16 +106,16 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
sb.AppendLine(contents);
|
sb.AppendLine(contents);
|
||||||
|
|
||||||
Logger.Trace($"Added {path}!");
|
Logger?.LogTrace($"Added {path}!");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Trace("Done concatenating!");
|
Logger?.LogTrace("Done concatenating!");
|
||||||
|
|
||||||
if (sb.Length > 0)
|
if (sb.Length > 0)
|
||||||
{
|
{
|
||||||
var scriptPath = Path.GetTempFileName();
|
var scriptPath = Path.GetTempFileName();
|
||||||
|
|
||||||
Logger.Trace($"Creating temp script at path {scriptPath}");
|
Logger?.LogTrace($"Creating temp script at path {scriptPath}");
|
||||||
|
|
||||||
File.WriteAllText(scriptPath, sb.ToString());
|
File.WriteAllText(scriptPath, sb.ToString());
|
||||||
|
|
||||||
|
@ -120,14 +123,14 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunScripts(IEnumerable<Game> games, ScriptType type, string arguments = null)
|
public static void RunScripts(IEnumerable<string> installDirectories, ScriptType type, string arguments = null)
|
||||||
{
|
{
|
||||||
List<string> scripts = new List<string>();
|
List<string> scripts = new List<string>();
|
||||||
List<string> adminScripts = new List<string>();
|
List<string> adminScripts = new List<string>();
|
||||||
|
|
||||||
foreach (var game in games)
|
foreach (var installDirectory in installDirectories)
|
||||||
{
|
{
|
||||||
var path = GetScriptFilePath(game, type);
|
var path = GetScriptFilePath(installDirectory, type);
|
||||||
|
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
continue;
|
continue;
|
||||||
|
@ -149,6 +152,11 @@ namespace LANCommander.PlaynitePlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetScriptFilePath(Game game, ScriptType type)
|
public static string GetScriptFilePath(Game game, ScriptType type)
|
||||||
|
{
|
||||||
|
return GetScriptFilePath(game.InstallDirectory, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetScriptFilePath(string installDirectory, ScriptType type)
|
||||||
{
|
{
|
||||||
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
|
Dictionary<ScriptType, string> filenames = new Dictionary<ScriptType, string>() {
|
||||||
{ ScriptType.Install, "_install.ps1" },
|
{ ScriptType.Install, "_install.ps1" },
|
||||||
|
@ -159,7 +167,7 @@ namespace LANCommander.PlaynitePlugin
|
||||||
|
|
||||||
var filename = filenames[type];
|
var filename = filenames[type];
|
||||||
|
|
||||||
return Path.Combine(game.InstallDirectory, filename);
|
return Path.Combine(installDirectory, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
168
LANCommander.SDK/RedistributableManager.cs
Normal file
168
LANCommander.SDK/RedistributableManager.cs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
using LANCommander.SDK.Enums;
|
||||||
|
using LANCommander.SDK.Extensions;
|
||||||
|
using LANCommander.SDK.Helpers;
|
||||||
|
using LANCommander.SDK.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
using SharpCompress.Readers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace LANCommander.SDK
|
||||||
|
{
|
||||||
|
public class RedistributableManager
|
||||||
|
{
|
||||||
|
private readonly ILogger Logger;
|
||||||
|
private Client Client { get; set; }
|
||||||
|
|
||||||
|
public delegate void OnArchiveEntryExtractionProgressHandler(object sender, ArchiveEntryExtractionProgressArgs e);
|
||||||
|
public event OnArchiveEntryExtractionProgressHandler OnArchiveEntryExtractionProgress;
|
||||||
|
|
||||||
|
public delegate void OnArchiveExtractionProgressHandler(long position, long length);
|
||||||
|
public event OnArchiveExtractionProgressHandler OnArchiveExtractionProgress;
|
||||||
|
|
||||||
|
public RedistributableManager(Client client)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RedistributableManager(Client client, ILogger logger)
|
||||||
|
{
|
||||||
|
Client = client;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Install(Game game)
|
||||||
|
{
|
||||||
|
foreach (var redistributable in game.Redistributables)
|
||||||
|
{
|
||||||
|
Install(redistributable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Install(Redistributable redistributable)
|
||||||
|
{
|
||||||
|
string installScriptTempFile = null;
|
||||||
|
string detectionScriptTempFile = null;
|
||||||
|
string extractTempPath = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||||
|
installScriptTempFile = ScriptHelper.SaveTempScript(installScript);
|
||||||
|
|
||||||
|
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||||
|
detectionScriptTempFile = ScriptHelper.SaveTempScript(detectionScript);
|
||||||
|
|
||||||
|
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
||||||
|
|
||||||
|
// Redistributable is not installed
|
||||||
|
if (detectionResult == 0)
|
||||||
|
{
|
||||||
|
if (redistributable.Archives.Count() > 0)
|
||||||
|
{
|
||||||
|
var extractionResult = DownloadAndExtract(redistributable);
|
||||||
|
|
||||||
|
if (extractionResult.Success)
|
||||||
|
{
|
||||||
|
extractTempPath = extractionResult.Directory;
|
||||||
|
|
||||||
|
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger?.LogError(ex, "Redistributable {Redistributable} failed to install", redistributable.Name);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(installScriptTempFile))
|
||||||
|
File.Delete(installScriptTempFile);
|
||||||
|
|
||||||
|
if (File.Exists(detectionScriptTempFile))
|
||||||
|
File.Delete(detectionScriptTempFile);
|
||||||
|
|
||||||
|
if (Directory.Exists(extractTempPath))
|
||||||
|
Directory.Delete(extractTempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtractionResult DownloadAndExtract(Redistributable redistributable)
|
||||||
|
{
|
||||||
|
if (redistributable == null)
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Redistributable failed to download! No redistributable was specified");
|
||||||
|
throw new ArgumentNullException("No redistributable was specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||||
|
|
||||||
|
Logger?.LogTrace("Downloading and extracting {Redistributable} to path {Destination}", redistributable.Name, destination);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(destination);
|
||||||
|
|
||||||
|
using (var redistributableStream = Client.StreamRedistributable(redistributable.Id))
|
||||||
|
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||||
|
{
|
||||||
|
redistributableStream.OnProgress += (pos, len) =>
|
||||||
|
{
|
||||||
|
OnArchiveExtractionProgress?.Invoke(pos, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||||
|
{
|
||||||
|
OnArchiveEntryExtractionProgress?.Invoke(this, new ArchiveEntryExtractionProgressArgs
|
||||||
|
{
|
||||||
|
Entry = e.Item,
|
||||||
|
Progress = e.ReaderProgress,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger?.LogError(ex, "Could not extract to path {Destination}", destination);
|
||||||
|
|
||||||
|
if (Directory.Exists(destination))
|
||||||
|
{
|
||||||
|
Logger?.LogTrace("Cleaning up orphaned files after bad install");
|
||||||
|
|
||||||
|
Directory.Delete(destination, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("The redistributable archive could not be extracted, is it corrupted? Please try again");
|
||||||
|
}
|
||||||
|
|
||||||
|
var extractionResult = new ExtractionResult
|
||||||
|
{
|
||||||
|
Canceled = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!extractionResult.Canceled)
|
||||||
|
{
|
||||||
|
extractionResult.Success = true;
|
||||||
|
extractionResult.Directory = destination;
|
||||||
|
Logger?.LogTrace("Redistributable {Redistributable} successfully downloaded and extracted to {Destination}", redistributable.Name, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractionResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace LANCommander.PlaynitePlugin
|
namespace LANCommander.SDK
|
||||||
{
|
{
|
||||||
internal class TrackableStream : MemoryStream, IDisposable
|
public class TrackableStream : MemoryStream, IDisposable
|
||||||
{
|
{
|
||||||
public delegate void OnProgressDelegate(long Position, long Length);
|
public delegate void OnProgressDelegate(long Position, long Length);
|
||||||
public event OnProgressDelegate OnProgress = delegate { };
|
public event OnProgressDelegate OnProgress = delegate { };
|
|
@ -44,6 +44,7 @@ namespace LANCommander.Services
|
||||||
|
|
||||||
var manifest = new GameManifest()
|
var manifest = new GameManifest()
|
||||||
{
|
{
|
||||||
|
Id = game.Id,
|
||||||
Title = game.Title,
|
Title = game.Title,
|
||||||
SortTitle = game.SortTitle,
|
SortTitle = game.SortTitle,
|
||||||
Description = game.Description,
|
Description = game.Description,
|
||||||
|
|
Loading…
Add table
Reference in a new issue