From 695e7ede8b65cd6e1ebc3833c91597a8b97ebdfd Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 24 May 2022 20:59:15 -0600 Subject: [PATCH] Add other profiles and always enabled to Compiler screen --- .../View Models/Compilers/CompilerVM.cs | 103 ++++++++---------- .../Controls/RemovableItemView.xaml | 24 ++++ .../Controls/RemovableItemView.xaml.cs | 23 ++++ .../Controls/RemovableItemViewModel.cs | 19 ++++ .../Views/Compilers/CompilerView.xaml | 31 ++++++ .../Views/Compilers/CompilerView.xaml.cs | 99 ++++++++++++++++- .../CompilerSettingsInferencer.cs | 84 ++++++++++++++ .../ServiceExtensions.cs | 1 + 8 files changed, 324 insertions(+), 60 deletions(-) create mode 100644 Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml create mode 100644 Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml.cs create mode 100644 Wabbajack.App.Wpf/View Models/Controls/RemovableItemViewModel.cs create mode 100644 Wabbajack.Compiler/CompilerSettingsInferencer.cs diff --git a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs b/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs index e15eb103..68ecb31a 100644 --- a/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack.App.Wpf/View Models/Compilers/CompilerVM.cs @@ -55,6 +55,7 @@ namespace Wabbajack private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly ResourceMonitor _resourceMonitor; + private readonly CompilerSettingsInferencer _inferencer; [Reactive] public CompilerState State { get; set; } @@ -86,6 +87,7 @@ namespace Wabbajack [Reactive] public bool IsMO2Compilation { get; set; } [Reactive] public RelativePath[] AlwaysEnabled { get; set; } = Array.Empty(); + [Reactive] public string[] OtherProfiles { get; set; } = Array.Empty(); [Reactive] public AbsolutePath Source { get; set; } @@ -99,7 +101,8 @@ namespace Wabbajack public ReadOnlyObservableCollection StatusList => _resourceMonitor.Tasks; public CompilerVM(ILogger logger, DTOSerializer dtos, SettingsManager settingsManager, - IServiceProvider serviceProvider, LogStream loggerProvider, ResourceMonitor resourceMonitor) : base(logger) + IServiceProvider serviceProvider, LogStream loggerProvider, ResourceMonitor resourceMonitor, + CompilerSettingsInferencer inferencer) : base(logger) { _logger = logger; _dtos = dtos; @@ -107,6 +110,7 @@ namespace Wabbajack _serviceProvider = serviceProvider; LoggerProvider = loggerProvider; _resourceMonitor = resourceMonitor; + _inferencer = inferencer; BackCommand = ReactiveCommand.CreateFromTask(async () => @@ -160,69 +164,26 @@ namespace Wabbajack }); } - private async Task InferModListFromLocation(AbsolutePath settingsFile) + private async Task InferModListFromLocation(AbsolutePath path) { - if (settingsFile == default) return; + using var _ = LoadingLock.WithLoading(); + if (path == default || path.FileName != "modlist.txt".ToRelativePath()) + return; - using var ll = LoadingLock.WithLoading(); - if (settingsFile.FileName == "modlist.txt".ToRelativePath() && settingsFile.Depth > 3) - { - var mo2Folder = settingsFile.Parent.Parent.Parent; - var mo2Ini = mo2Folder.Combine(Consts.MO2IniName); - if (mo2Ini.FileExists()) - { - var iniData = mo2Ini.LoadIniFile(); - - var general = iniData["General"]; - - BaseGame = GameRegistry.GetByFuzzyName(general["gameName"].FromMO2Ini()).Game; - Source = mo2Folder; - - SelectedProfile = general["selected_profile"].FromMO2Ini(); - GamePath = general["gamePath"].FromMO2Ini().ToAbsolutePath(); - ModListName = SelectedProfile; - - var settings = iniData["Settings"]; - var downloadLocation = settings["download_directory"].FromMO2Ini().ToAbsolutePath(); - - if (downloadLocation == default) - downloadLocation = Source.Combine("downloads"); - - DownloadLocation.TargetPath = downloadLocation; - IsMO2Compilation = true; - - - - AlwaysEnabled = Array.Empty(); - // Find Always Enabled mods - foreach (var modFolder in mo2Folder.Combine("mods").EnumerateDirectories()) - { - var iniFile = modFolder.Combine("meta.ini"); - if (!iniFile.FileExists()) continue; - - var data = iniFile.LoadIniFile(); - var generalModData = data["General"]; - if ((generalModData["notes"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false) || - (generalModData["comments"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false)) - AlwaysEnabled = AlwaysEnabled.Append(modFolder.RelativeTo(mo2Folder)).ToArray(); - } - - var otherProfilesFile = settingsFile.Parent.Combine("otherprofiles.txt"); - if (otherProfilesFile.FileExists()) - { - OtherProfiles = await otherProfilesFile.ReadAllLinesAsync().ToArray(); - } - - if (mo2Folder.Depth > 1) - OutputLocation.TargetPath = mo2Folder.Parent; - - await SaveSettingsFile(); - ModlistLocation.TargetPath = SettingsOutputLocation; - } - } + var settings = await _inferencer.InferModListFromLocation(path); + if (settings == null) return; + BaseGame = settings.Game; + ModListName = settings.ModListName; + Source = settings.Source; + DownloadLocation.TargetPath = settings.Downloads; + OutputLocation.TargetPath = settings.OutputFile; + SelectedProfile = settings.Profile; + OtherProfiles = settings.OtherProfiles; + AlwaysEnabled = settings.AlwaysEnabled; } + private async Task StartCompilation() { var tsk = Task.Run(async () => @@ -306,5 +267,29 @@ namespace Wabbajack OtherProfiles = OtherProfiles.ToArray() }; } + + #region ListOps + + public void AddOtherProfile(string profile) + { + OtherProfiles = (OtherProfiles ?? Array.Empty()).Append(profile).Distinct().ToArray(); + } + + public void RemoveProfile(string profile) + { + OtherProfiles = OtherProfiles.Where(p => p != profile).ToArray(); + } + + public void AddAlwaysEnabled(RelativePath path) + { + AlwaysEnabled = (AlwaysEnabled ?? Array.Empty()).Append(path).Distinct().ToArray(); + } + + public void RemoveAlwaysEnabled(RelativePath path) + { + AlwaysEnabled = AlwaysEnabled.Where(p => p != path).ToArray(); + } + + #endregion } } diff --git a/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml new file mode 100644 index 00000000..54c7ffd2 --- /dev/null +++ b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml.cs b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml.cs new file mode 100644 index 00000000..9dcb281c --- /dev/null +++ b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemView.xaml.cs @@ -0,0 +1,23 @@ +using System.Reactive.Disposables; +using System.Windows.Controls; +using ReactiveUI; + +namespace Wabbajack.View_Models.Controls; + +public partial class RemovableItemView : ReactiveUserControl +{ + public RemovableItemView() + { + InitializeComponent(); + + this.WhenActivated(disposables => + { + this.BindStrict(ViewModel, vm => vm.Text, view => view.DisplayText.Text) + .DisposeWith(disposables); + + + }); + + DeleteButton.Command = ReactiveCommand.Create(() => ViewModel.RemoveFn()); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/View Models/Controls/RemovableItemViewModel.cs b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemViewModel.cs new file mode 100644 index 00000000..b7ba6581 --- /dev/null +++ b/Wabbajack.App.Wpf/View Models/Controls/RemovableItemViewModel.cs @@ -0,0 +1,19 @@ +using System; +using ReactiveUI.Fody.Helpers; + +namespace Wabbajack.View_Models.Controls; + +public class RemovableItemViewModel : ViewModel +{ + + public string Text { get; } + + public Action RemoveFn { get; } + + public RemovableItemViewModel(string text, Action removeFn) + { + Text = text; + RemoveFn = removeFn; + + } +} \ No newline at end of file diff --git a/Wabbajack.App.Wpf/Views/Compilers/CompilerView.xaml b/Wabbajack.App.Wpf/Views/Compilers/CompilerView.xaml index 8747618b..5f3e01e0 100644 --- a/Wabbajack.App.Wpf/Views/Compilers/CompilerView.xaml +++ b/Wabbajack.App.Wpf/Views/Compilers/CompilerView.xaml @@ -10,6 +10,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:rxui="http://reactiveui.net" xmlns:wabbacommon="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common" + xmlns:controls1="clr-namespace:Wabbajack.View_Models.Controls" d:DataContext="{d:DesignInstance local:CompilerVM}" d:DesignHeight="450" d:DesignWidth="800" @@ -142,6 +143,36 @@ Text="MachineUrl" ToolTip="If this box has a value the modlist will be published to this MachineUrl after compilation" /> + + + + + + + + + + + + + + + + + + + + + + + + + + vm.MachineUrl, view => view.MachineUrl.Text) .DisposeWith(disposables); + ViewModel.WhenAnyValue(vm => vm.AlwaysEnabled) + .WhereNotNull() + .Select(itms => itms.Select(itm => new RemovableItemViewModel(itm.ToString(), () => ViewModel.RemoveAlwaysEnabled(itm))).ToArray()) + .BindToStrict(this, view => view.AlwaysEnabled.ItemsSource) + .DisposeWith(disposables); + + AddAlwaysEnabled.Command = ReactiveCommand.CreateFromTask(async () => await AddAlwaysEnabledCommand()); + + ViewModel.WhenAnyValue(vm => vm.OtherProfiles) + .WhereNotNull() + .Select(itms => itms.Select(itm => new RemovableItemViewModel(itm.ToString(), () => ViewModel.RemoveProfile(itm))).ToArray()) + .BindToStrict(this, view => view.OtherProfiles.ItemsSource) + .DisposeWith(disposables); + + AddOtherProfile.Command = ReactiveCommand.CreateFromTask(async () => await AddOtherProfileCommand()); + }); } + + public async Task AddAlwaysEnabledCommand() + { + AbsolutePath dirPath; + + if (ViewModel!.Source != default && ViewModel.Source.Combine("mods").DirectoryExists()) + { + dirPath = ViewModel.Source.Combine("mods"); + } + else + { + dirPath = ViewModel.Source; + } + + var dlg = new CommonOpenFileDialog + { + Title = "Please select a folder", + IsFolderPicker = true, + InitialDirectory = dirPath.ToString(), + AddToMostRecentlyUsedList = false, + AllowNonFileSystemItems = false, + DefaultDirectory = dirPath.ToString(), + EnsureFileExists = true, + EnsurePathExists = true, + EnsureReadOnly = false, + EnsureValidNames = true, + Multiselect = false, + ShowPlacesList = true, + }; + + if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return; + var selectedPath = dlg.FileNames.First().ToAbsolutePath(); + + if (!selectedPath.InFolder(ViewModel.Source)) return; + + ViewModel.AddAlwaysEnabled(selectedPath.RelativeTo(ViewModel.Source)); + } + + public async Task AddOtherProfileCommand() + { + AbsolutePath dirPath; + + if (ViewModel!.Source != default && ViewModel.Source.Combine("mods").DirectoryExists()) + { + dirPath = ViewModel.Source.Combine("mods"); + } + else + { + dirPath = ViewModel.Source; + } + + var dlg = new CommonOpenFileDialog + { + Title = "Please select a profile folder", + IsFolderPicker = true, + InitialDirectory = dirPath.ToString(), + AddToMostRecentlyUsedList = false, + AllowNonFileSystemItems = false, + DefaultDirectory = dirPath.ToString(), + EnsureFileExists = true, + EnsurePathExists = true, + EnsureReadOnly = false, + EnsureValidNames = true, + Multiselect = false, + ShowPlacesList = true, + }; + + if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return; + var selectedPath = dlg.FileNames.First().ToAbsolutePath(); + + if (!selectedPath.InFolder(ViewModel.Source.Combine("profiles"))) return; + + ViewModel.AddOtherProfile(selectedPath.FileName.ToString()); + } } } diff --git a/Wabbajack.Compiler/CompilerSettingsInferencer.cs b/Wabbajack.Compiler/CompilerSettingsInferencer.cs new file mode 100644 index 00000000..2d8c298c --- /dev/null +++ b/Wabbajack.Compiler/CompilerSettingsInferencer.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.Common; +using Wabbajack.DTOs; +using Wabbajack.Installer; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; + +namespace Wabbajack.Compiler; + +/// +/// Given a modlist.txt file, infer as much of CompilerSettings as possible +/// +public class CompilerSettingsInferencer +{ + private readonly ILogger _logger; + + public CompilerSettingsInferencer(ILogger logger) + { + _logger = logger; + + } + + public async Task InferModListFromLocation(AbsolutePath settingsFile) + { + var cs = new CompilerSettings(); + + if (settingsFile.FileName == "modlist.txt".ToRelativePath() && settingsFile.Depth > 3) + { + _logger.LogInformation("Inferencing basic settings"); + var mo2Folder = settingsFile.Parent.Parent.Parent; + var mo2Ini = mo2Folder.Combine(Consts.MO2IniName); + if (mo2Ini.FileExists()) + { + var iniData = mo2Ini.LoadIniFile(); + + var general = iniData["General"]; + + cs.Game = GameRegistry.GetByFuzzyName(general["gameName"].FromMO2Ini()).Game; + cs.Source = mo2Folder; + + var selectedProfile = general["selected_profile"].FromMO2Ini(); + //cs.GamePath = general["gamePath"].FromMO2Ini().ToAbsolutePath(); + cs.ModListName = selectedProfile; + + cs.OutputFile = cs.Source.Parent; + + var settings = iniData["Settings"]; + cs.Downloads = settings["download_directory"].FromMO2Ini().ToAbsolutePath(); + + if (cs.Downloads == default) + cs.Downloads = cs.Source.Combine("downloads"); + + _logger.LogInformation("Finding Always Enabled mods"); + cs.AlwaysEnabled = Array.Empty(); + // Find Always Enabled mods + foreach (var modFolder in mo2Folder.Combine("mods").EnumerateDirectories()) + { + var iniFile = modFolder.Combine("meta.ini"); + if (!iniFile.FileExists()) continue; + + var data = iniFile.LoadIniFile(); + var generalModData = data["General"]; + if ((generalModData["notes"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false) || + (generalModData["comments"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false)) + cs.AlwaysEnabled = cs.AlwaysEnabled.Append(modFolder.RelativeTo(mo2Folder)).ToArray(); + } + + _logger.LogInformation("Finding other profiles"); + var otherProfilesFile = settingsFile.Parent.Combine("otherprofiles.txt"); + if (otherProfilesFile.FileExists()) + { + cs.OtherProfiles = await otherProfilesFile.ReadAllLinesAsync().ToArray(); + } + } + + return cs; + } + + return null; + } +} \ No newline at end of file diff --git a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs index 00130379..149de66f 100644 --- a/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs +++ b/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs @@ -154,6 +154,7 @@ public static class ServiceExtensions service.AddScoped(); service.AddScoped(); service.AddScoped(); + service.AddSingleton(); // Application Info var version =