public static IDisposable HelpWiring(ConfigurationVM config, Button button, TextBlock helpBlock, IObservable <bool>?show = null) { CompositeDisposable ret = new CompositeDisposable(); show ??= Observable.Return(true); show.Select(x => x ? Visibility.Visible : Visibility.Collapsed) .Subscribe(x => button.Visibility = x) .DisposeWith(ret); Observable.CombineLatest( show, config.WhenAnyValue(x => x.ShowHelp), (newPatcher, on) => on && newPatcher) .Select(x => x ? Visibility.Visible : Visibility.Collapsed) .Subscribe(x => helpBlock.Visibility = x) .DisposeWith(ret); config.WhenAnyValue(x => x.ShowHelpToggleCommand) .Subscribe(x => button.Command = x) .DisposeWith(ret); config.WhenAnyValue(x => x.ShowHelp) .Select(x => x ? 1.0d : 0.4d) .Subscribe(x => button.Opacity = x) .DisposeWith(ret); return(ret); }
public NewProfileVM(ConfigurationVM config, Action <ProfileVM> postRun) { ReleaseOptions.AddRange(EnumExt.GetValues <GameRelease>()); this.WhenAnyValue(x => x.SelectedGame) .Subscribe(game => { if (game == null) { return; } var profile = new ProfileVM(config, game.Value) { Nickname = Nickname }; config.Profiles.AddOrUpdate(profile); postRun(profile); }) .DisposeWith(this); }
public ProfilesDisplayVM(ConfigurationVM parent, ViewModel previousPage) { _previous = previousPage; Config = parent; GoBackCommand = ReactiveCommand.Create(() => { parent.MainVM.ActivePanel = _previous; }); AddCommand = ReactiveCommand.Create(() => { DisplayObject = new NewProfileVM(Config, (profile) => { if (string.IsNullOrWhiteSpace(profile.Nickname)) { profile.Nickname = profile.Release.ToDescriptionString(); } }); DisplayedProfile = null; }); ProfilesDisplay = parent.Profiles.Connect() .Transform(x => new ProfileDisplayVM(this, x)) // Select the currently active profile during initial display .OnItemAdded(p => { if (DisplayedProfile == null || (p.Profile?.IsActive ?? false)) { DisplayedProfile = p; } }) .ToObservableCollection(this); this.WhenAnyValue(x => x.DisplayedProfile) .NotNull() .Subscribe(p => DisplayObject = p) .DisposeWith(this); }
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 MainVM(Window window) { _window = window; var dotNet = Observable.Interval(TimeSpan.FromSeconds(10), RxApp.TaskpoolScheduler) .StartWith(0) .SelectTask(async i => { try { var ret = await DotNetCommands.DotNetSdkVersion(CancellationToken.None); Log.Logger.Information($"dotnet SDK: {ret}"); return(ret); } catch (Exception ex) { Log.Logger.Error(ex, $"Error retrieving dotnet SDK version"); return(default(Version?)); } }); DotNetSdkInstalled = dotNet .Take(1) .Merge(dotNet .FirstAsync(v => v != null)) .DistinctUntilChanged() .Replay(1) .RefCount(); Configuration = new ConfigurationVM(this); ActivePanel = Configuration; DiscardActionCommand = NoggogCommand.CreateFromObject( objectSource: this.WhenAnyValue(x => x.TargetConfirmation), canExecute: target => target != null, execute: (_) => { TargetConfirmation = null; }, disposable: this.CompositeDisposable); ConfirmActionCommand = NoggogCommand.CreateFromObject( objectSource: this.WhenAnyFallback(x => x.TargetConfirmation !.ToDo), canExecute: toDo => toDo != null, execute: toDo => { toDo?.Invoke(); TargetConfirmation = null; }, disposable: this.CompositeDisposable); _Hot = this.WhenAnyValue(x => x.ActivePanel) .Select(x => { switch (x) { case ConfigurationVM config: return(config.WhenAnyFallback(x => x.CurrentRun !.Running, fallback: false)); case PatchersRunVM running: return(running.WhenAnyValue(x => x.Running)); default: break; } return(Observable.Return(false)); }) .Switch() .DistinctUntilChanged() .ToGuiProperty(this, nameof(Hot)); OpenProfilesPageCommand = ReactiveCommand.Create(() => { ActivePanel = new ProfilesDisplayVM(Configuration, ActivePanel); }, canExecute: Observable.CombineLatest( this.WhenAnyFallback(x => x.Configuration.CurrentRun !.Running, fallback: false), this.WhenAnyValue(x => x.ActivePanel) .Select(x => x is ProfilesDisplayVM), (running, isProfile) => !running && !isProfile)); IdeOptions.AddRange(EnumExt.GetValues <IDE>()); Task.Run(() => Mutagen.Bethesda.WarmupAll.Init()).FireAndForget(); SynthesisVersion = Mutagen.Bethesda.Synthesis.Versions.SynthesisVersion; MutagenVersion = Mutagen.Bethesda.Synthesis.Versions.MutagenVersion; var latestVersions = Observable.Return(Unit.Default) .ObserveOn(RxApp.TaskpoolScheduler) .CombineLatest( DotNetSdkInstalled, (_, v) => v) .SelectTask(async v => { var normalUpdateTask = GetLatestVersions(v, includePrerelease: false); var prereleaseUpdateTask = GetLatestVersions(v, includePrerelease: true); await Task.WhenAll(normalUpdateTask, prereleaseUpdateTask); return(Normal: await normalUpdateTask, Prerelease: await prereleaseUpdateTask); }) .Replay(1) .RefCount(); NewestMutagenVersion = Observable.CombineLatest( latestVersions, this.WhenAnyFallback(x => x.Configuration.SelectedProfile !.ConsiderPrereleaseNugets), (vers, prereleases) => prereleases ? vers.Prerelease.MutagenVersion : vers.Normal.MutagenVersion) .Replay(1) .RefCount(); NewestSynthesisVersion = Observable.CombineLatest( latestVersions, this.WhenAnyFallback(x => x.Configuration.SelectedProfile !.ConsiderPrereleaseNugets), (vers, prereleases) => prereleases ? vers.Prerelease.SynthesisVersion : vers.Normal.SynthesisVersion) .Replay(1) .RefCount(); // Switch to DotNet screen if missing DotNetSdkInstalled .Subscribe(v => { if (v == null) { ActivePanel = new DotNetNotInstalledVM(this, this.ActivePanel, DotNetSdkInstalled); } }); _ActiveConfirmation = Observable.CombineLatest( this.WhenAnyFallback(x => x.Configuration.SelectedProfile !.SelectedPatcher) .Select(x => { if (x is not GitPatcherVM gitPatcher) { return(Observable.Return(default(GitPatcherVM?))); } return(gitPatcher.WhenAnyValue(x => x.PatcherSettings.SettingsOpen) .Select(open => open ? (GitPatcherVM?)gitPatcher : null)); }) .Switch(), this.WhenAnyValue(x => x.TargetConfirmation), (openPatcher, target) => { if (target != null) { return(target); } if (openPatcher == null) { return(default(ConfirmationActionVM?)); } return(new ConfirmationActionVM( "External Patcher Settings Open", $"{openPatcher.Nickname} is open for settings manipulation.", toDo: null)); }) .ToGuiProperty(this, nameof(ActiveConfirmation), default(ConfirmationActionVM?)); _InModal = this.WhenAnyValue(x => x.ActiveConfirmation) .Select(x => x != null) .ToGuiProperty(this, nameof(InModal)); }
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 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)); }
public MainVM() { var dotNet = Observable.Interval(TimeSpan.FromSeconds(10), RxApp.TaskpoolScheduler) .StartWith(0) .SelectTask(async i => { try { var ret = await DotNetQueries.DotNetSdkVersion(); Log.Logger.Information($"dotnet SDK: {ret}"); return(ret); } catch (Exception ex) { Log.Logger.Error(ex, $"Error retrieving dotnet SDK version"); return(default(Version?)); } }); DotNetSdkInstalled = dotNet .Take(1) .Merge(dotNet .FirstAsync(v => v != null)) .DistinctUntilChanged() .Replay(1) .RefCount(); Configuration = new ConfigurationVM(this); ActivePanel = Configuration; DiscardActionCommand = ReactiveCommand.Create(() => ActiveConfirmation = null); ConfirmActionCommand = ReactiveCommand.Create( () => { if (ActiveConfirmation == null) { return; } ActiveConfirmation.ToDo(); ActiveConfirmation = null; }); _Hot = this.WhenAnyValue(x => x.ActivePanel) .Select(x => { switch (x) { case ConfigurationVM config: return(config.WhenAnyFallback(x => x.CurrentRun !.Running, fallback: false)); case PatchersRunVM running: return(running.WhenAnyValue(x => x.Running)); default: break; } return(Observable.Return(false)); }) .Switch() .DistinctUntilChanged() .ToGuiProperty(this, nameof(Hot)); OpenProfilesPageCommand = ReactiveCommand.Create(() => { ActivePanel = new ProfilesDisplayVM(Configuration, ActivePanel); }, canExecute: Observable.CombineLatest( this.WhenAnyFallback(x => x.Configuration.CurrentRun !.Running, fallback: false), this.WhenAnyValue(x => x.ActivePanel) .Select(x => x is ProfilesDisplayVM), (running, isProfile) => !running && !isProfile)); IdeOptions.AddRange(EnumExt.GetValues <IDE>()); Task.Run(() => Mutagen.Bethesda.WarmupAll.Init()).FireAndForget(); SynthesisVersion = Mutagen.Bethesda.Synthesis.Versions.SynthesisVersion; MutagenVersion = Mutagen.Bethesda.Synthesis.Versions.MutagenVersion; var latestVersions = Observable.Return(Unit.Default) .ObserveOn(RxApp.TaskpoolScheduler) .CombineLatest( DotNetSdkInstalled, (_, v) => v) .SelectTask(GetLatestVersions) .Replay(1) .RefCount(); NewestMutagenVersion = latestVersions .Select(x => x.MutagenVersion); NewestSynthesisVersion = latestVersions .Select(x => x.SynthesisVersion); // Switch to DotNet screen if missing DotNetSdkInstalled .Subscribe(v => { if (v == null) { ActivePanel = new DotNetNotInstalledVM(this, this.ActivePanel, DotNetSdkInstalled); } }); }