Add other profiles and always enabled to Compiler screen

This commit is contained in:
Timothy Baldridge 2022-05-24 20:59:15 -06:00
parent 8cceacf78d
commit 695e7ede8b
8 changed files with 324 additions and 60 deletions

View File

@ -55,6 +55,7 @@ namespace Wabbajack
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<CompilerVM> _logger; private readonly ILogger<CompilerVM> _logger;
private readonly ResourceMonitor _resourceMonitor; private readonly ResourceMonitor _resourceMonitor;
private readonly CompilerSettingsInferencer _inferencer;
[Reactive] [Reactive]
public CompilerState State { get; set; } public CompilerState State { get; set; }
@ -86,6 +87,7 @@ namespace Wabbajack
[Reactive] public bool IsMO2Compilation { get; set; } [Reactive] public bool IsMO2Compilation { get; set; }
[Reactive] public RelativePath[] AlwaysEnabled { get; set; } = Array.Empty<RelativePath>(); [Reactive] public RelativePath[] AlwaysEnabled { get; set; } = Array.Empty<RelativePath>();
[Reactive] public string[] OtherProfiles { get; set; } = Array.Empty<string>(); [Reactive] public string[] OtherProfiles { get; set; } = Array.Empty<string>();
[Reactive] public AbsolutePath Source { get; set; } [Reactive] public AbsolutePath Source { get; set; }
@ -99,7 +101,8 @@ namespace Wabbajack
public ReadOnlyObservableCollection<CPUDisplayVM> StatusList => _resourceMonitor.Tasks; public ReadOnlyObservableCollection<CPUDisplayVM> StatusList => _resourceMonitor.Tasks;
public CompilerVM(ILogger<CompilerVM> logger, DTOSerializer dtos, SettingsManager settingsManager, public CompilerVM(ILogger<CompilerVM> 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; _logger = logger;
_dtos = dtos; _dtos = dtos;
@ -107,6 +110,7 @@ namespace Wabbajack
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
LoggerProvider = loggerProvider; LoggerProvider = loggerProvider;
_resourceMonitor = resourceMonitor; _resourceMonitor = resourceMonitor;
_inferencer = inferencer;
BackCommand = BackCommand =
ReactiveCommand.CreateFromTask(async () => 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(); var settings = await _inferencer.InferModListFromLocation(path);
if (settingsFile.FileName == "modlist.txt".ToRelativePath() && settingsFile.Depth > 3) if (settings == null) return;
{
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<RelativePath>();
// 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;
}
}
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() private async Task StartCompilation()
{ {
var tsk = Task.Run(async () => var tsk = Task.Run(async () =>
@ -306,5 +267,29 @@ namespace Wabbajack
OtherProfiles = OtherProfiles.ToArray() OtherProfiles = OtherProfiles.ToArray()
}; };
} }
#region ListOps
public void AddOtherProfile(string profile)
{
OtherProfiles = (OtherProfiles ?? Array.Empty<string>()).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<RelativePath>()).Append(path).Distinct().ToArray();
}
public void RemoveAlwaysEnabled(RelativePath path)
{
AlwaysEnabled = AlwaysEnabled.Where(p => p != path).ToArray();
}
#endregion
} }
} }

View File

@ -0,0 +1,24 @@
<reactiveUi:ReactiveUserControl x:Class="Wabbajack.View_Models.Controls.RemovableItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack.View_Models.Controls"
x:TypeArguments="controls:RemovableItemViewModel"
xmlns:reactiveUi="http://reactiveui.net"
xmlns:controls="clr-namespace:Wabbajack.View_Models.Controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
mc:Ignorable="d"
d:DesignHeight="28" d:DesignWidth="300">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Name="DeleteButton">
<iconPacks:Material Kind="Delete"></iconPacks:Material>
</Button>
<TextBlock Margin="10, 0, 0, 0" Grid.Column="1" Name="DisplayText" VerticalAlignment="Center"></TextBlock>
</Grid>
</reactiveUi:ReactiveUserControl>

