LANCommander/LANCommander.Client/Services/DownloadService.cs
Pat Hartl 8791d8d4ae Move from MAUI to Photino for wrapper
Photino brings some benefits:
- The initial memory usage seems to be lower than MAUI
- Build times are reduced
- Potential support for Linux
- The Photino.Blazor.CustomWindow package allows us to have a custom window frame

The project has been converted to use Photino. If it needs to be moved back at some point, it probably wouldn't be very difficult. The window chrome in this verison has been heavily customized for stylistic purposes.
2024-06-05 01:21:31 -05:00

367 lines
12 KiB
C#

using LANCommander.Client.Data.Models;
using LANCommander.Client.Enums;
using LANCommander.Client.Models;
using LANCommander.SDK.Exceptions;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.PowerShell;
using Microsoft.Toolkit.Uwp.Notifications;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LANCommander.Client.Services
{
public class DownloadService : BaseService
{
private readonly SDK.Client Client;
private readonly GameService GameService;
private readonly SaveService SaveService;
private Settings Settings;
private Stopwatch Stopwatch { get; set; }
public ObservableCollection<IDownloadQueueItem> Queue { get; set; }
public delegate Task OnQueueChangedHandler();
public event OnQueueChangedHandler OnQueueChanged;
public delegate Task OnInstallCompleteHandler();
public event OnInstallCompleteHandler OnInstallComplete;
public DownloadService(SDK.Client client, GameService gameService, SaveService saveService) : base()
{
Client = client;
GameService = gameService;
SaveService = saveService;
Stopwatch = new Stopwatch();
Settings = SettingService.GetSettings();
Queue = new ObservableCollection<IDownloadQueueItem>();
Queue.CollectionChanged += (sender, e) =>
{
OnQueueChanged?.Invoke();
};
// Client.Games.OnArchiveExtractionProgress += Games_OnArchiveExtractionProgress;
// Client.Games.OnArchiveEntryExtractionProgress += Games_OnArchiveEntryExtractionProgress;
}
private void Games_OnArchiveExtractionProgress(long position, long length, SDK.Models.Game game)
{
OnQueueChanged?.Invoke();
}
private void Games_OnArchiveEntryExtractionProgress(object sender, SDK.ArchiveEntryExtractionProgressArgs e)
{
OnQueueChanged?.Invoke();
}
public async Task Add(Game game)
{
var gameInfo = await Client.Games.GetAsync(game.Id);
// Check to see if we need to install the base game (this game is probably a mod or expansion)
if (gameInfo.BaseGame != null)
{
var baseGame = await GameService.Get(gameInfo.BaseGame.Id);
if (baseGame != null && !baseGame.Installed)
{
// Install game
// Where does this get executed? In Playnite it's triggering the InstallController
// Probably because it does a bunch of UI things
}
}
if (!Queue.Any(i => i.Id == game.Id || (i.Status != DownloadStatus.Canceled || i.Status != DownloadStatus.Failed)))
{
var queueItem = new DownloadQueueGame(gameInfo);
if (Queue.Any(i => i.State))
Queue.Add(queueItem);
else
{
queueItem.Status = DownloadStatus.Downloading;
Queue.Add(queueItem);
Install();
}
OnQueueChanged?.Invoke();
game.Title = gameInfo.Title;
game.Installed = false;
await GameService.Update(game);
}
}
public async Task CancelInstall()
{
}
public async Task Install()
{
var currentItem = Queue.FirstOrDefault(i => i.State);
if (currentItem == null)
return;
var game = await GameService.Get(currentItem.Id);
var gameInfo = await Client.Games.GetAsync(currentItem.Id);
if (gameInfo == null)
return;
currentItem.Status = DownloadStatus.Downloading;
OnQueueChanged?.Invoke();
string installDirectory;
try
{
installDirectory = await Task.Run(() => Client.Games.Install(gameInfo.Id));
game.InstallDirectory = installDirectory;
game.Installed = true;
game.InstalledVersion = currentItem.Version;
}
catch (InstallCanceledException ex)
{
// OnInstallCancelled?.Invoke(currentItem);
Queue.Remove(currentItem);
return;
}
catch (InstallException ex)
{
Queue.Remove(currentItem);
return;
}
catch (Exception ex)
{
Queue.Remove(currentItem);
return;
}
currentItem.Progress = 1;
currentItem.BytesDownloaded = currentItem.TotalBytes;
OnQueueChanged?.Invoke();
#region Install Redistributables
if (gameInfo.Redistributables != null && gameInfo.Redistributables.Any())
{
currentItem.Status = DownloadStatus.InstallingRedistributables;
OnQueueChanged?.Invoke();
await Task.Run(() => Client.Redistributables.Install(gameInfo));
}
#endregion
#region Download Latest Save
// Logger?.Trace("Attempting to download the latest save");
currentItem.Status = DownloadStatus.DownloadingSaves;
OnQueueChanged?.Invoke();
await SaveService.DownloadLatest(game);
#endregion
#region Run Scripts
if (gameInfo.Scripts != null && gameInfo.Scripts.Any())
{
currentItem.Status = DownloadStatus.RunningScripts;
OnQueueChanged?.Invoke();
try
{
RunInstallScript(gameInfo);
RunKeyChangeScript(gameInfo);
RunNameChangeScript(gameInfo);
}
catch (Exception ex) {
// Logger?.Error
}
}
#endregion
#region Install Expansions/Mods
foreach (var dependentGame in gameInfo.DependentGames.Where(g => g.Type == SDK.Enums.GameType.Expansion || g.Type == SDK.Enums.GameType.Mod))
{
if (dependentGame.Type == SDK.Enums.GameType.Expansion)
currentItem.Status = DownloadStatus.InstallingExpansions;
else if (dependentGame.Type == SDK.Enums.GameType.Mod)
currentItem.Status = DownloadStatus.InstallingMods;
OnQueueChanged?.Invoke();
try
{
await Task.Run(() => Client.Games.Install(dependentGame.Id));
}
catch (InstallCanceledException ex)
{
}
try
{
if (dependentGame.BaseGame == null)
dependentGame.BaseGame = gameInfo;
RunInstallScript(dependentGame);
RunNameChangeScript(dependentGame);
RunKeyChangeScript(dependentGame);
}
catch (Exception ex)
{
// Logger?.Error(ex, "Scripts failed to run for mod/expansion");
}
}
#endregion
if (currentItem is DownloadQueueGame)
{
currentItem.CompletedOn = DateTime.Now;
currentItem.Status = DownloadStatus.Complete;
currentItem.Progress = 1;
currentItem.BytesDownloaded = currentItem.TotalBytes;
OnQueueChanged?.Invoke();
await GameService.Update(game);
ShowCompletedNotification(currentItem);
OnInstallComplete?.Invoke();
}
Install();
}
private void ShowCompletedNotification(IDownloadQueueItem queueItem)
{
var builder = new ToastContentBuilder();
if (queueItem.IsUpdate)
builder.AddText("Game Updated")
.AddText($"{queueItem.Title} has finished updating!");
else
builder.AddText("Game Installed")
.AddText($"{queueItem.Title} has finished installing!");
builder.AddArgument("gameId", queueItem.Id.ToString())
.AddButton(
new ToastButton()
.SetContent("Play")
.AddArgument("action", "play")
)
.AddButton(
new ToastButton()
.SetContent("View in Library")
.AddArgument("action", "viewInLibrary")
);
//.Show
// .AddAppLogoOverride()
//.Show();
}
private int RunInstallScript(SDK.Models.Game game)
{
var installDirectory = Client.Games.GetInstallDirectory(game);
var manifest = ManifestHelper.Read(installDirectory, game.Id);
var path = ScriptHelper.GetScriptFilePath(installDirectory, game.Id, SDK.Enums.ScriptType.Install);
if (File.Exists(path))
{
// Logger?.Trace("Running install script");
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.Games.DefaultInstallDirectory);
script.AddVariable("ServerAddress", Settings.Authentication.ServerAddress);
script.UseFile(ScriptHelper.GetScriptFilePath(installDirectory, game.Id, SDK.Enums.ScriptType.Install));
return script.Execute();
}
return 0;
}
private int RunNameChangeScript(SDK.Models.Game game)
{
var installDirectory = Client.Games.GetInstallDirectory(game);
var manifest = ManifestHelper.Read(installDirectory, game.Id);
var path = ScriptHelper.GetScriptFilePath(installDirectory, game.Id, SDK.Enums.ScriptType.NameChange);
var oldName = SDK.GameService.GetPlayerAlias(installDirectory, game.Id);
var newName = Settings.Profile.Alias;
if (File.Exists(path))
{
// Logger?.Trace("Running name change script");
var script = new PowerShellScript();
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.Games.DefaultInstallDirectory);
script.AddVariable("ServerAddress", Settings.Authentication.ServerAddress);
script.AddVariable("OldPlayerAlias", oldName);
script.AddVariable("NewPlayerAlias", newName);
script.UseFile(path);
SDK.GameService.UpdatePlayerAlias(installDirectory, game.Id, newName);
return script.Execute();
}
return 0;
}
private int RunKeyChangeScript(SDK.Models.Game game)
{
var installDirectory = Client.Games.GetInstallDirectory(game);
var manifest = ManifestHelper.Read(installDirectory, game.Id);
var path = ScriptHelper.GetScriptFilePath(installDirectory, game.Id, SDK.Enums.ScriptType.KeyChange);
if (File.Exists(path))
{
// Logger?.Trace("Running key change script");
var script = new PowerShellScript();
var key = Client.Games.GetAllocatedKey(manifest.Id);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
script.AddVariable("DefaultInstallDirectory", Settings.Games.DefaultInstallDirectory);
script.AddVariable("ServerAddress", Settings.Authentication.ServerAddress);
script.AddVariable("AllocatedKey", key);
script.UseFile(path);
SDK.GameService.UpdateCurrentKey(installDirectory, game.Id, key);
return script.Execute();
}
return 0;
}
}
}