BeginCommands refactored

This commit is contained in:
Justin Swanson 2019-12-18 19:14:21 -06:00
parent 5f7188d53d
commit f4f9272858
10 changed files with 217 additions and 258 deletions

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
@ -46,6 +47,7 @@ namespace Wabbajack
public IReactiveCommand BackCommand { get; }
public IReactiveCommand GoToModlistCommand { get; }
public IReactiveCommand CloseWhenCompleteCommand { get; }
public IReactiveCommand BeginCommand { get; }
public FilePickerVM OutputLocation { get; }
@ -180,10 +182,24 @@ namespace Wabbajack
.Debounce(TimeSpan.FromMilliseconds(25))
.ToProperty(this, nameof(PercentCompleted));
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: this.WhenAny(x => x.Compiler.CanCompile)
.Switch(),
execute: async () =>
{
try
{
await this.Compiler.Compile();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Error(ex, $"Compiler error");
}
});
// When sub compiler begins a compile, mark state variable
this.WhenAny(x => x.Compiler.BeginCommand)
.Select(x => x?.StartingExecution() ?? Observable.Empty<Unit>())
.Switch()
BeginCommand.StartingExecution()
.Subscribe(_ =>
{
StartedCompilation = true;
@ -191,9 +207,7 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
// When sub compiler finishes a compile, mark state variable
this.WhenAny(x => x.Compiler.BeginCommand)
.Select(x => x?.EndingExecution() ?? Observable.Empty<Unit>())
.Switch()
BeginCommand.EndingExecution()
.Subscribe(_ =>
{
Completed = true;

View File

@ -1,4 +1,6 @@
using ReactiveUI;
using System;
using System.Threading.Tasks;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Lib;
@ -6,10 +8,10 @@ namespace Wabbajack
{
public interface ISubCompilerVM
{
IReactiveCommand BeginCommand { get; }
ACompiler ActiveCompilation { get; }
ModlistSettingsEditorVM ModlistSettings { get; }
void Unload();
IObservable<bool> CanCompile { get; }
Task Compile();
}
}

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Lib;
@ -38,6 +39,8 @@ namespace Wabbajack
[Reactive]
public StatusUpdateTracker StatusTracker { get; private set; }
public IObservable<bool> CanCompile { get; }
public MO2CompilerVM(CompilerVM parent)
{
Parent = parent;
@ -93,8 +96,8 @@ namespace Wabbajack
// Load custom modlist settings per MO2 profile
_modlistSettings = Observable.CombineLatest(
this.WhenAny(x => x.ModListLocation.ErrorState),
this.WhenAny(x => x.ModListLocation.TargetPath),
(this).WhenAny(x => x.ModListLocation.ErrorState),
(this).WhenAny(x => x.ModListLocation.TargetPath),
resultSelector: (state, path) => (State: state, Path: path))
// A short throttle is a quick hack to make the above changes "atomic"
.Throttle(TimeSpan.FromMilliseconds(25))
@ -119,67 +122,16 @@ namespace Wabbajack
.ObserveOnGuiThread()
.ToProperty(this, nameof(ModlistSettings));
// Wire start command
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.ModListLocation.InError),
this.WhenAny(x => x.DownloadLocation.InError),
parent.WhenAny(x => x.OutputLocation.InError),
this.WhenAny(x => x.ModlistSettings)
.Select(x => x?.InError ?? Observable.Return(false))
.Switch(),
resultSelector: (ml, down, output, modlistSettings) => !ml && !down && !output && !modlistSettings)
.ObserveOnGuiThread(),
execute: async () =>
{
try
{
string outputFile;
if (string.IsNullOrWhiteSpace(parent.OutputLocation.TargetPath))
{
outputFile = MOProfile + ExtensionManager.Extension;
}
else
{
outputFile = Path.Combine(parent.OutputLocation.TargetPath, MOProfile + ExtensionManager.Extension);
}
ActiveCompilation = new MO2Compiler(
mo2Folder: Mo2Folder,
mo2Profile: MOProfile,
outputFile: outputFile)
{
ModListName = ModlistSettings.ModListName,
ModListAuthor = ModlistSettings.AuthorText,
ModListDescription = ModlistSettings.Description,
ModListImage = ModlistSettings.ImagePath.TargetPath,
ModListWebsite = ModlistSettings.Website,
ModListReadme = ModlistSettings.ReadMeText.TargetPath,
};
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Error(ex, $"Compiler error");
return;
}
try
{
await ActiveCompilation.Begin();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Error(ex, $"Compiler error");
}
finally
{
StatusTracker = null;
ActiveCompilation.Dispose();
ActiveCompilation = null;
}
});
CanCompile = Observable.CombineLatest(
this.WhenAny(x => x.ModListLocation.InError),
this.WhenAny(x => x.DownloadLocation.InError),
parent.WhenAny(x => x.OutputLocation.InError),
this.WhenAny(x => x.ModlistSettings)
.Select(x => x?.InError ?? Observable.Return(false))
.Switch(),
resultSelector: (ml, down, output, modlistSettings) => !ml && !down && !output && !modlistSettings)
.Publish()
.RefCount();
// Load settings
_settings = parent.MWVM.Settings.Compiler.MO2Compilation;
@ -193,11 +145,11 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
// If Mo2 folder changes and download location is empty, set it for convenience
this.WhenAny(x => x.Mo2Folder)
(this).WhenAny(x => x.Mo2Folder)
.DelayInitial(TimeSpan.FromMilliseconds(100))
.Where(x => Directory.Exists(x))
.FilterSwitch(
this.WhenAny(x => x.DownloadLocation.Exists)
(this).WhenAny(x => x.DownloadLocation.Exists)
.Invert())
// A skip is needed to ignore the initial signal when the FilterSwitch turns on
.Skip(1)
@ -214,5 +166,41 @@ namespace Wabbajack
_settings.LastCompiledProfileLocation = ModListLocation.TargetPath;
ModlistSettings?.Save();
}
public async Task Compile()
{
string outputFile;
if (string.IsNullOrWhiteSpace(Parent.OutputLocation.TargetPath))
{
outputFile = MOProfile + ExtensionManager.Extension;
}
else
{
outputFile = Path.Combine(Parent.OutputLocation.TargetPath, MOProfile + ExtensionManager.Extension);
}
try
{
ActiveCompilation = new MO2Compiler(
mo2Folder: Mo2Folder,
mo2Profile: MOProfile,
outputFile: outputFile)
{
ModListName = ModlistSettings.ModListName,
ModListAuthor = ModlistSettings.AuthorText,
ModListDescription = ModlistSettings.Description,
ModListImage = ModlistSettings.ImagePath.TargetPath,
ModListWebsite = ModlistSettings.Website,
ModListReadme = ModlistSettings.ReadMeText.TargetPath,
};
await ActiveCompilation.Begin();
}
finally
{
StatusTracker = null;
ActiveCompilation.Dispose();
ActiveCompilation = null;
}
}
}
}

