Relocate crucial installation logic to SDK

pull/32/head
Pat Hartl 2023-11-09 19:40:38 -06:00
parent ff6f9997f5
commit a679fae0cb
11 changed files with 497 additions and 78 deletions

View File

@ -99,15 +99,10 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ExtractionResult.cs" />
<Compile Include="TrackableStream.cs" />
<Compile Include="Extensions\MultiplayerInfoExtensions.cs" />
<Compile Include="Helpers\RetryHelper.cs" />
<Compile Include="PowerShellRuntime.cs" />
<Compile Include="Services\GameSaveService.cs" />
<Compile Include="UninstallController.cs" />
<Compile Include="InstallController.cs" />
<Compile Include="LANCommanderClient.cs" />
<Compile Include="LANCommanderLibraryPlugin.cs" />
<Compile Include="ViewModels\LANCommanderSettingsViewModel.cs" />
<Compile Include="Views\LANCommanderSettingsView.xaml.cs">

View File

@ -21,7 +21,7 @@ namespace LANCommander.PlaynitePlugin
{
public static readonly ILogger Logger = LogManager.GetLogger();
internal LANCommanderSettingsViewModel Settings { get; set; }
internal LANCommanderClient LANCommander { get; set; }
internal LANCommander.SDK.LANCommander LANCommander { get; set; }
internal PowerShellRuntime PowerShellRuntime { get; set; }
internal GameSaveService GameSaveService { get; set; }
@ -39,16 +39,14 @@ namespace LANCommander.PlaynitePlugin
Settings = new LANCommanderSettingsViewModel(this);
LANCommander = new LANCommanderClient(Settings.ServerAddress);
LANCommander.Token = new SDK.Models.AuthToken()
LANCommander = new SDK.LANCommander(Settings.ServerAddress);
LANCommander.Client.UseToken(new SDK.Models.AuthToken()
{
AccessToken = Settings.AccessToken,
RefreshToken = Settings.RefreshToken,
};
});
PowerShellRuntime = new PowerShellRuntime();
GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
// GameSaveService = new GameSaveService(LANCommander, PlayniteApi, PowerShellRuntime);
api.UriHandler.RegisterSource("lancommander", args =>
{
@ -91,7 +89,7 @@ namespace LANCommander.PlaynitePlugin
public bool ValidateConnection()
{
return LANCommander.ValidateToken(LANCommander.Token);
return LANCommander.Client.ValidateToken();
}
public override IEnumerable<GameMetadata> GetGames(LibraryGetGamesArgs args)
@ -111,7 +109,7 @@ namespace LANCommander.PlaynitePlugin
}
}
var games = LANCommander
var games = LANCommander.Client
.GetGames()
.Where(g => g != null && g.Archives != null && g.Archives.Count() > 0);
@ -121,7 +119,7 @@ namespace LANCommander.PlaynitePlugin
{
Logger.Trace($"Importing/updating metadata for game \"{game.Title}\"...");
var manifest = LANCommander.GetGameManifest(game.Id);
var manifest = LANCommander.Client.GetGameManifest(game.Id);
Logger.Trace("Successfully grabbed game manifest");
var existingGame = PlayniteApi.Database.Games.FirstOrDefault(g => g.GameId == game.Id.ToString() && g.PluginId == Id && g.IsInstalled);
@ -183,13 +181,13 @@ namespace LANCommander.PlaynitePlugin
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
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(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Icon)));
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(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Cover)));
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(LANCommander.Client.GetMediaUrl(game.Media.First(m => m.Type == SDK.Enums.MediaType.Background)));
gameMetadata.Add(metadata);
}
@ -244,7 +242,7 @@ namespace LANCommander.PlaynitePlugin
if (result.Result == true)
{
PowerShellRuntime.RunScript(nameChangeArgs.Games.First(), SDK.Enums.ScriptType.NameChange, $@"""{result.SelectedString}"" ""{oldName}""");
LANCommander.ChangeAlias(result.SelectedString);
LANCommander.Client.ChangeAlias(result.SelectedString);
}
}
};
@ -264,7 +262,7 @@ namespace LANCommander.PlaynitePlugin
if (Guid.TryParse(keyChangeArgs.Games.First().GameId, out gameId))
{
// NUKIEEEE
var newKey = LANCommander.GetNewKey(gameId);
var newKey = LANCommander.Client.GetNewKey(gameId);
if (String.IsNullOrEmpty(newKey))
PlayniteApi.Dialogs.ShowErrorMessage("There are no more keys available on the server.", "No Keys Available");
@ -402,7 +400,7 @@ namespace LANCommander.PlaynitePlugin
var games = PlayniteApi.Database.Games.Where(g => g.IsInstalled).ToList();
LANCommander.ChangeAlias(result.SelectedString);
LANCommander.Client.ChangeAlias(result.SelectedString);
Logger.Trace($"Running name change scripts across {games.Count} installed game(s)");

View File

@ -1,6 +1,6 @@
using LANCommander.SDK;
using LANCommander.SDK.Models;
using Playnite.SDK;
using Microsoft.Extensions.Logging;
using RestSharp;
using System;
using System.Collections.Generic;
@ -11,19 +11,19 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class LANCommanderClient
public class Client
{
public static readonly ILogger Logger = LogManager.GetLogger();
private static readonly ILogger Logger;
public readonly RestClient Client;
public AuthToken Token;
private readonly RestClient ApiClient;
private AuthToken Token;
public LANCommanderClient(string baseUrl)
public Client(string baseUrl)
{
if (!String.IsNullOrWhiteSpace(baseUrl))
Client = new RestClient(baseUrl);
ApiClient = new RestClient(baseUrl);
}
private T PostRequest<T>(string route, object body)
@ -32,7 +32,7 @@ namespace LANCommander.PlaynitePlugin
.AddJsonBody(body)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
var response = Client.Post<T>(request);
var response = ApiClient.Post<T>(request);
return response.Data;
}
@ -42,7 +42,7 @@ namespace LANCommander.PlaynitePlugin
var request = new RestRequest(route)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
var response = Client.Get<T>(request);
var response = ApiClient.Get<T>(request);
return response.Data;
}
@ -58,7 +58,7 @@ namespace LANCommander.PlaynitePlugin
client.DownloadProgressChanged += (s, e) => progressHandler(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;
}
@ -72,14 +72,14 @@ namespace LANCommander.PlaynitePlugin
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"]));
}
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,
Password = password
@ -88,7 +88,14 @@ namespace LANCommander.PlaynitePlugin
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return response.Data;
Token = new AuthToken
{
AccessToken = response.Data.AccessToken,
RefreshToken = response.Data.RefreshToken,
Expiration = response.Data.Expiration
};
return Token;
case HttpStatusCode.Forbidden:
case HttpStatusCode.BadRequest:
@ -102,7 +109,7 @@ namespace LANCommander.PlaynitePlugin
public async Task<AuthResponse> 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,
Password = password
@ -125,19 +132,19 @@ namespace LANCommander.PlaynitePlugin
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;
}
public AuthResponse RefreshToken(AuthToken token)
{
Logger.Trace("Refreshing token...");
Logger.LogTrace("Refreshing token...");
var request = new RestRequest("/api/Auth/Refresh")
.AddJsonBody(token);
var response = Client.Post<AuthResponse>(request);
var response = ApiClient.Post<AuthResponse>(request);
if (response.StatusCode != HttpStatusCode.OK)
throw new WebException(response.ErrorMessage);
@ -145,13 +152,18 @@ namespace LANCommander.PlaynitePlugin
return response.Data;
}
public bool ValidateToken()
{
return ValidateToken(Token);
}
public bool ValidateToken(AuthToken token)
{
Logger.Trace("Validating token...");
Logger.LogTrace("Validating token...");
if (token == null)
{
Logger.Trace("Token is null!");
Logger.LogTrace("Token is null!");
return false;
}
@ -160,22 +172,27 @@ namespace LANCommander.PlaynitePlugin
if (String.IsNullOrEmpty(token.AccessToken) || String.IsNullOrEmpty(token.RefreshToken))
{
Logger.Trace("Token is empty!");
Logger.LogTrace("Token is empty!");
return false;
}
var response = Client.Post(request);
var response = ApiClient.Post(request);
var valid = response.StatusCode == HttpStatusCode.OK;
if (valid)
Logger.Trace("Token is valid!");
Logger.LogTrace("Token is valid!");
else
Logger.Trace("Token is invalid!");
Logger.LogTrace("Token is invalid!");
return response.StatusCode == HttpStatusCode.OK;
}
public void UseToken(AuthToken token)
{
Token = token;
}
public IEnumerable<Game> GetGames()
{
return GetRequest<IEnumerable<Game>>("/api/Games");
@ -223,26 +240,26 @@ namespace LANCommander.PlaynitePlugin
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)
.AddHeader("Authorization", $"Bearer {Token.AccessToken}");
request.AddFile(gameId, data, gameId);
var response = Client.Post<GameSave>(request);
var response = ApiClient.Post<GameSave>(request);
return response.Data;
}
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)
{
Logger.Trace("Requesting key allocation...");
Logger.LogTrace("Requesting key allocation...");
var macAddress = GetMacAddress();
@ -261,7 +278,7 @@ namespace LANCommander.PlaynitePlugin
public string GetAllocatedKey(Guid id)
{
Logger.Trace("Requesting allocated key...");
Logger.LogTrace("Requesting allocated key...");
var macAddress = GetMacAddress();
@ -283,7 +300,7 @@ namespace LANCommander.PlaynitePlugin
public string GetNewKey(Guid id)
{
Logger.Trace("Requesting new key allocation...");
Logger.LogTrace("Requesting new key allocation...");
var macAddress = GetMacAddress();
@ -305,14 +322,14 @@ namespace LANCommander.PlaynitePlugin
public User GetProfile()
{
Logger.Trace("Requesting player's profile...");
Logger.LogTrace("Requesting player's profile...");
return GetRequest<User>("/api/Profile");
}
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);

View File

@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class ExtractionResult
{

View File

@ -1,15 +1,12 @@
using Playnite.SDK;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin.Helpers
namespace LANCommander.SDK.Helpers
{
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)
{
@ -19,14 +16,14 @@ namespace LANCommander.PlaynitePlugin.Helpers
{
try
{
Logger.Trace($"Attempt #{attempts + 1}/{maxAttempts}...");
Logger.LogTrace($"Attempt #{attempts + 1}/{maxAttempts}...");
attempts++;
return action();
}
catch (Exception ex)
{
Logger.Error(ex, $"Attempt failed!");
Logger.LogError(ex, $"Attempt failed!");
if (attempts >= maxAttempts)
return @default;

View File

@ -4,4 +4,11 @@
<TargetFramework>netstandard2.0</TargetFramework>
</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="13.7.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,405 @@
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;
using System.Threading.Tasks;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace LANCommander.SDK
{
public class ArchiveExtractionProgressArgs : EventArgs
{
public long Position { get; set; }
public long Length { get; set; }
}
public class ArchiveEntryExtractionProgressArgs : EventArgs
{
public IReader Reader { get; set; }
public TrackableStream Stream { get; set; }
public ReaderProgress Progress { get; set; }
public IEntry Entry { get; set; }
}
public class LANCommander
{
public static readonly ILogger Logger;
private const string ManifestFilename = "_manifest.yml";
private string DefaultInstallDirectory { get; set; }
public Client Client { get; set; }
private PowerShellRuntime PowerShellRuntime;
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 LANCommander(string baseUrl)
{
Client = new Client(baseUrl);
}
/// <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 InstallGame(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 DownloadAndExtractGame(game);
});
if (!result.Success && !result.Canceled)
throw new Exception("Could not extract the installer. Retry the install or check your connection");
else if (result.Canceled)
throw new Exception("Game install was canceled");
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);
WriteManifest(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");
SaveScript(game, ScriptType.Install);
SaveScript(game, ScriptType.Uninstall);
SaveScript(game, ScriptType.NameChange);
SaveScript(game, ScriptType.KeyChange);
if (game.Redistributables != null && game.Redistributables.Count() > 0)
{
Logger.LogTrace("Installing required redistributables");
InstallRedistributables(game);
}
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");
}
// Plugin.UpdateGame(manifest, gameId)
// Plugin.DownloadCache.Remove(gameId);
return result.Directory;
}
private ExtractionResult DownloadAndExtractGame(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);
try
{
Directory.CreateDirectory(destination);
using (var gameStream = Client.StreamGame(game.Id))
using (var reader = ReaderFactory.Open(gameStream))
{
gameStream.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 = reader,
Stream = gameStream
});
};
reader.WriteAllToDirectory(destination, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
catch (Exception ex)
{
if (false)
{
}
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");
}
}
var extractionResult = new ExtractionResult
{
Canceled = false,
};
if (!extractionResult.Canceled)
{
extractionResult.Success = true;
extractionResult.Directory = destination;
Logger.LogTrace("Game {Game} successfully downloaded and extracted to {Destination}", game.Title, destination);
}
return extractionResult;
}
private void InstallRedistributables(Game game)
{
foreach (var redistributable in game.Redistributables)
{
InstallRedistributable(redistributable);
}
}
private void InstallRedistributable(Redistributable redistributable)
{
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.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 DownloadAndExtractRedistributable(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 = reader,
Stream = redistributableStream
});
};
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;
}
public void WriteManifest(GameManifest manifest, string installDirectory)
{
var destination = Path.Combine(installDirectory, ManifestFilename);
Logger.LogTrace("Attempting to write manifest to path {Destination}", destination);
var serializer = new SerializerBuilder()
.WithNamingConvention(PascalCaseNamingConvention.Instance)
.Build();
Logger.LogTrace("Serializing manifest");
var yaml = serializer.Serialize(manifest);
Logger.LogTrace("Writing manifest file");
File.WriteAllText(destination, yaml);
}
private 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;
}
private 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);
}
public void ChangeAlias(string alias)
{
}
}
}

