public ProfileVM(ConfigurationVM parent, GameRelease?release = null, string?id = null) { ID = id ?? Guid.NewGuid().ToString(); Config = parent; Release = release ?? GameRelease.Oblivion; AddGitPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new GitPatcherInitVM(this))); AddSolutionPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new SolutionPatcherInitVM(this))); AddCliPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new CliPatcherInitVM(this))); AddSnippetPatcherCommand = ReactiveCommand.Create(() => SetPatcherForInitialConfiguration(new CodeSnippetPatcherVM(this))); ProfileDirectory = Path.Combine(Execution.Constants.WorkingDirectory, ID); WorkingDirectory = Execution.Constants.ProfileWorkingDirectory(ID); var dataFolderResult = this.WhenAnyValue(x => x.Release) .ObserveOn(RxApp.TaskpoolScheduler) .Select(x => { try { return(GetResponse <string> .Succeed( Path.Combine(x.ToWjGame().MetaData().GameLocation().ToString(), "Data"))); } catch (Exception ex) { return(GetResponse <string> .Fail(string.Empty, ex)); } }) .Replay(1) .RefCount(); _DataFolder = dataFolderResult .Select(x => x.Value) .ToGuiProperty <string>(this, nameof(DataFolder)); var loadOrderResult = Observable.CombineLatest( this.WhenAnyValue(x => x.Release), dataFolderResult, (release, dataFolder) => (release, dataFolder)) .ObserveOn(RxApp.TaskpoolScheduler) .Select(x => { if (x.dataFolder.Failed) { return(Results: Observable.Empty <IChangeSet <LoadOrderListing> >(), State: Observable.Return <ErrorResponse>(ErrorResponse.Fail("Data folder not set"))); } var path = Mutagen.Bethesda.LoadOrder.GetPluginsPath(x.release); return(Results: Mutagen.Bethesda.LoadOrder.GetLiveLoadOrder(x.release, path, x.dataFolder.Value, out var errors), State: errors); }) .Replay(1) .RefCount(); LoadOrder = loadOrderResult .Select(x => x.Results) .Switch() .AsObservableList(); _LargeOverallError = Observable.CombineLatest( dataFolderResult, loadOrderResult .Select(x => x.State) .Switch(), Patchers.Connect() .AutoRefresh(x => x.IsOn) .Filter(p => p.IsOn) .AutoRefresh(x => x.State) .QueryWhenChanged(q => q) .StartWith(Noggog.ListExt.Empty <PatcherVM>()), (dataFolder, loadOrder, coll) => { if (coll.Count == 0) { return(GetResponse <PatcherVM> .Fail("There are no enabled patchers to run.")); } if (!dataFolder.Succeeded) { return(dataFolder.BubbleFailure <PatcherVM>()); } if (!loadOrder.Succeeded) { return(loadOrder.BubbleFailure <PatcherVM>()); } var blockingError = coll.FirstOrDefault(p => p.State.IsHaltingError); if (blockingError != null) { return(GetResponse <PatcherVM> .Fail(blockingError, $"\"{blockingError.DisplayName}\" has a blocking error")); } return(GetResponse <PatcherVM> .Succeed(null !)); })
public ProfileVM(ConfigurationVM parent, GameRelease?release = null, string?id = null) { ID = id ?? Guid.NewGuid().ToString(); Config = parent; Release = release ?? GameRelease.Oblivion; AddGitPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new GitPatcherInitVM(this))); AddSolutionPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new SolutionPatcherInitVM(this))); AddCliPatcherCommand = ReactiveCommand.Create(() => SetInitializer(new CliPatcherInitVM(this))); AddSnippetPatcherCommand = ReactiveCommand.Create(() => SetPatcherForInitialConfiguration(new CodeSnippetPatcherVM(this))); ProfileDirectory = Path.Combine(Execution.Paths.WorkingDirectory, ID); WorkingDirectory = Execution.Paths.ProfileWorkingDirectory(ID); var dataFolderResult = this.WhenAnyValue(x => x.DataPathOverride) .Select(path => { if (path != null) { return(Observable.Return(GetResponse <string> .Succeed(path))); } Log.Logger.Information("Starting to locate data folder"); return(this.WhenAnyValue(x => x.Release) .ObserveOn(RxApp.TaskpoolScheduler) .Select(x => { try { if (!GameLocations.TryGetGameFolder(x, out var gameFolder)) { return GetResponse <string> .Fail("Could not automatically locate Data folder. Run Steam/GoG/etc once to properly register things."); } return GetResponse <string> .Succeed(Path.Combine(gameFolder, "Data")); } catch (Exception ex) { return GetResponse <string> .Fail(string.Empty, ex); } })); }) .Switch() // Watch folder for existance .Select(x => { if (x.Failed) { return(Observable.Return(x)); } return(Noggog.ObservableExt.WatchFile(x.Value) .StartWith(Unit.Default) .Select(_ => { if (Directory.Exists(x.Value)) { return x; } return GetResponse <string> .Fail($"Data folder did not exist: {x.Value}"); })); }) .Switch() .StartWith(GetResponse <string> .Fail("Data folder uninitialized")) .Replay(1) .RefCount(); _DataFolder = dataFolderResult .Select(x => x.Value) .ToGuiProperty <string>(this, nameof(DataFolder), string.Empty); dataFolderResult .Subscribe(d => { if (d.Failed) { Log.Logger.Error($"Could not locate data folder: {d.Reason}"); } else { Log.Logger.Information($"Data Folder: {d.Value}"); } }) .DisposeWith(this); var loadOrderResult = Observable.CombineLatest( this.WhenAnyValue(x => x.Release), dataFolderResult, (release, dataFolder) => (release, dataFolder)) .ObserveOn(RxApp.TaskpoolScheduler) .Select(x => { if (x.dataFolder.Failed) { return(Results: Observable.Empty <IChangeSet <LoadOrderEntryVM> >(), State: Observable.Return(ErrorResponse.Fail("Data folder not set"))); } Log.Logger.Error($"Getting live load order for {x.release} -> {x.dataFolder.Value}"); var liveLo = Mutagen.Bethesda.LoadOrder.GetLiveLoadOrder(x.release, x.dataFolder.Value, out var errors) .Transform(listing => new LoadOrderEntryVM(listing, x.dataFolder.Value)) .DisposeMany(); return(Results: liveLo, State: errors); }) .StartWith((Results: Observable.Empty <IChangeSet <LoadOrderEntryVM> >(), State: Observable.Return(ErrorResponse.Fail("Load order uninitialized")))) .Replay(1) .RefCount(); LoadOrder = loadOrderResult .Select(x => x.Results) .Switch() .AsObservableList(); loadOrderResult.Select(lo => lo.State) .Switch() .Subscribe(loErr => { if (loErr.Succeeded) { Log.Logger.Information($"Load order location successful"); } else { Log.Logger.Information($"Load order location error: {loErr.Reason}"); } }) .DisposeWith(this); _LargeOverallError = Observable.CombineLatest( dataFolderResult, loadOrderResult .Select(x => x.State) .Switch(), Patchers.Connect() .ObserveOnGui() .FilterOnObservable(p => p.WhenAnyValue(x => x.IsOn), scheduler: RxApp.MainThreadScheduler) .QueryWhenChanged(q => q) .StartWith(Noggog.ListExt.Empty <PatcherVM>()), Patchers.Connect() .ObserveOnGui() .FilterOnObservable(p => Observable.CombineLatest( p.WhenAnyValue(x => x.IsOn), p.WhenAnyValue(x => x.State.IsHaltingError), (on, halting) => on && halting), scheduler: RxApp.MainThreadScheduler) .QueryWhenChanged(q => q) .StartWith(Noggog.ListExt.Empty <PatcherVM>()), LoadOrder.Connect() .ObserveOnGui() .FilterOnObservable( x => x.WhenAnyValue(y => y.Exists) .DistinctUntilChanged() .Select(x => !x), scheduler: RxApp.MainThreadScheduler) .QueryWhenChanged(q => q) .StartWith(Noggog.ListExt.Empty <LoadOrderEntryVM>()) .Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler), (dataFolder, loadOrder, enabledPatchers, erroredEnabledPatchers, missingMods) => { if (enabledPatchers.Count == 0) { return(GetResponse <PatcherVM> .Fail("There are no enabled patchers to run.")); } if (!dataFolder.Succeeded) { return(dataFolder.BubbleFailure <PatcherVM>()); } if (!loadOrder.Succeeded) { return(loadOrder.BubbleFailure <PatcherVM>()); } if (missingMods.Count > 0) { return(GetResponse <PatcherVM> .Fail($"Load order had mods that were missing:{Environment.NewLine}{string.Join(Environment.NewLine, missingMods.Select(x => x.Listing.ModKey))}")); } if (erroredEnabledPatchers.Count > 0) { var errPatcher = erroredEnabledPatchers.First(); return(GetResponse <PatcherVM> .Fail(errPatcher, $"\"{errPatcher.DisplayName}\" has a blocking error: {errPatcher.State.RunnableState.Reason}")); } return(GetResponse <PatcherVM> .Succeed(null !)); })
public PatchersRunVM(ConfigurationVM parent, ProfileVM profile) { Config = parent; RunningProfile = profile; Patchers.AddOrUpdate(RunningProfile.Patchers.Items .Where(x => x.IsOn) .Select(p => p.ToRunner(this))); PatchersDisplay = Patchers.Connect() .ToObservableCollection(this); if (parent.SelectedPatcher != null && Patchers.TryGetValue(parent.SelectedPatcher.InternalID, out var run)) { SelectedPatcher = run; } BackCommand = ReactiveCommand.Create(() => { parent.SelectedPatcher = SelectedPatcher?.Config; parent.MainVM.ActivePanel = parent; }, canExecute: this.WhenAnyValue(x => x.Running) .Select(running => !running)); CancelCommand = ReactiveCommand.CreateFromTask( execute: Cancel, canExecute: this.WhenAnyValue(x => x.Running)); _reporter.Overall .ObserveOnGui() .Subscribe(ex => { Log.Logger.Error(ex, "Error while running patcher pipeline"); ResultError = ex; }) .DisposeWith(this); _reporter.PrepProblem .Select(data => (data, type: "prepping")) .Merge(_reporter.RunProblem .Select(data => (data, type: "running"))) .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.data.Key); vm.State = GetResponse <RunState> .Fail(RunState.Error, i.data.Error); SelectedPatcher = vm; Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.data.Run.Name) .Error(i.data.Error, $"Error while prepping {i.type}"); }) .DisposeWith(this); _reporter.Starting .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.Key); vm.State = GetResponse <RunState> .Succeed(RunState.Started); Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.Run.Name) .Information($"Starting"); }) .DisposeWith(this); _reporter.RunSuccessful .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.Key); vm.State = GetResponse <RunState> .Succeed(RunState.Finished); Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.Run.Name) .Information("Finished {RunTime}", vm.RunTime); }) .DisposeWith(this); _reporter.Output .Subscribe(s => { Log.Logger .ForContextIfNotNull(nameof(PatcherVM.DisplayName), s.Run?.Name) .Information(s.String); }) .DisposeWith(this); _reporter.Error .Subscribe(s => { Log.Logger .ForContextIfNotNull(nameof(PatcherVM.DisplayName), s.Run?.Name) .Error(s.String); }) .DisposeWith(this); // Clear selected patcher on showing error this.ShowOverallErrorCommand.StartingExecution() .Subscribe(_ => this.SelectedPatcher = null) .DisposeWith(this); _DetailDisplay = Observable.Merge( this.WhenAnyValue(x => x.SelectedPatcher) .Select(i => i as object), this.ShowOverallErrorCommand.EndingExecution() .Select(_ => ResultError == null ? null : new OverallErrorVM(ResultError))) .ToGuiProperty(this, nameof(DetailDisplay)); }