View File

@ -54,6 +54,8 @@ namespace Wabbajack
[Reactive]
public StatusUpdateTracker StatusTracker { get; private set; }
public IObservable<bool> CanCompile { get; }
public VortexCompilerVM(CompilerVM parent)
{
Parent = parent;
@ -77,7 +79,7 @@ namespace Wabbajack
};
// Load custom ModList settings when game type changes
_modListSettings = this.WhenAny(x => x.SelectedGame)
_modListSettings = (this).WhenAny(x => x.SelectedGame)
.Select(game =>
{
if (game == null) return null;
@ -97,67 +99,16 @@ namespace Wabbajack
.ObserveOnGuiThread()
.ToProperty(this, nameof(ModlistSettings));
// Wire start command
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.GameLocation.InError),
this.WhenAny(x => x.DownloadsLocation.InError),
this.WhenAny(x => x.StagingLocation.InError),
this.WhenAny(x => x.ModlistSettings)
.Select(x => x?.InError ?? Observable.Return(false))
.Switch(),
(g, d, s, ml) => !g && !d && !s && !ml)
.ObserveOnGuiThread(),
execute: async () =>
{
try
{
string outputFile = $"{ModlistSettings.ModListName}{ExtensionManager.Extension}";
if (!string.IsNullOrWhiteSpace(parent.OutputLocation.TargetPath))
{
outputFile = Path.Combine(parent.OutputLocation.TargetPath, outputFile);
}
ActiveCompilation = new VortexCompiler(
game: SelectedGame.Game,
gamePath: GameLocation.TargetPath,
vortexFolder: VortexCompiler.TypicalVortexFolder(),
downloadsFolder: DownloadsLocation.TargetPath,
stagingFolder: StagingLocation.TargetPath,
outputFile: outputFile)
{
ModListName = ModlistSettings.ModListName,
ModListAuthor = ModlistSettings.AuthorText,
ModListDescription = ModlistSettings.Description,
ModListImage = ModlistSettings.ImagePath.TargetPath,
ModListWebsite = ModlistSettings.Website,
ModListReadme = ModlistSettings.ReadMeText.TargetPath,
};
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Error(ex, $"Compiler error");
return;
}
await Task.Run(async () =>
{
try
{
await ActiveCompilation.Begin();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Error(ex, $"Compiler error");
}
finally
{
StatusTracker = null;
ActiveCompilation.Dispose();
ActiveCompilation = null;
}
});
});
CanCompile = Observable.CombineLatest(
this.WhenAny(x => x.GameLocation.InError),
this.WhenAny(x => x.DownloadsLocation.InError),
this.WhenAny(x => x.StagingLocation.InError),
this.WhenAny(x => x.ModlistSettings)
.Select(x => x?.InError ?? Observable.Return(false))
.Switch(),
(g, d, s, ml) => !g && !d && !s && !ml)
.Publish()
.RefCount();
// Load settings
_settings = parent.MWVM.Settings.Compiler.VortexCompilation;
@ -167,7 +118,7 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
// Load custom game settings when game type changes
this.WhenAny(x => x.SelectedGame)
(this).WhenAny(x => x.SelectedGame)
.Select(game => _settings.ModlistSettings.TryCreate(game.Game))
.Pairwise()
.Subscribe(pair =>
@ -230,5 +181,39 @@ namespace Wabbajack
var gogGame = GOGHandler.Instance.Games.FirstOrDefault(g => g.Game.HasValue && g.Game == SelectedGame.Game);
GameLocation.TargetPath = gogGame?.Path;
}
public async Task Compile()
{
string outputFile = $"{ModlistSettings.ModListName}{ExtensionManager.Extension}";
if (!string.IsNullOrWhiteSpace(Parent.OutputLocation.TargetPath))
{
outputFile = Path.Combine(Parent.OutputLocation.TargetPath, outputFile);
}
try
{
ActiveCompilation = new VortexCompiler(
game: SelectedGame.Game,
gamePath: GameLocation.TargetPath,
vortexFolder: VortexCompiler.TypicalVortexFolder(),
downloadsFolder: DownloadsLocation.TargetPath,
stagingFolder: StagingLocation.TargetPath,
outputFile: outputFile)
{
ModListName = ModlistSettings.ModListName,
ModListAuthor = ModlistSettings.AuthorText,
ModListDescription = ModlistSettings.Description,
ModListImage = ModlistSettings.ImagePath.TargetPath,
ModListWebsite = ModlistSettings.Website,
ModListReadme = ModlistSettings.ReadMeText.TargetPath,
};
await ActiveCompilation.Begin();
}
finally
{
StatusTracker = null;
ActiveCompilation.Dispose();
ActiveCompilation = null;
}
}
}
}

