diff --git a/Wabbajack.CLI/Verbs/Changelog.cs b/Wabbajack.CLI/Verbs/Changelog.cs index 04d8adac..465e1032 100644 --- a/Wabbajack.CLI/Verbs/Changelog.cs +++ b/Wabbajack.CLI/Verbs/Changelog.cs @@ -75,7 +75,7 @@ namespace Wabbajack.CLI.Verbs var mdText = $"## {update.Version}\n\n" + - $"**Build at:** `{File.GetCreationTime(Update)}`\n\n" + + $"**Build at:** `{File.GetLastWriteTime(Update)}`\n\n" + "**Info**:\n\n" + $"- Download Size change: {downloadSizeChanges.ToFileSizeString()} (Total: {update.DownloadSize.ToFileSizeString()})\n" + $"- Install Size change: {installSizeChanges.ToFileSizeString()} (Total: {update.InstallSize.ToFileSizeString()})\n\n"; diff --git a/Wabbajack.Common/Logging.cs b/Wabbajack.Common/Logging.cs index 0be8267e..816ed898 100644 --- a/Wabbajack.Common/Logging.cs +++ b/Wabbajack.Common/Logging.cs @@ -30,7 +30,7 @@ namespace Wabbajack.Common private static readonly Subject LoggerSubj = new Subject(); public static IObservable LogMessages => LoggerSubj; - public static async Task InitalizeLogging() + public static async Task InitializeLogging() { _startTime = DateTime.Now; @@ -126,7 +126,7 @@ namespace Wabbajack.Common if (!LoggingSettings.LogToFile || LogFile == default) return; lock (_logLock) { - File.AppendAllText(LogFile.ToString(), $"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}\r\n"); + File.AppendAllText(LogFile.ToString(), $"{(DateTime.Now - _startTime).TotalSeconds:0.##} - {msg}\r\n", new UTF8Encoding(false, true)); } } diff --git a/Wabbajack.Common/Paths/AbsolutePath.cs b/Wabbajack.Common/Paths/AbsolutePath.cs index 74686425..074444b5 100644 --- a/Wabbajack.Common/Paths/AbsolutePath.cs +++ b/Wabbajack.Common/Paths/AbsolutePath.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -209,6 +209,21 @@ namespace Wabbajack.Common return new RelativePath(relPath); } + public bool IsChildOf(AbsolutePath? parent) + { + if (parent is null) return false; + var child = this; + if (child == parent) return true; + while (child.Parent.Exists) + { + if (child.Parent == parent) + { + return true; + } + child = child.Parent; + } + return false; + } public async Task ReadAllTextAsync() { diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index 151bb4b6..9588b4ac 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -38,7 +38,7 @@ namespace Wabbajack.Common static Utils() { - InitalizeLogging().Wait(); + InitializeLogging().Wait(); } private static readonly string[] Suffix = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; // Longs run out around EB diff --git a/Wabbajack.Lib/ABatchProcessor.cs b/Wabbajack.Lib/ABatchProcessor.cs index dcf10bb6..5c6b695a 100644 --- a/Wabbajack.Lib/ABatchProcessor.cs +++ b/Wabbajack.Lib/ABatchProcessor.cs @@ -174,7 +174,12 @@ namespace Wabbajack.Lib { Utils.Log("Installation has Started"); _isRunning.OnNext(true); - return await _Begin(_cancel.Token); + var task = await _Begin(_cancel.Token); + Utils.Log("Vacuuming databases"); + HashCache.VacuumDatabase(); + VirtualFile.VacuumDatabase(); + Utils.Log("Vacuuming completed"); + return task; } catch (Exception ex) { @@ -183,10 +188,6 @@ namespace Wabbajack.Lib } finally { - Utils.Log("Vacuuming databases"); - HashCache.VacuumDatabase(); - VirtualFile.VacuumDatabase(); - Utils.Log("Vacuuming completed"); _isRunning.OnNext(false); } }); diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs index 3faa3b9d..652efc17 100644 --- a/Wabbajack.Lib/MO2Installer.cs +++ b/Wabbajack.Lib/MO2Installer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; @@ -22,6 +22,7 @@ using File = Alphaleonis.Win32.Filesystem.File; using Path = Alphaleonis.Win32.Filesystem.Path; using SectionData = Wabbajack.Common.SectionData; using System.Collections.Generic; +using Wabbajack.Common.IO; using Wabbajack.Lib.ModListRegistry; using Wabbajack.VirtualFileSystem; @@ -69,21 +70,19 @@ namespace Wabbajack.Lib var otherGame = Game.CommonlyConfusedWith.Where(g => g.MetaData().IsInstalled).Select(g => g.MetaData()).FirstOrDefault(); if (otherGame != null) { - await Utils.Log(new CriticalFailureIntervention( - $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find a installed " + - $"copy of {otherGame.HumanFriendlyGameName}, did you install the wrong game?", - $"Could not locate {Game.HumanFriendlyGameName}")) - .Task; + Utils.Error(new CriticalFailureIntervention( + $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find an installed " + + $"copy of {otherGame.HumanFriendlyGameName}, did you install the wrong game?", + $"Could not locate {Game.HumanFriendlyGameName}")); } else { - await Utils.Log(new CriticalFailureIntervention( - $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed", - $"Could not locate {Game.HumanFriendlyGameName}")) - .Task; + Utils.Error(new CriticalFailureIntervention( + $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed.", + $"Could not locate {Game.HumanFriendlyGameName}")); } - Utils.Log("Exiting because we couldn't find the game folder."); + Utils.Error("Exiting because we couldn't find the game folder."); return false; } @@ -503,14 +502,24 @@ namespace Wabbajack.Lib await OutputFolder.Combine(directive.To).WriteAllTextAsync(data); } - public static IErrorResponse CheckValidInstallPath(AbsolutePath path, AbsolutePath? downloadFolder) + public static IErrorResponse CheckValidInstallPath(AbsolutePath path, AbsolutePath? downloadFolder, GameMetaData? game) { + // Check if null path + if (string.IsNullOrEmpty(path.ToString())) return ErrorResponse.Fail("Please select an install directory."); + + // Check if child of game folder + if (game?.TryGetGameLocation() != null && path.IsChildOf(game.TryGetGameLocation())) return ErrorResponse.Fail("Cannot install to game directory."); + + // Check if child of Program Files + if (path.IsChildOf(new AbsolutePath(KnownFolders.ProgramFiles.Path))) return ErrorResponse.Fail("Cannot install to Program Files directory."); + + // If the folder doesn't exist, it's empty so we don't need to check further if (!path.Exists) return ErrorResponse.Success; // Check folder does not have a Wabbajack ModList if (path.EnumerateFiles(false).Where(file => file.Exists).Any(file => file.Extension == Consts.ModListExtension)) { - return ErrorResponse.Fail($"Cannot install into a folder with a Wabbajack ModList inside of it"); + return ErrorResponse.Fail($"Cannot install into a folder with a Wabbajack ModList inside of it."); } // Check if folder is empty diff --git a/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs b/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs index 8bd5554d..579c9afe 100644 --- a/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs +++ b/Wabbajack.Lib/StatusMessages/CriticalFailureIntervention.cs @@ -12,13 +12,17 @@ namespace Wabbajack.Lib private TaskCompletionSource _source = new TaskCompletionSource(); public Task Task => _source.Task; - public CriticalFailureIntervention(string description, string title) + public CriticalFailureIntervention(string description, string title, bool exit = false) { ExtendedDescription = description; ShortDescription = title; + ExitApplication = exit; } + public override string ShortDescription { get; } public override string ExtendedDescription { get; } + public bool ExitApplication { get; } + public void Cancel() { _source.SetResult(ConfirmationIntervention.Choice.Abort); diff --git a/Wabbajack.Test/MO2Tests.cs b/Wabbajack.Test/MO2Tests.cs index fa075fc8..9e28b7f5 100644 --- a/Wabbajack.Test/MO2Tests.cs +++ b/Wabbajack.Test/MO2Tests.cs @@ -13,14 +13,14 @@ namespace Wabbajack.Test public async Task CheckValidInstallPath_Empty() { await using var tempDir = await TempFolder.Create(); - Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadFolder: null).Succeeded); + Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, null, null).Succeeded); } [Fact] public async Task CheckValidInstallPath_DoesNotExist() { await using var tempDir = await TempFolder.Create(); - Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir.Combine("Subfolder"), downloadFolder: null).Succeeded); + Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir.Combine("Subfolder"), null, null).Succeeded); } [Fact] @@ -29,7 +29,7 @@ namespace Wabbajack.Test await using var tempDir = await TempFolder.Create(); await using var mo2 = await tempDir.Dir.Combine("ModOrganizer.exe").Create(); await using var molist = await tempDir.Dir.Combine(((RelativePath)"modlist")).WithExtension(Consts.ModListExtension).Create(); - Assert.False(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadFolder: null).Succeeded); + Assert.False(MO2Installer.CheckValidInstallPath(tempDir.Dir, null, null).Succeeded); } [Fact] @@ -37,7 +37,7 @@ namespace Wabbajack.Test { await using var tempDir = await TempFolder.Create(); await using var tmp = await tempDir.Dir.Combine(Consts.ModOrganizer2Exe).Create(); - Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadFolder: null).Succeeded); + Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, null, null).Succeeded); } [Fact] @@ -47,7 +47,7 @@ namespace Wabbajack.Test await tempDir.Dir.DeleteDirectory(); tempDir.Dir.CreateDirectory(); await using var tmp = await tempDir.Dir.Combine($"someFile.txt").Create(); - Assert.False(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadFolder: null).Succeeded); + Assert.False(MO2Installer.CheckValidInstallPath(tempDir.Dir, null, null).Succeeded); } [Fact] @@ -57,7 +57,14 @@ namespace Wabbajack.Test var downloadsFolder = tempDir.Dir.Combine("downloads"); downloadsFolder.CreateDirectory(); await using var tmp = await tempDir.Dir.Combine($"downloads/someFile.txt").Create(); - Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadFolder: downloadsFolder).Succeeded); + Assert.True(MO2Installer.CheckValidInstallPath(tempDir.Dir, downloadsFolder, null).Succeeded); + } + + [Fact] + public async Task CheckValidInstallPath_NullPath() + { + var tempDir = new AbsolutePath("", true); + Assert.True(MO2Installer.CheckValidInstallPath(tempDir, null, null).Succeeded == false); } #endregion } diff --git a/Wabbajack/App.xaml.cs b/Wabbajack/App.xaml.cs index 2a11ecae..d1593ac8 100644 --- a/Wabbajack/App.xaml.cs +++ b/Wabbajack/App.xaml.cs @@ -16,7 +16,7 @@ namespace Wabbajack Consts.LogsFolder.CreateDirectory(); LoggingSettings.LogToFile = true; - Utils.InitalizeLogging().Wait(); + Utils.InitializeLogging().Wait(); CLIOld.ParseOptions(Environment.GetCommandLineArgs()); if (CLIArguments.Help) diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index dd264a7d..93ed3a73 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -97,13 +97,19 @@ namespace Wabbajack public InstallerVM(MainWindowVM mainWindowVM) : base(mainWindowVM) { - - if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower()) + if (AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(KnownFolders.Downloads.Path))) { Utils.Error(new CriticalFailureIntervention( "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " + - "conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.", - "Cannot run inside Downloads")); + "conflict with the operations Wabbajack needs to perform. Please move Wabbajack outside of your Downloads folder and then restart the app.", + "Cannot run inside Downloads", true)); + } + else if (AbsolutePath.EntryPoint.IsChildOf(new AbsolutePath(KnownFolders.SkyDrive.Path))) + { + Utils.Error(new CriticalFailureIntervention( + $"Wabbajack is running inside a OneDrive folder \"{new AbsolutePath(KnownFolders.SkyDrive.Path)}\". This folder is known to cause issues with Wabbajack. " + + "Please move Wabbajack outside of your OneDrive folder and then restart the app.", + "Cannot run inside OneDrive", true)); } MWVM = mainWindowVM; diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs index c183b679..1743218e 100644 --- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs +++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs @@ -63,7 +63,7 @@ namespace Wabbajack this.WhenAny(x => x.DownloadLocation.TargetPath), resultSelector: (target, download) => (target, download)) .ObserveOn(RxApp.TaskpoolScheduler) - .Select(i => MO2Installer.CheckValidInstallPath(i.target, i.download)) + .Select(i => MO2Installer.CheckValidInstallPath(i.target, i.download, Parent.ModList?.SourceModList.GameType.MetaData())) .ObserveOnGuiThread(); _CanInstall = Observable.CombineLatest( @@ -83,7 +83,7 @@ namespace Wabbajack { if (DownloadLocation.TargetPath == default || DownloadLocation.TargetPath == installPath) { - DownloadLocation.TargetPath = installPath.Combine("downloads"); + if (installPath.Exists) DownloadLocation.TargetPath = installPath.Combine("downloads"); } }) .DisposeWith(CompositeDisposable); diff --git a/Wabbajack/View Models/MainWindowVM.cs b/Wabbajack/View Models/MainWindowVM.cs index 09846dfd..ee8df51b 100644 --- a/Wabbajack/View Models/MainWindowVM.cs +++ b/Wabbajack/View Models/MainWindowVM.cs @@ -77,9 +77,9 @@ namespace Wabbajack .Bind(Log) .Subscribe() .DisposeWith(CompositeDisposable); - + Utils.LogMessages - .OfType() + .Where(a => a is IUserIntervention or CriticalFailureIntervention) .ObserveOnGuiThread() .SelectTask(async msg => { @@ -93,9 +93,9 @@ namespace Wabbajack Utils.Error(ex, $"Error while handling user intervention of type {msg?.GetType()}"); try { - if (!msg.Handled) + if (msg is IUserIntervention {Handled: false} intervention) { - msg.Cancel(); + intervention.Cancel(); } } catch (Exception cancelEx) diff --git a/Wabbajack/View Models/UserInterventionHandlers.cs b/Wabbajack/View Models/UserInterventionHandlers.cs index 64ab01fd..618d926f 100644 --- a/Wabbajack/View Models/UserInterventionHandlers.cs +++ b/Wabbajack/View Models/UserInterventionHandlers.cs @@ -10,6 +10,7 @@ using System.Windows.Threading; using CefSharp; using ReactiveUI; using Wabbajack.Common; +using Wabbajack.Common.StatusFeed; using Wabbajack.Lib; using Wabbajack.Lib.Downloaders; using Wabbajack.Lib.LibCefHelpers; @@ -57,12 +58,12 @@ namespace Wabbajack MainWindow.NavigateTo(oldPane); } - public async Task Handle(IUserIntervention msg) + public async Task Handle(IStatusMessage msg) { switch (msg) { case RequestNexusAuthorization c: - await WrapBrowserJob(msg, async (vm, cancel) => + await WrapBrowserJob(c, async (vm, cancel) => { await vm.Driver.WaitForInitialized(); var key = await NexusApiClient.SetupNexusLogin(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); @@ -70,13 +71,13 @@ namespace Wabbajack }); break; case ManuallyDownloadNexusFile c: - await WrapBrowserJob(msg, (vm, cancel) => HandleManualNexusDownload(vm, cancel, c)); + await WrapBrowserJob(c, (vm, cancel) => HandleManualNexusDownload(vm, cancel, c)); break; case ManuallyDownloadFile c: - await WrapBrowserJob(msg, (vm, cancel) => HandleManualDownload(vm, cancel, c)); + await WrapBrowserJob(c, (vm, cancel) => HandleManualDownload(vm, cancel, c)); break; case AbstractNeedsLoginDownloader.RequestSiteLogin c: - await WrapBrowserJob(msg, async (vm, cancel) => + await WrapBrowserJob(c, async (vm, cancel) => { await vm.Driver.WaitForInitialized(); var data = await c.Downloader.GetAndCacheCookies(new CefSharpWrapper(vm.Browser), m => vm.Instructions = m, cancel.Token); @@ -87,6 +88,7 @@ namespace Wabbajack MessageBox.Show(c.ExtendedDescription, c.ShortDescription, MessageBoxButton.OK, MessageBoxImage.Error); c.Cancel(); + if (c.ExitApplication) await MainWindow.ShutdownApplication(); break; case ConfirmationIntervention c: break;