From 10278524811fe1f4bc0da134b7e004d687fc3944 Mon Sep 17 00:00:00 2001 From: Joyal Jose Date: Mon, 1 Jan 2024 23:33:08 +0530 Subject: [PATCH] TEMP --- Crimson/App.xaml | 3 + Crimson/Core/InstallManager.cs | 215 ++++++++++++++++++----------- Crimson/Crimson.csproj | 2 +- Crimson/Utils/Storage.cs | 4 +- Crimson/Views/GameInfoPage.xaml | 19 ++- Crimson/Views/GameInfoPage.xaml.cs | 24 ++++ Crimson/Views/SettingsPage.xaml | 2 +- 7 files changed, 182 insertions(+), 87 deletions(-) diff --git a/Crimson/App.xaml b/Crimson/App.xaml index 1028267..f3cbfc9 100644 --- a/Crimson/App.xaml +++ b/Crimson/App.xaml @@ -16,6 +16,9 @@ Crimson 1200 1200 + diff --git a/Crimson/Core/InstallManager.cs b/Crimson/Core/InstallManager.cs index dd06286..1c36e8d 100644 --- a/Crimson/Core/InstallManager.cs +++ b/Crimson/Core/InstallManager.cs @@ -26,7 +26,7 @@ public class InstallManager private readonly ConcurrentQueue _downloadQueue = new(); private readonly ConcurrentQueue _ioQueue = new(); - private readonly CancellationTokenSource _cancellationTokenSource = new(); + private CancellationTokenSource _cancellationTokenSource = new(); private readonly ConcurrentDictionary _fileLocksConcurrentDictionary = new(); @@ -130,13 +130,6 @@ private async void ProcessNext() // TODO Handle stats if game is installed - // create CurrentInstall.folder if it doesn't exist - if (!Directory.Exists(CurrentInstall.Location)) - { - Directory.CreateDirectory(CurrentInstall.Location); - _log.Debug("Folder created at: {location}", CurrentInstall.Location); - } - if (!HasFolderWritePermissions(CurrentInstall.Location)) { _log.Error("ProcessNext: No write permissions to {Location}", CurrentInstall.Location); @@ -146,44 +139,39 @@ private async void ProcessNext() ProcessNext(); } - var chunkDownloadList = new List(); - - foreach (var fileManifest in data.FileManifestList.Elements) + if (CurrentInstall.Action == ActionType.Install) { - foreach (var chunkPart in fileManifest.ChunkParts) + // create CurrentInstall.folder if it doesn't exist + if (!Directory.Exists(CurrentInstall.Location)) { - if (_chunkToFileManifestsDictionary.TryGetValue(chunkPart.GuidStr, out var fileManifests)) - { - fileManifests.Add(fileManifest); - _chunkToFileManifestsDictionary[chunkPart.GuidStr] = fileManifests; - } - else - { - _ = _chunkToFileManifestsDictionary.TryAdd(chunkPart.GuidStr, - new List() { fileManifest }); - } + Directory.CreateDirectory(CurrentInstall.Location); + _log.Debug("Folder created at: {location}", CurrentInstall.Location); + } - if (chunkDownloadList.FirstOrDefault(chunk => chunk.GuidStr == chunkPart.GuidStr) != null) continue; + GetChunksToDownload(manifestData, data); + } + else if (CurrentInstall.Action == ActionType.Uninstall) + { + foreach (var fileManifest in data.FileManifestList.Elements) + { + CurrentInstall.TotalWriteSizeMb += fileManifest.FileSize / 1000000.0; - var chunkInfo = data.CDL.GetChunkByGuid(chunkPart.GuidStr); - var newTask = new DownloadTask() + var task = new IoTask() { - Url = manifestData.BaseUrls.FirstOrDefault() + "/" + chunkInfo.Path, - TempPath = Path.Combine(CurrentInstall.Location, ".temp", (chunkInfo.GuidStr + ".chunk")), - Guid = chunkInfo.GuidStr, - ChunkInfo = chunkInfo + DestinationFilePath = Path.Combine(CurrentInstall.Location, fileManifest.Filename), + TaskType = IoTaskType.Delete, + Size = fileManifest.FileSize, }; - _log.Debug("ProcessNext: Adding new download task {@task}", newTask); - chunkDownloadList.Add(chunkInfo); - _downloadQueue.Enqueue(newTask); - - CurrentInstall.TotalDownloadSizeMb += chunkInfo.FileSize / 1000000.0; + _log.Debug("ProcessNext: Adding ioTask: {task}", task); + _ioQueue.Enqueue(task); } - CurrentInstall.TotalWriteSizeMb += fileManifest.FileSize / 1000000.0; } CurrentInstall!.Status = ActionStatus.Processing; InstallationStatusChanged?.Invoke(CurrentInstall); + + // Reset cancellation token + _cancellationTokenSource = new CancellationTokenSource(); _installStopWatch.Start(); for (var i = 0; i < _numberOfThreads; i++) @@ -448,6 +436,24 @@ private async Task ProcessIoQueue() } } break; + case IoTaskType.Delete: + File.Delete(ioTask.DestinationFilePath); + lock (_installItemLock) + { + CurrentInstall.WrittenSize += ioTask.Size / 1000000.0; + CurrentInstall.WriteSpeed = _installStopWatch.IsRunning && _installStopWatch.Elapsed.TotalSeconds > 0 + ? Math.Round(CurrentInstall.WrittenSize / _installStopWatch.Elapsed.TotalSeconds, 2) + : 0; + CurrentInstall.ProgressPercentage = Convert.ToInt32((CurrentInstall.WrittenSize / CurrentInstall.TotalWriteSizeMb) * 100); + + // Limit firing progress update events + if ((DateTime.Now - _lastUpdateTime).TotalMilliseconds >= _progressUpdateIntervalInMS) + { + _lastUpdateTime = DateTime.Now; + InstallProgressUpdate?.Invoke(CurrentInstall); + } + } + break; } } catch (Exception ex) @@ -460,7 +466,6 @@ private async Task ProcessIoQueue() InstallationStatusChanged?.Invoke(CurrentInstall); CurrentInstall = null; } - ProcessNext(); } } @@ -511,64 +516,76 @@ private async Task UpdateInstalledGameStatus() CurrentInstall.AppName); throw new Exception("Invalid game data"); } + var installedGamesDictionary = _storage.InstalledGamesDictionary; + installedGamesDictionary ??= []; - if (!_storage.InstalledGamesDictionary.TryGetValue(CurrentInstall.AppName, out InstalledGame installedGame)) + if (installedGamesDictionary.Count > 0 && CurrentInstall.Action == ActionType.Uninstall) { - installedGame = new InstalledGame(); + installedGamesDictionary.Remove(CurrentInstall.AppName); + _log.Information("UpdateInstalledGameStatus: Removing entry: {appName} from installed games list", CurrentInstall.AppName); } + else + { + if (!installedGamesDictionary.TryGetValue(CurrentInstall.AppName, out var installedGame)) + { + installedGame = new InstalledGame(); + } - var manifestDataBytes = await _repository.GetGameManifest(gameData.AssetInfos.Windows.Namespace, - gameData.AssetInfos.Windows.CatalogItemId, gameData.AppName); - var manifestData = Manifest.ReadAll(manifestDataBytes.ManifestBytes); + var manifestDataBytes = await _repository.GetGameManifest(gameData.AssetInfos.Windows.Namespace, + gameData.AssetInfos.Windows.CatalogItemId, gameData.AppName); + var manifestData = Manifest.ReadAll(manifestDataBytes.ManifestBytes); - // Verify all the files - CurrentInstall.Action = ActionType.Verify; - InstallationStatusChanged?.Invoke(CurrentInstall); - var invalidFilesList = await VerifyFiles(CurrentInstall.Location, manifestData.FileManifestList.Elements); + // Verify all the files + CurrentInstall.Action = ActionType.Verify; + InstallationStatusChanged?.Invoke(CurrentInstall); + var invalidFilesList = await VerifyFiles(CurrentInstall.Location, manifestData.FileManifestList.Elements); - if (invalidFilesList.Count > 0) - { - // We will handle this later - // For now fail install - throw new Exception("UpdateInstalledGameStatus: Verification failed"); - } - _log.Information("UpdateInstalledGameStatus: Verification successful for {appName}", CurrentInstall.AppName); + if (invalidFilesList.Count > 0) + { + // We will handle this later + // For now fail install + throw new Exception("UpdateInstalledGameStatus: Verification failed"); + } + _log.Information("UpdateInstalledGameStatus: Verification successful for {appName}", CurrentInstall.AppName); - await File.WriteAllBytesAsync(CurrentInstall.Location + "/.temp/manifest", manifestDataBytes.ManifestBytes); + await File.WriteAllBytesAsync(CurrentInstall.Location + "/.temp/manifest", manifestDataBytes.ManifestBytes); - var canRunOffLine = gameData.Metadata.CustomAttributes.CanRunOffline.Value == "true"; - var requireOwnerShipToken = gameData.Metadata.CustomAttributes?.OwnershipToken?.Value == "true"; + var canRunOffLine = gameData.Metadata.CustomAttributes.CanRunOffline.Value == "true"; + var requireOwnerShipToken = gameData.Metadata.CustomAttributes?.OwnershipToken?.Value == "true"; - if (installedGame?.AppName == null) - { - installedGame = new InstalledGame() + if (installedGame?.AppName == null) { - AppName = CurrentInstall.AppName, - IsDlc = gameData.IsDlc() - }; - } + installedGame = new InstalledGame() + { + AppName = CurrentInstall.AppName, + IsDlc = gameData.IsDlc() + }; + } - installedGame.BaseUrls = gameData.BaseUrls; - installedGame.CanRunOffline = canRunOffLine; - installedGame.Executable = manifestData.ManifestMeta.LaunchExe; - installedGame.InstallPath = CurrentInstall.Location; - installedGame.LaunchParameters = manifestData.ManifestMeta.LaunchCommand; - installedGame.RequiresOt = requireOwnerShipToken; - installedGame.Version = manifestData.ManifestMeta.BuildVersion; - installedGame.Title = gameData.AppTitle; + installedGame.BaseUrls = gameData.BaseUrls; + installedGame.CanRunOffline = canRunOffLine; + installedGame.Executable = manifestData.ManifestMeta.LaunchExe; + installedGame.InstallPath = CurrentInstall.Location; + installedGame.LaunchParameters = manifestData.ManifestMeta.LaunchCommand; + installedGame.RequiresOt = requireOwnerShipToken; + installedGame.Version = manifestData.ManifestMeta.BuildVersion; + installedGame.Title = gameData.AppTitle; - if (manifestData.ManifestMeta.UninstallActionPath != null) - { - installedGame.Uninstaller = new Dictionary + if (manifestData.ManifestMeta.UninstallActionPath != null) + { + installedGame.Uninstaller = new Dictionary { { manifestData.ManifestMeta.UninstallActionPath, manifestData.ManifestMeta.UninstallActionArgs } }; - } + } + + _log.Information("UpdateInstalledGameStatus: Adding new entry installed games list {@entry}", + installedGame); - _log.Information("UpdateInstalledGameStatus: Adding new entry installed games list {@entry}", - installedGame); + installedGamesDictionary.Add(gameData.AppName, installedGame); + } - _storage.SaveInstalledGamesList(installedGame); + _storage.UpdateInstalledGames(installedGamesDictionary); gameData.InstallStatus = CurrentInstall.Action switch { @@ -626,6 +643,50 @@ await Parallel.ForEachAsync(fileManifestLists, options, async (manifest, token) }); return invalidFilesList; } + + /// + /// Retrieves the chunks to download from the file manifest list + /// + /// + /// + private void GetChunksToDownload(GetGameManifest manifestData, Manifest data) + { + var chunkDownloadList = new List(); + + foreach (var fileManifest in data.FileManifestList.Elements) + { + foreach (var chunkPart in fileManifest.ChunkParts) + { + if (_chunkToFileManifestsDictionary.TryGetValue(chunkPart.GuidStr, out var fileManifests)) + { + fileManifests.Add(fileManifest); + _chunkToFileManifestsDictionary[chunkPart.GuidStr] = fileManifests; + } + else + { + _ = _chunkToFileManifestsDictionary.TryAdd(chunkPart.GuidStr, + new List() { fileManifest }); + } + + if (chunkDownloadList.FirstOrDefault(chunk => chunk.GuidStr == chunkPart.GuidStr) != null) continue; + + var chunkInfo = data.CDL.GetChunkByGuid(chunkPart.GuidStr); + var newTask = new DownloadTask() + { + Url = manifestData.BaseUrls.FirstOrDefault() + "/" + chunkInfo.Path, + TempPath = Path.Combine(CurrentInstall.Location, ".temp", (chunkInfo.GuidStr + ".chunk")), + Guid = chunkInfo.GuidStr, + ChunkInfo = chunkInfo + }; + _log.Debug("ProcessNext: Adding new download task {@task}", newTask); + chunkDownloadList.Add(chunkInfo); + _downloadQueue.Enqueue(newTask); + + CurrentInstall.TotalDownloadSizeMb += chunkInfo.FileSize / 1000000.0; + } + CurrentInstall.TotalWriteSizeMb += fileManifest.FileSize / 1000000.0; + } + } } internal class InstallItemComparer : IEqualityComparer diff --git a/Crimson/Crimson.csproj b/Crimson/Crimson.csproj index bef20ec..2a555de 100644 --- a/Crimson/Crimson.csproj +++ b/Crimson/Crimson.csproj @@ -10,7 +10,7 @@ true Crimson true - + None true diff --git a/Crimson/Utils/Storage.cs b/Crimson/Utils/Storage.cs index e0d6464..9dd15e7 100644 --- a/Crimson/Utils/Storage.cs +++ b/Crimson/Utils/Storage.cs @@ -175,9 +175,9 @@ public void SaveMetaData(Game game) _gameMetaDataDictionary.TryAdd(game.AppName, game); } - public void SaveInstalledGamesList(InstalledGame installedGame) + public void UpdateInstalledGames(Dictionary installedGamesDict) { - _installedGamesDictionary.TryAdd(installedGame.AppName, installedGame); + _installedGamesDictionary = installedGamesDict; var jsonString = JsonSerializer.Serialize(_installedGamesDictionary); diff --git a/Crimson/Views/GameInfoPage.xaml b/Crimson/Views/GameInfoPage.xaml index 6586bea..92bf583 100644 --- a/Crimson/Views/GameInfoPage.xaml +++ b/Crimson/Views/GameInfoPage.xaml @@ -80,7 +80,7 @@ - + @@ -91,11 +91,18 @@ - - - - - + + + + + + + + + + diff --git a/Crimson/Views/GameInfoPage.xaml.cs b/Crimson/Views/GameInfoPage.xaml.cs index 214ead6..374fbb6 100644 --- a/Crimson/Views/GameInfoPage.xaml.cs +++ b/Crimson/Views/GameInfoPage.xaml.cs @@ -2,6 +2,7 @@ using System.Linq; using Crimson.Core; using Crimson.Models; +using Crimson.Utils; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media.Imaging; @@ -21,7 +22,9 @@ public sealed partial class GameInfoPage : Page private readonly InstallManager _installer; private readonly LibraryManager _libraryManager; + private readonly Storage _storage; public Game Game { get; set; } + public bool IsInstalled { get; set; } = false; public GameInfoPage() { @@ -29,6 +32,7 @@ public GameInfoPage() _log = App.GetService(); _installer = App.GetService(); _libraryManager = App.GetService(); + _storage = App.GetService(); } private readonly ILogger _log; protected override void OnNavigatedTo(NavigationEventArgs e) @@ -163,22 +167,26 @@ private void CheckGameStatus(Game updatedGame) { PrimaryActionButtonText.Text = "Install"; PrimaryActionButtonIcon.Glyph = "\uE896"; + IsInstalled = false; } else if (Game.InstallStatus == InstallState.Installed) { PrimaryActionButtonText.Text = "Play"; PrimaryActionButtonIcon.Glyph = "\uE768"; + IsInstalled = true; } else if (Game.InstallStatus == InstallState.NeedUpdate) { PrimaryActionButtonText.Text = "Update"; PrimaryActionButtonIcon.Glyph = "\uE777"; + IsInstalled = true; } else if (Game.InstallStatus == InstallState.Broken) { PrimaryActionButtonText.Text = "Repair"; PrimaryActionButtonIcon.Glyph = "\uE90F"; + IsInstalled = true; } }); } @@ -240,4 +248,20 @@ private async void PrimaryButton_Click(object sender, RoutedEventArgs e) _installer.AddToQueue(new InstallItem(Game.AppName, ActionType.Install, InstallLocationText.Text)); _log.Information("GameInfoPage: Added {Game} to Installation Queue", Game.AppTitle); } + + private void UninstallBtn_Click(object sender, RoutedEventArgs e) + { + if (Game == null || Game.InstallStatus == InstallState.NotInstalled) return; + + _storage.InstalledGamesDictionary.TryGetValue(Game.AppName, out var installedGame); + + if (installedGame == null) + { + _log.Information("ProcessNext: Attempting to uninstall not installed game"); + return; + } + + _installer.AddToQueue(new InstallItem(Game.AppName, ActionType.Uninstall, installedGame.InstallPath)); + _log.Information("GameInfoPage: Added {Game} to Installation Queue", Game.AppTitle); + } } diff --git a/Crimson/Views/SettingsPage.xaml b/Crimson/Views/SettingsPage.xaml index d89c411..06a6954 100644 --- a/Crimson/Views/SettingsPage.xaml +++ b/Crimson/Views/SettingsPage.xaml @@ -73,7 +73,7 @@ - +