View File

@ -12,11 +12,12 @@ namespace Wabbajack
public interface ISubInstallerVM
{
InstallerVM Parent { get; }
IReactiveCommand BeginCommand { get; }
AInstaller ActiveInstallation { get; }
void Unload();
bool SupportsAfterInstallNavigation { get; }
void AfterInstallNavigation();
int ConfigVisualVerticalOffset { get; }
IObservable<bool> CanInstall { get; }
Task Install();
}
}

View File

@ -89,6 +89,7 @@ namespace Wabbajack
public IReactiveCommand BackCommand { get; }
public IReactiveCommand CloseWhenCompleteCommand { get; }
public IReactiveCommand GoToInstallCommand { get; }
public IReactiveCommand BeginCommand { get; }
public InstallerVM(MainWindowVM mainWindowVM)
{
@ -306,10 +307,26 @@ namespace Wabbajack
.Subscribe()
.DisposeWith(CompositeDisposable);
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: this.WhenAny(x => x.Installer.CanInstall)
.Switch(),
execute: async () =>
{
try
{
await this.Installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
});
// When sub installer begins an install, mark state variable
this.WhenAny(x => x.Installer.BeginCommand)
.Select(x => x?.StartingExecution() ?? Observable.Empty<Unit>())
.Switch()
BeginCommand.StartingExecution()
.Subscribe(_ =>
{
StartedInstallation = true;
@ -317,9 +334,7 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
// When sub installer ends an install, mark state variable
this.WhenAny(x => x.Installer.BeginCommand)
.Select(x => x?.EndingExecution() ?? Observable.Empty<Unit>())
.Switch()
BeginCommand.EndingExecution()
.Subscribe(_ =>
{
Completed = true;

View File

@ -18,7 +18,7 @@ namespace Wabbajack
{
public InstallerVM Parent { get; }
public IReactiveCommand BeginCommand { get; }
public IObservable<bool> CanInstall { get; }
[Reactive]
public AInstaller ActiveInstallation { get; private set; }
@ -58,61 +58,13 @@ namespace Wabbajack
DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath)
.Select(x => Utils.IsDirectoryPathValid(x));
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.Location.InError),
this.WhenAny(x => x.DownloadLocation.InError),
installerVM.WhenAny(x => x.ModListLocation.InError),
resultSelector: (loc, modlist, download) =>
{
return !loc && !download && !modlist;
})
.ObserveOnGuiThread(),
execute: async () =>
CanInstall = Observable.CombineLatest(
this.WhenAny(x => x.Location.InError),
this.WhenAny(x => x.DownloadLocation.InError),
installerVM.WhenAny(x => x.ModListLocation.InError),
resultSelector: (loc, modlist, download) =>
{
AInstaller installer;
try
{
installer = new MO2Installer(
archive: installerVM.ModListLocation.TargetPath,
modList: installerVM.ModList.SourceModList,
outputFolder: Location.TargetPath,
downloadFolder: DownloadLocation.TargetPath);
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
ActiveInstallation = null;
return;
}
await Task.Run(async () =>
{
IDisposable subscription = null;
try
{
var workTask = installer.Begin();
ActiveInstallation = installer;
await workTask;
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
{
// Dispose of CPU tracking systems
subscription?.Dispose();
ActiveInstallation = null;
}
});
return !loc && !download && !modlist;
});
// Have Installation location updates modify the downloads location if empty
@ -131,7 +83,7 @@ namespace Wabbajack
_CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath)
.Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path))
.ToProperty(this, nameof(CurrentSettings));
this.WhenAny(x => x.CurrentSettings)
(this).WhenAny(x => x.CurrentSettings)
.Pairwise()
.Subscribe(settingsPair =>
{
@ -184,5 +136,29 @@ namespace Wabbajack
{
Process.Start("explorer.exe", Location.TargetPath);
}
public async Task Install()
{
var installer = new MO2Installer(
archive: Parent.ModListLocation.TargetPath,
modList: Parent.ModList.SourceModList,
outputFolder: Location.TargetPath,
downloadFolder: DownloadLocation.TargetPath);
await Task.Run(async () =>
{
try
{
var workTask = installer.Begin();
ActiveInstallation = installer;
await workTask;
return ErrorResponse.Success;
}
finally
{
ActiveInstallation = null;
}
});
}
}
}

View File

@ -15,8 +15,6 @@ namespace Wabbajack
{
public InstallerVM Parent { get; }
public IReactiveCommand BeginCommand { get; }
[Reactive]
public AInstaller ActiveInstallation { get; private set; }
@ -33,6 +31,8 @@ namespace Wabbajack
public int ConfigVisualVerticalOffset => 0;
public IObservable<bool> CanInstall { get; }
public VortexInstallerVM(InstallerVM installerVM)
{
Parent = installerVM;
@ -40,60 +40,11 @@ namespace Wabbajack
_TargetGame = installerVM.WhenAny(x => x.ModList.SourceModList.GameType)
.ToProperty(this, nameof(TargetGame));
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.TargetGame)
.Select(game => VortexCompiler.IsActiveVortexGame(game)),
installerVM.WhenAny(x => x.ModListLocation.InError),
resultSelector: (isVortexGame, modListErr) => isVortexGame && !modListErr),
execute: async () =>
{
AInstaller installer;
try
{
var download = VortexCompiler.RetrieveDownloadLocation(TargetGame);
var staging = VortexCompiler.RetrieveStagingLocation(TargetGame);
installer = new VortexInstaller(
archive: installerVM.ModListLocation.TargetPath,
modList: installerVM.ModList.SourceModList,
outputFolder: staging,
downloadFolder: download);
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
ActiveInstallation = null;
return;
}
await Task.Run(async () =>
{
IDisposable subscription = null;
try
{
var workTask = installer.Begin();
ActiveInstallation = installer;
await workTask;
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
{
// Dispose of CPU tracking systems
subscription?.Dispose();
ActiveInstallation = null;
}
});
});
CanInstall = Observable.CombineLatest(
this.WhenAny(x => x.TargetGame)
.Select(game => VortexCompiler.IsActiveVortexGame(game)),
installerVM.WhenAny(x => x.ModListLocation.InError),
resultSelector: (isVortexGame, modListErr) => isVortexGame && !modListErr);
}
public void Unload()
@ -104,5 +55,32 @@ namespace Wabbajack
{
throw new NotImplementedException();
}
public async Task Install()
{
AInstaller installer;
var download = VortexCompiler.RetrieveDownloadLocation(TargetGame);
var staging = VortexCompiler.RetrieveStagingLocation(TargetGame);
installer = new VortexInstaller(
archive: Parent.ModListLocation.TargetPath,
modList: Parent.ModList.SourceModList,
outputFolder: staging,
downloadFolder: download);
await Task.Run(async () =>
{
try
{
var workTask = installer.Begin();
ActiveInstallation = installer;
await workTask;
}
finally
{
ActiveInstallation = null;
}
});
}
}
}

View File

@ -232,7 +232,7 @@
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="5"
Command="{Binding Compiler.BeginCommand}" />
Command="{Binding BeginCommand}" />
</Grid>
</Grid>
<Grid

View File

@ -388,7 +388,7 @@
Margin="0,0,25,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding Installer.BeginCommand, Mode=OneWay}" />
Command="{Binding BeginCommand, Mode=OneWay}" />
</Grid>
</Grid>
<Grid