Added redistributable installing to plugin

redistributables
Pat Hartl 2023-10-24 19:11:50 -05:00
parent e4cf2aabfe
commit b4405d3034
7 changed files with 258 additions and 4 deletions

View File

@ -50,7 +50,7 @@ namespace LANCommander.PlaynitePlugin
var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () => var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
{ {
Logger.Trace("Attempting to download and extract game..."); Logger.Trace("Attempting to download and extract game...");
return DownloadAndExtract(game); return DownloadAndExtractGame(game);
}); });
if (!result.Success && !result.Canceled) if (!result.Success && !result.Canceled)
@ -88,6 +88,12 @@ namespace LANCommander.PlaynitePlugin
SaveScript(game, result.Directory, ScriptType.NameChange); SaveScript(game, result.Directory, ScriptType.NameChange);
SaveScript(game, result.Directory, ScriptType.KeyChange); SaveScript(game, result.Directory, ScriptType.KeyChange);
if (game.Redistributables != null && game.Redistributables.Count() > 0)
{
Logger.Trace("Installing required redistributables...");
InstallRedistributables(game);
}
try try
{ {
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install); PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
@ -106,7 +112,7 @@ namespace LANCommander.PlaynitePlugin
InvokeOnInstalled(new GameInstalledEventArgs(installInfo)); InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
} }
private ExtractionResult DownloadAndExtract(LANCommander.SDK.Models.Game game) private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game)
{ {
if (game == null) if (game == null)
{ {
@ -204,6 +210,152 @@ namespace LANCommander.PlaynitePlugin
return extractionResult; 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);
if (!detectionResult == 0)
{
var extractionResult = DownloadAndExtractRedistributable(redistributable);
if (extractionResult.Success)
{
extractTempPath = extractionResult.Directory;
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) private string Download(LANCommander.SDK.Models.Game game)
{ {
string tempFile = String.Empty; string tempFile = String.Empty;
@ -294,6 +446,17 @@ namespace LANCommander.PlaynitePlugin
File.WriteAllText(destination, yaml); File.WriteAllText(destination, yaml);
} }
private string SaveTempScript(LANCommander.SDK.Models.Script script)
{
var tempPath = Path.GetTempFileName();
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) private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
{ {
var script = game.Scripts.FirstOrDefault(s => s.Type == type); var script = game.Scripts.FirstOrDefault(s => s.Type == type);

View File

@ -206,6 +206,11 @@ namespace LANCommander.PlaynitePlugin
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler); return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
} }
public TrackableStream StreamRedistributable(Guid id)
{
return StreamRequest($"/api/Redistributables/{id}/Download");
}
public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler) public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
{ {
return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler); return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler);

View File

@ -39,7 +39,7 @@ namespace LANCommander.PlaynitePlugin
File.Delete(tempScript); File.Delete(tempScript);
} }
public void RunScript(string path, bool asAdmin = false, string arguments = null) 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.Trace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}");
@ -58,6 +58,9 @@ namespace LANCommander.PlaynitePlugin
if (arguments != null) if (arguments != null)
process.StartInfo.Arguments += " " + arguments; process.StartInfo.Arguments += " " + arguments;
if (workingDirectory != null)
process.StartInfo.WorkingDirectory = workingDirectory;
if (asAdmin) if (asAdmin)
{ {
process.StartInfo.Verb = "runas"; process.StartInfo.Verb = "runas";
@ -68,6 +71,8 @@ namespace LANCommander.PlaynitePlugin
process.WaitForExit(); process.WaitForExit();
Wow64RevertWow64FsRedirection(ref wow64Value); Wow64RevertWow64FsRedirection(ref wow64Value);
return process.ExitCode;
} }
public void RunScript(Game game, ScriptType type, string arguments = null) public void RunScript(Game game, ScriptType type, string arguments = null)

View File

@ -4,7 +4,15 @@
{ {
Install, Install,
Uninstall, Uninstall,
[Display(Name = "Name Change")]
NameChange, NameChange,
KeyChange [Display(Name = "Key Change")]
KeyChange,
[Display(Name = "Save Upload")]
SaveUpload,
[Display(Name = "Save Download")]
SaveDownload,
[Display(Name = "Detect Install")]
DetectInstall
} }
} }

View File

@ -16,5 +16,6 @@ namespace LANCommander.SDK.Models
public virtual Company Developer { get; set; } public virtual Company Developer { get; set; }
public virtual IEnumerable<Archive> Archives { get; set; } public virtual IEnumerable<Archive> Archives { get; set; }
public virtual IEnumerable<Script> Scripts { get; set; } public virtual IEnumerable<Script> Scripts { get; set; }
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
} }
} }

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
namespace LANCommander.SDK.Models
{
public class Redistributable : BaseModel
{
public string Name { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public DateTime ReleasedOn { get; set; }
public virtual IEnumerable<Archive> Archives { get; set; }
public virtual IEnumerable<Script> Scripts { get; set; }
}
}

View File

@ -0,0 +1,57 @@
using LANCommander.Data.Models;
using LANCommander.Extensions;
using LANCommander.Models;
using LANCommander.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LANCommander.Controllers.Api
{
[Authorize(AuthenticationSchemes = "Bearer")]
[Route("api/[controller]")]
[ApiController]
public class RedistributableController : ControllerBase
{
private readonly RedistributableService RedistributableService;
private readonly LANCommanderSettings Settings = SettingService.GetSettings();
public RedistributableController(RedistributableService redistributableService)
{
RedistributableService = redistributableService;
}
[HttpGet]
public async Task<IEnumerable<Redistributable>> Get()
{
return await RedistributableService.Get();
}
[HttpGet("{id}")]
public async Task<Redistributable> Get(Guid id)
{
return await RedistributableService.Get(id);
}
[HttpGet("{id}/Download")]
public async Task<IActionResult> Download(Guid id)
{
var redistributable = await RedistributableService.Get(id);
if (redistributable == null)
return NotFound();
if (redistributable.Archives == null || redistributable.Archives.Count == 0)
return NotFound();
var archive = redistributable.Archives.OrderByDescending(a => a.CreatedOn).First();
var filename = Path.Combine(Settings.Archives.StoragePath, archive.ObjectKey);
if (!System.IO.File.Exists(filename))
return NotFound();
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{redistributable.Name.SanitizeFilename()}.zip");
}
}
}