From 295b629169c58f1f1849e9f2e6c660320e3a835b Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 2 Nov 2021 23:03:41 -0600 Subject: [PATCH] Several UI fixes --- Wabbajack.App/Models/SettingsManager.cs | 2 + .../Screens/StandardInstallationViewModel.cs | 10 +++-- .../InstallConfigurationViewModel.cs | 21 +++++++++-- .../ViewModels/MainWindowViewModel.cs | 4 +- Wabbajack.FileExtractor/FileExtractor.cs | 2 +- Wabbajack.Installer/AInstaller.cs | 37 ++++++++++++------- Wabbajack.Installer/StandardInstaller.cs | 10 ++--- Wabbajack.RateLimiter/Percent.cs | 2 +- Wabbajack.VFS/Context.cs | 2 +- 9 files changed, 59 insertions(+), 31 deletions(-) diff --git a/Wabbajack.App/Models/SettingsManager.cs b/Wabbajack.App/Models/SettingsManager.cs index 15335685..7ca49047 100644 --- a/Wabbajack.App/Models/SettingsManager.cs +++ b/Wabbajack.App/Models/SettingsManager.cs @@ -48,10 +48,12 @@ public class SettingsManager try { if (path.FileExists()) + { await using (var s = path.Open(FileMode.Open)) { return (await JsonSerializer.DeserializeAsync(s, _dtos.Options))!; } + } } catch (Exception ex) { diff --git a/Wabbajack.App/Screens/StandardInstallationViewModel.cs b/Wabbajack.App/Screens/StandardInstallationViewModel.cs index 8e56d212..8d489822 100644 --- a/Wabbajack.App/Screens/StandardInstallationViewModel.cs +++ b/Wabbajack.App/Screens/StandardInstallationViewModel.cs @@ -20,6 +20,7 @@ using Wabbajack.App.Utilities; using Wabbajack.App.ViewModels.SubViewModels; using Wabbajack.Common; using Wabbajack.Downloaders.GameFile; +using Wabbajack.Downloaders.Interfaces; using Wabbajack.DTOs; using Wabbajack.DTOs.DownloadStates; using Wabbajack.DTOs.JsonConverters; @@ -64,7 +65,7 @@ public class StandardInstallationViewModel : ViewModelBase this.WhenActivated(disposables => { - _updateTimer = new Timer(UpdateStatus, null, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(250)); + _updateTimer = new Timer(UpdateStatus, null, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(100)); _updateTimer.DisposeWith(disposables); _slideTimer = new Timer(_ => @@ -108,7 +109,7 @@ public class StandardInstallationViewModel : ViewModelBase [Reactive] public Percent StepProgress { get; set; } = Percent.Zero; // Not Reactive, so we don't end up spamming the UI threads with events - public StatusUpdate _latestStatus { get; set; } = new("", Percent.Zero, Percent.Zero); + public StatusUpdate _latestStatus = new("", Percent.Zero, Percent.Zero); public void Receive(StartInstallation msg) { @@ -117,11 +118,12 @@ public class StandardInstallationViewModel : ViewModelBase private void UpdateStatus(object? state) { - Dispatcher.UIThread.Post(() => { + Dispatcher.UIThread.Post(() => + { StepsProgress = _latestStatus.StepsProgress; StepProgress = _latestStatus.StepProgress; StatusText = _latestStatus.StatusText; - }); + }, DispatcherPriority.Render); } private void NextSlide(int direction) diff --git a/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs b/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs index e1bb76b3..47a9b587 100644 --- a/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs +++ b/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs @@ -27,11 +27,13 @@ public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewMode { private readonly DTOSerializer _dtos; private readonly InstallationStateManager _stateManager; + private readonly SettingsManager _settingsManager; - public InstallConfigurationViewModel(DTOSerializer dtos, InstallationStateManager stateManager) + public InstallConfigurationViewModel(DTOSerializer dtos, InstallationStateManager stateManager, SettingsManager settingsManager) { _stateManager = stateManager; + _settingsManager = settingsManager; _dtos = dtos; Activator = new ViewModelActivator(); @@ -76,9 +78,20 @@ public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewMode settings.Select(s => s!.Downloads) .BindTo(this, vm => vm.Download) .DisposeWith(disposables); + + + LoadSettings().FireAndForget(); + }); } + private async Task LoadSettings() + { + var path = await _settingsManager.Load("last-install-path"); + if (path != default && path.FileExists()) + ModListPath = path; + } + [Reactive] public AbsolutePath ModListPath { get; set; } [Reactive] public AbsolutePath Install { get; set; } @@ -107,13 +120,15 @@ public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewMode if (metadataPath.FileExists()) metadata = _dtos.Deserialize(await metadataPath.ReadAllTextAsync()); - _stateManager.SetLastState(new InstallationConfigurationSetting + await _stateManager.SetLastState(new InstallationConfigurationSetting { ModList = ModListPath, Downloads = Download, Install = Install, Metadata = metadata - }).FireAndForget(); + }); + + await _settingsManager.Save("last-install-path", ModListPath); MessageBus.Current.SendMessage(new NavigateTo(typeof(StandardInstallationViewModel))); MessageBus.Current.SendMessage(new StartInstallation(ModListPath, Install, Download, metadata)); diff --git a/Wabbajack.App/ViewModels/MainWindowViewModel.cs b/Wabbajack.App/ViewModels/MainWindowViewModel.cs index ef36998b..530c2a56 100644 --- a/Wabbajack.App/ViewModels/MainWindowViewModel.cs +++ b/Wabbajack.App/ViewModels/MainWindowViewModel.cs @@ -59,8 +59,8 @@ public class MainWindowViewModel : ReactiveValidationObject, IActivatableViewMod this.WhenActivated(disposables => { BackButton = ReactiveCommand.Create(() => { Receive(new NavigateBack()); }, - this.ObservableForProperty(vm => vm.BreadCrumbs) - .Select(bc => bc.Value.Count() > 1)) + this.WhenAnyValue(vm => vm.BreadCrumbs) + .Select(bc => bc.Count() > 1)) .DisposeWith(disposables); SettingsButton = ReactiveCommand.Create(() => { Receive(new NavigateTo(typeof(SettingsViewModel))); }) diff --git a/Wabbajack.FileExtractor/FileExtractor.cs b/Wabbajack.FileExtractor/FileExtractor.cs index aa6d2db8..9a7347c8 100644 --- a/Wabbajack.FileExtractor/FileExtractor.cs +++ b/Wabbajack.FileExtractor/FileExtractor.cs @@ -321,6 +321,6 @@ public class FileExtractor await using var stream = await factory.GetStream(); await abs.WriteAllAsync(stream, token); return 0; - }, token); + }, token, progressFunction: updateProgress); } } \ No newline at end of file diff --git a/Wabbajack.Installer/AInstaller.cs b/Wabbajack.Installer/AInstaller.cs index 1f40b695..b6583172 100644 --- a/Wabbajack.Installer/AInstaller.cs +++ b/Wabbajack.Installer/AInstaller.cs @@ -87,7 +87,7 @@ public abstract class AInstaller public ModList ModList => _configuration.ModList; - public async Task NextStep(string statusText, long maxStepProgress) + public void NextStep(string statusText, long maxStepProgress) { _updateStopWatch.Restart(); MaxStepProgress = maxStepProgress; @@ -109,11 +109,20 @@ public abstract class AInstaller public abstract Task Begin(CancellationToken token); - public async Task ExtractModlist(CancellationToken token) + protected async Task ExtractModlist(CancellationToken token) { - await NextStep("Extracting Modlist", 100); ExtractedModlistFolder = _manager.CreateFolder(); - await _extractor.ExtractAll(_configuration.ModlistArchive, ExtractedModlistFolder, token, updateProgress: p => UpdateProgress((long)(p.Value * 100))); + await using var stream = _configuration.ModlistArchive.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + using var archive = new ZipArchive(stream, ZipArchiveMode.Read); + NextStep("Extracting Modlist", archive.Entries.Count); + foreach (var entry in archive.Entries) + { + var path = entry.FullName.ToRelativePath().RelativeTo(ExtractedModlistFolder); + path.Parent.CreateDirectory(); + await using var of = path.Open(FileMode.Create, FileAccess.Write, FileShare.None); + await entry.Open().CopyToAsync(of, token); + UpdateProgress(1); + } } public async Task LoadBytesFromPath(RelativePath path) @@ -157,7 +166,7 @@ public abstract class AInstaller /// protected async Task PrimeVFS() { - await NextStep("Priming VFS", 0); + NextStep("Priming VFS", 0); _vfs.AddKnown(_configuration.ModList.Directives.OfType().Select(d => d.ArchiveHashPath), HashedArchives); await _vfs.BackfillMissing(); @@ -165,7 +174,7 @@ public abstract class AInstaller public async Task BuildFolderStructure() { - await NextStep("Building Folder Structure", 0); + NextStep("Building Folder Structure", 0); _logger.LogInformation("Building Folder Structure"); ModList.Directives .Where(d => d.To.Depth > 1) @@ -176,7 +185,7 @@ public abstract class AInstaller public async Task InstallArchives(CancellationToken token) { - await NextStep("Installing files", ModList.Directives.Sum(d => d.Size)); + NextStep("Installing files", ModList.Directives.Sum(d => d.Size)); var grouped = ModList.Directives .OfType() .Select(a => new {VF = _vfs.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a}) @@ -278,7 +287,7 @@ public abstract class AInstaller } _logger.LogInformation("Downloading {count} archives", missing.Count); - await NextStep("Downloading files", missing.Count); + NextStep("Downloading files", missing.Count); await missing .OrderBy(a => a.Size) @@ -339,7 +348,7 @@ public abstract class AInstaller public async Task HashArchives(CancellationToken token) { - await NextStep("Hashing Archives", 0); + NextStep("Hashing Archives", 0); _logger.LogInformation("Looking for files to hash"); var allFiles = _configuration.Downloads.EnumerateFiles() @@ -390,7 +399,7 @@ public abstract class AInstaller var savePath = (RelativePath) "saves"; var existingFiles = _configuration.Install.EnumerateFiles().ToList(); - await NextStep("Optimizing Modlist: Looking for files to delete", existingFiles.Count); + NextStep("Optimizing Modlist: Looking for files to delete", existingFiles.Count); await existingFiles .PDoAll(async f => { @@ -409,7 +418,7 @@ public abstract class AInstaller }); _logger.LogInformation("Cleaning empty folders"); - await NextStep("Optimizing Modlist: Cleaning empty folders", indexed.Keys.Count); + NextStep("Optimizing Modlist: Cleaning empty folders", indexed.Keys.Count); var expectedFolders = (indexed.Keys .Select(f => f.RelativeTo(_configuration.Install)) // We ignore the last part of the path, so we need a dummy file name @@ -444,10 +453,9 @@ public abstract class AInstaller var existingfiles = _configuration.Install.EnumerateFiles().ToHashSet(); - await NextStep("Optimizing Modlist: Removing redundant directives", indexed.Count); + NextStep("Optimizing Modlist: Removing redundant directives", indexed.Count); await indexed.Values.PMapAll(async d => { - UpdateProgress(1); // Bit backwards, but we want to return null for // all files we *want* installed. We return the files // to remove from the install list. @@ -458,12 +466,13 @@ public abstract class AInstaller }) .Do(d => { + UpdateProgress(1); if (d != null) indexed.Remove(d.To); }); _logger.LogInformation("Optimized {optimized} directives to {indexed} required", ModList.Directives.Length, indexed.Count); - await NextStep("Finalizing modlist optimization", 0); + NextStep("Finalizing modlist optimization", 0); var requiredArchives = indexed.Values.OfType() .GroupBy(d => d.ArchiveHashPath.Hash) .Select(d => d.Key) diff --git a/Wabbajack.Installer/StandardInstaller.cs b/Wabbajack.Installer/StandardInstaller.cs index 414fd594..cb2d0802 100644 --- a/Wabbajack.Installer/StandardInstaller.cs +++ b/Wabbajack.Installer/StandardInstaller.cs @@ -38,14 +38,14 @@ public class StandardInstaller : AInstaller base(logger, config, gameLocator, extractor, jsonSerializer, vfs, fileHashCache, downloadDispatcher, parallelOptions, wjClient) { - MaxSteps = 13; + MaxSteps = 14; } public override async Task Begin(CancellationToken token) { if (token.IsCancellationRequested) return false; await _wjClient.SendMetric(MetricNames.BeginInstall, ModList.Name); - await NextStep("Configuring Installer", 0); + NextStep("Configuring Installer", 0); _logger.LogInformation("Configuring Processor"); if (_configuration.GameFolder == default) @@ -106,7 +106,7 @@ public class StandardInstaller : AInstaller await PrimeVFS(); - BuildFolderStructure(); + await BuildFolderStructure(); await InstallArchives(token); @@ -129,7 +129,7 @@ public class StandardInstaller : AInstaller await ExtractedModlistFolder!.DisposeAsync(); await _wjClient.SendMetric(MetricNames.FinishInstall, ModList.Name); - await NextStep("Finished", 1); + NextStep("Finished", 1); _logger.LogInformation("Finished Installation"); return true; } @@ -259,7 +259,7 @@ public class StandardInstaller : AInstaller private async Task InstallIncludedFiles(CancellationToken token) { _logger.LogInformation("Writing inline files"); - await NextStep("Installing Included Files", ModList.Directives.OfType().Count()); + NextStep("Installing Included Files", ModList.Directives.OfType().Count()); await ModList.Directives .OfType() .PDoAll(async directive => diff --git a/Wabbajack.RateLimiter/Percent.cs b/Wabbajack.RateLimiter/Percent.cs index aa6702ee..ecb2d55f 100644 --- a/Wabbajack.RateLimiter/Percent.cs +++ b/Wabbajack.RateLimiter/Percent.cs @@ -30,7 +30,7 @@ public readonly struct Percent : IComparable, IEquatable public static bool InRange(double d) { - return d >= 0 || d <= 1; + return d is >= 0 or <= 1; } public static Percent operator +(Percent c1, Percent c2) diff --git a/Wabbajack.VFS/Context.cs b/Wabbajack.VFS/Context.cs index cf9b1fd2..fa99d0b6 100644 --- a/Wabbajack.VFS/Context.cs +++ b/Wabbajack.VFS/Context.cs @@ -172,7 +172,7 @@ public class Context _knownArchives.TryAdd(key, value); } - public async Task BackfillMissing() + public async ValueTask BackfillMissing() { var newFiles = _knownArchives.ToDictionary(kv => kv.Key, kv => new VirtualFile