View File

@ -0,0 +1,23 @@
using System.Reactive.Disposables;
using System.Windows.Controls;
using ReactiveUI;
namespace Wabbajack.View_Models.Controls;
public partial class RemovableItemView : ReactiveUserControl<RemovableItemViewModel>
{
public RemovableItemView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.BindStrict(ViewModel, vm => vm.Text, view => view.DisplayText.Text)
.DisposeWith(disposables);
});
DeleteButton.Command = ReactiveCommand.Create(() => ViewModel.RemoveFn());
}
}

View File

@ -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;
}
}

View File

@ -10,6 +10,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:rxui="http://reactiveui.net" xmlns:rxui="http://reactiveui.net"
xmlns:wabbacommon="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common" xmlns:wabbacommon="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common"
xmlns:controls1="clr-namespace:Wabbajack.View_Models.Controls"
d:DataContext="{d:DesignInstance local:CompilerVM}" d:DataContext="{d:DesignInstance local:CompilerVM}"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
@ -142,6 +143,36 @@
Text="MachineUrl" Text="MachineUrl"
ToolTip="If this box has a value the modlist will be published to this MachineUrl after compilation" /> ToolTip="If this box has a value the modlist will be published to this MachineUrl after compilation" />
<TextBox x:Name="MachineUrl" Style="{StaticResource ValueStyle}" /> <TextBox x:Name="MachineUrl" Style="{StaticResource ValueStyle}" />
<StackPanel Orientation="Horizontal">
<Button Name="AddOtherProfile">
<icon:Material Kind="Plus"></icon:Material>
</Button>
<Label>Other Profiles</Label>
</StackPanel>
<ListBox x:Name="OtherProfiles">
<ListBox.ItemTemplate>
<DataTemplate>
<controls1:RemovableItemView ViewModel="{Binding}" ></controls1:RemovableItemView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal">
<Button Name="AddAlwaysEnabled">
<icon:Material Kind="Plus"></icon:Material>
</Button>
<Label>Always Enabled Mods</Label>
</StackPanel>
<ListBox x:Name="AlwaysEnabled">
<ListBox.ItemTemplate>
<DataTemplate>
<controls1:RemovableItemView ViewModel="{Binding}" ></controls1:RemovableItemView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
<Border Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5" <Border Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5"

View File

@ -1,11 +1,18 @@
using System.Linq; using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using ReactiveUI; using ReactiveUI;
using System.Windows; using System.Windows;
using System.Windows.Forms;
using DynamicData; using DynamicData;
using Microsoft.WindowsAPICodePack.Dialogs;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.View_Models.Controls;
namespace Wabbajack namespace Wabbajack
{ {
@ -98,9 +105,99 @@ namespace Wabbajack
this.Bind(ViewModel, vm => vm.MachineUrl, view => view.MachineUrl.Text) this.Bind(ViewModel, vm => vm.MachineUrl, view => view.MachineUrl.Text)
.DisposeWith(disposables); .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());
}
} }
} }

View File

@ -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;
/// <summary>
/// Given a modlist.txt file, infer as much of CompilerSettings as possible
/// </summary>
public class CompilerSettingsInferencer
{
private readonly ILogger<CompilerSettingsInferencer> _logger;
public CompilerSettingsInferencer(ILogger<CompilerSettingsInferencer> logger)
{
_logger = logger;
}
public async Task<CompilerSettings?> 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<RelativePath>();
// 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;
}
}

View File

@ -154,6 +154,7 @@ public static class ServiceExtensions
service.AddScoped<StandardInstaller>(); service.AddScoped<StandardInstaller>();
service.AddScoped<MO2CompilerSettings>(); service.AddScoped<MO2CompilerSettings>();
service.AddScoped<MO2Compiler>(); service.AddScoped<MO2Compiler>();
service.AddSingleton<CompilerSettingsInferencer>();
// Application Info // Application Info
var version = var version =