View File

@ -8,5 +8,6 @@ namespace LANCommander.SDK.Models
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime Expiration { get; set; }
}
}

View File

@ -10,6 +10,7 @@ namespace LANCommander.SDK.Models
public string DirectoryName { get; set; }
public string Description { get; set; }
public DateTime ReleasedOn { get; set; }
public string InstallDirectory { get; set; }
public virtual IEnumerable<Action> Actions { get; set; }
public virtual IEnumerable<Tag> Tags { get; set; }
public virtual Company Publisher { get; set; }

View File

@ -1,22 +1,20 @@
using LANCommander.SDK.Enums;
using Playnite.SDK;
using Playnite.SDK.Models;
using LANCommander.SDK.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security.RightsManagement;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.PlaynitePlugin
namespace LANCommander.SDK
{
internal class PowerShellRuntime
{
public static readonly ILogger Logger = LogManager.GetLogger();
public static readonly ILogger Logger;
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
@ -26,11 +24,11 @@ namespace LANCommander.PlaynitePlugin
public 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");
Logger.Trace($"Creating temp script at path {tempScript}");
Logger.LogTrace($"Creating temp script at path {tempScript}");
File.WriteAllText(tempScript, command);
@ -41,7 +39,7 @@ namespace LANCommander.PlaynitePlugin
public 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;
@ -95,7 +93,7 @@ namespace LANCommander.PlaynitePlugin
// Concatenate scripts
var sb = new StringBuilder();
Logger.Trace("Concatenating scripts...");
Logger.LogTrace("Concatenating scripts...");
foreach (var path in paths)
{
@ -103,16 +101,16 @@ namespace LANCommander.PlaynitePlugin
sb.AppendLine(contents);
Logger.Trace($"Added {path}!");
Logger.LogTrace($"Added {path}!");
}
Logger.Trace("Done concatenating!");
Logger.LogTrace("Done concatenating!");
if (sb.Length > 0)
{
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());

View File

@ -1,9 +1,9 @@
using System;
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 event OnProgressDelegate OnProgress = delegate { };