public ModlistSettingsEditorVM(CompilationModlistSettings settings) { this._settings = settings; ImagePath = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty, PathType = FilePickerVM.PathTypeOptions.File, }; ImagePath.Filters.Add(new CommonFileDialogFilter("Banner image", "*.png")); ReadmeFilePath = new FilePickerVM() { PathType = FilePickerVM.PathTypeOptions.File, ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty, }; ReadmeFilePath.Filters.Add(new CommonFileDialogFilter("Text", "*.txt")); ReadmeFilePath.Filters.Add(new CommonFileDialogFilter("HTML File", "*.html")); InError = Observable.CombineLatest( this.WhenAny(x => x.ImagePath.ErrorState).Select(err => err.Failed), this.WhenAny(x => x.ReadmeFilePath.ErrorState).Select(err => err.Failed), this.WhenAny(x => x.ReadmeIsWebsite), resultSelector: (img, readme, isWebsite) => img || (readme && !isWebsite)) .Publish() .RefCount(); SwapToTextReadmeCommand = ReactiveCommand.Create(() => ReadmeIsWebsite = false); SwapToWebsiteReadmeCommand = ReactiveCommand.Create(() => ReadmeIsWebsite = true); }
public MO2CompilerVM(CompilerVM parent) { Parent = parent; ModListLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.File, PromptTitle = "Select a ModList" }; DownloadLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select a downloads location", }; _mo2Folder = this.WhenAny(x => x.ModListLocation.TargetPath) .Select(loc => { try { var profileFolder = loc.Parent; return(profileFolder.Parent.Parent); } catch (Exception) { return(default);
public MO2CompilerVM(CompilerVM parent) { Parent = parent; ModListLocation = new FilePickerVM { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.File, PromptTitle = "Select a Modlist" }; ModListLocation.Filters.Add(new CommonFileDialogFilter("MO2 Profile (modlist.txt) or Native Settings (native_compiler_settings.json)", ".txt,.json")); DownloadLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select a downloads location", }; _mo2Folder = this.WhenAny(x => x.ModListLocation.TargetPath) .Select(loc => { try { if (loc.FileName == Consts.ModListTxt) { var profileFolder = loc.Parent; return(profileFolder.Parent.Parent); } if (loc.FileName == Consts.NativeSettingsJson) { return(loc.Parent); } return(default);
public ModlistSettingsEditorVM(CompilationModlistSettings settings) { _settings = settings; ImagePath = new FilePickerVM { ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty, PathType = FilePickerVM.PathTypeOptions.File, }; ImagePath.Filters.Add(new CommonFileDialogFilter("Banner image", "*.png")); _version = this.WhenAny(x => x.VersionText) .Select(x => { if (string.IsNullOrWhiteSpace(x)) { return(new Version(0, 0)); } return(!Version.TryParse(x, out var version) ? new Version(0, 0) : version); }).ObserveOnGuiThread() .ToProperty(this, x => x.Version); InError = this.WhenAny(x => x.ImagePath.ErrorState) .Select(err => err.Failed) .CombineLatest( this.WhenAny(x => x.VersionText) .Select(x => Version.TryParse(x, out _)), (image, version) => !image && !version) .Publish() .RefCount(); }
public AuthorFilesVM(SettingsVM vm) : base(vm.MWVM) { IsUploading = _isUploading; Picker = new FilePickerVM(this); _isVisible = AuthorAPI.HaveAuthorAPIKey.Select(h => h ? Visibility.Visible : Visibility.Collapsed) .ToProperty(this, x => x.IsVisible); SelectFile = Picker.ConstructTypicalPickerCommand(IsUploading.StartWith(false).Select(u => !u)); HyperlinkCommand = ReactiveCommand.Create(() => Clipboard.SetText(FinalUrl)); Upload = ReactiveCommand.Create(async() => { _isUploading.OnNext(true); try { FinalUrl = await AuthorAPI.UploadFile(Picker.TargetPath, progress => UploadProgress = progress); } catch (Exception ex) { FinalUrl = ex.ToString(); } finally { _isUploading.OnNext(false); } }, IsUploading.StartWith(false).Select(u => !u) .CombineLatest(Picker.WhenAnyValue(t => t.TargetPath).Select(f => f != null), (a, b) => a && b)); }
public AuthorFilesVM(SettingsVM vm) : base(vm.MWVM) { IsUploading = _isUploading; Picker = new FilePickerVM(this); _isVisible = AuthorAPI.HaveAuthorAPIKey.Select(h => h ? Visibility.Visible : Visibility.Collapsed) .ToProperty(this, x => x.IsVisible); SelectFile = Picker.ConstructTypicalPickerCommand(IsUploading.StartWith(false).Select(u => !u)); HyperlinkCommand = ReactiveCommand.Create(() => Clipboard.SetText(FinalUrl)); ManageFiles = ReactiveCommand.Create(async() => { var authorApiKey = await AuthorAPI.GetAPIKey(); Utils.OpenWebsite(new Uri($"{Consts.WabbajackBuildServerUri}author_controls/login/{authorApiKey}")); }); Upload = ReactiveCommand.Create(async() => { _isUploading.OnNext(true); try { using var queue = new WorkQueue(); var result = await(await Client.Create()).UploadFile(queue, Picker.TargetPath, (msg, progress) => { FinalUrl = msg; UploadProgress = (double)progress; }); FinalUrl = result.ToString(); } catch (Exception ex) { FinalUrl = ex.ToString(); } finally { FinalUrl = FinalUrl.Replace(" ", "%20"); _isUploading.OnNext(false); } }, IsUploading.StartWith(false).Select(u => !u) .CombineLatest(Picker.WhenAnyValue(t => t.TargetPath).Select(f => f != default), (a, b) => a && b)); }
public MO2InstallerVM(InstallerVM installerVM) { Parent = installerVM; Location = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.Off, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select Installation Directory", }; DownloadLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.Off, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select a location for MO2 downloads", }; DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath) .Select(x => Utils.IsDirectoryPathValid(x)); Location.AdditionalError = Observable.CombineLatest( this.WhenAny(x => x.Location.TargetPath), this.WhenAny(x => x.DownloadLocation.TargetPath), resultSelector: (target, download) => (target, download)) .ObserveOn(RxApp.TaskpoolScheduler) .Select(i => MO2Installer.CheckValidInstallPath(i.target, i.download)) .ObserveOnGuiThread(); 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) => { return(!loc && !download && !modlist); }); // Have Installation location updates modify the downloads location if empty this.WhenAny(x => x.Location.TargetPath) .Skip(1) // Don't do it initially .Subscribe(installPath => { if (string.IsNullOrWhiteSpace(DownloadLocation.TargetPath)) { DownloadLocation.TargetPath = Path.Combine(installPath, "downloads"); } }) .DisposeWith(CompositeDisposable); // Load settings _CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath) .Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path)) .ToGuiProperty(this, nameof(CurrentSettings)); this.WhenAny(x => x.CurrentSettings) .Pairwise() .Subscribe(settingsPair => { SaveSettings(settingsPair.Previous); if (settingsPair.Current == null) { return; } Location.TargetPath = settingsPair.Current.InstallationLocation; DownloadLocation.TargetPath = settingsPair.Current.DownloadLocation; AutomaticallyOverwrite = settingsPair.Current.AutomaticallyOverrideExistingInstall; }) .DisposeWith(CompositeDisposable); installerVM.MWVM.Settings.SaveSignal .Subscribe(_ => SaveSettings(CurrentSettings)) .DisposeWith(CompositeDisposable); // Hook onto user interventions, and intercept MO2 specific ones for customization this.WhenAny(x => x.ActiveInstallation) .Select(x => x?.LogMessages ?? Observable.Empty <IStatusMessage>()) .Switch() .Subscribe(x => { switch (x) { case ConfirmUpdateOfExistingInstall c: if (AutomaticallyOverwrite) { c.Confirm(); } break; default: break; } }) .DisposeWith(CompositeDisposable); }
public VortexCompilerVM(CompilerVM parent) { Parent = parent; GameLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select Game Folder Location" }; DownloadsLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select Downloads Folder" }; StagingLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select Staging Folder" }; // Load custom ModList settings when game type changes _modListSettings = (this).WhenAny(x => x.SelectedGame) .Select(game => { if (game == null) { return(null); } var gameSettings = _settings.ModlistSettings.TryCreate(game.Game); return(new ModlistSettingsEditorVM(gameSettings.ModlistSettings)); }) // Interject and save old while loading new .Pairwise() .Do(pair => { var(previous, current) = pair; previous?.Save(); current?.Init(); }) .Select(x => x.Current) .ToGuiProperty(this, nameof(ModlistSettings)); 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; SelectedGame = _gameOptions.FirstOrDefault(x => x.Game == _settings.LastCompiledGame) ?? _gameOptions[0]; parent.MWVM.Settings.SaveSignal .Subscribe(_ => Unload()) .DisposeWith(CompositeDisposable); // Load custom game settings when game type changes (this).WhenAny(x => x.SelectedGame) .Select(game => _settings.ModlistSettings.TryCreate(game.Game)) .Pairwise() .Subscribe(pair => { // Save old var(previous, current) = pair; if (previous != null) { previous.GameLocation = GameLocation.TargetPath; } // Load new GameLocation.TargetPath = current?.GameLocation; if (string.IsNullOrWhiteSpace(GameLocation.TargetPath)) { SetGameToSteamLocation(); } if (string.IsNullOrWhiteSpace(GameLocation.TargetPath)) { SetGameToGogLocation(); } DownloadsLocation.TargetPath = current?.DownloadLocation; if (string.IsNullOrWhiteSpace(DownloadsLocation.TargetPath)) { DownloadsLocation.TargetPath = VortexCompiler.RetrieveDownloadLocation(SelectedGame.Game); } StagingLocation.TargetPath = current?.StagingLocation; if (string.IsNullOrWhiteSpace(StagingLocation.TargetPath)) { StagingLocation.TargetPath = VortexCompiler.RetrieveStagingLocation(SelectedGame.Game); } }) .DisposeWith(CompositeDisposable); // Find game commands FindGameInSteamCommand = ReactiveCommand.Create(SetGameToSteamLocation); FindGameInGogCommand = ReactiveCommand.Create(SetGameToGogLocation); // Add additional criteria to download/staging folders DownloadsLocation.AdditionalError = this.WhenAny(x => x.DownloadsLocation.TargetPath) .Select(path => path == null ? ErrorResponse.Success : VortexCompiler.IsValidDownloadsFolder(path)); StagingLocation.AdditionalError = this.WhenAny(x => x.StagingLocation.TargetPath) .Select(path => path == null ? ErrorResponse.Success : VortexCompiler.IsValidBaseStagingFolder(path)); }
public MO2CompilerVM(CompilerVM parent) { Parent = parent; ModListLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.File, PromptTitle = "Select a ModList" }; DownloadLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select a downloads location", }; _mo2Folder = this.WhenAny(x => x.ModListLocation.TargetPath) .Select(loc => { try { var profileFolder = Path.GetDirectoryName(loc); return(Path.GetDirectoryName(Path.GetDirectoryName(profileFolder))); } catch (Exception) { return(null); } }) .ToGuiProperty(this, nameof(Mo2Folder)); _moProfile = this.WhenAny(x => x.ModListLocation.TargetPath) .Select(loc => { try { var profileFolder = Path.GetDirectoryName(loc); return(Path.GetFileName(profileFolder)); } catch (Exception) { return(null); } }) .ToGuiProperty(this, nameof(MOProfile)); // Wire missing Mo2Folder to signal error state for ModList Location ModListLocation.AdditionalError = this.WhenAny(x => x.Mo2Folder) .Select <string, IErrorResponse>(moFolder => { if (Directory.Exists(moFolder)) { return(ErrorResponse.Success); } return(ErrorResponse.Fail($"MO2 folder could not be located from the given ModList location.{Environment.NewLine}Make sure your ModList is inside a valid MO2 distribution.")); }); // Load custom ModList settings per MO2 profile _modlistSettings = Observable.CombineLatest( (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), RxApp.MainThreadScheduler) .Select(u => { if (u.State.Failed) { return(null); } var modlistSettings = _settings.ModlistSettings.TryCreate(u.Path); return(new ModlistSettingsEditorVM(modlistSettings) { ModListName = MOProfile }); }) // Interject and save old while loading new .Pairwise() .Do(pair => { pair.Previous?.Save(); pair.Current?.Init(); }) .Select(x => x.Current) .ToGuiProperty(this, nameof(ModlistSettings)); 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; ModListLocation.TargetPath = _settings.LastCompiledProfileLocation; if (!string.IsNullOrWhiteSpace(_settings.DownloadLocation)) { DownloadLocation.TargetPath = _settings.DownloadLocation; } parent.MWVM.Settings.SaveSignal .Subscribe(_ => Unload()) .DisposeWith(CompositeDisposable); // If Mo2 folder changes and download location is empty, set it for convenience this.WhenAny(x => x.Mo2Folder) .DelayInitial(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler) .Where(x => Directory.Exists(x)) .FlowSwitch( (this).WhenAny(x => x.DownloadLocation.Exists) .Invert()) // A skip is needed to ignore the initial signal when the FilterSwitch turns on .Skip(1) .Subscribe(_ => { DownloadLocation.TargetPath = MO2Compiler.GetTypicalDownloadsFolder(Mo2Folder); }) .DisposeWith(CompositeDisposable); }
public InstallerVM(MainWindowVM mainWindowVM) { if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower()) { MessageBox.Show( "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " + "conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.", "Cannot run inside Downloads", MessageBoxButton.OK, MessageBoxImage.Error); Environment.Exit(1); } MWVM = mainWindowVM; ModListLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.On, PathType = FilePickerVM.PathTypeOptions.File, PromptTitle = "Select a modlist to install" }; // Swap to proper sub VM based on selected type _installer = this.WhenAny(x => x.TargetManager) // Delay so the initial VM swap comes in immediately, subVM comes right after .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) .Select <ModManager?, ISubInstallerVM>(type => { switch (type) { case ModManager.MO2: return(new MO2InstallerVM(this)); case ModManager.Vortex: return(new VortexInstallerVM(this)); default: return(null); } }) // Unload old VM .Pairwise() .Do(pair => { pair.Previous?.Unload(); }) .Select(p => p.Current) .ToProperty(this, nameof(Installer)); // Load settings MWVM.Settings.SaveSignal .Subscribe(_ => { MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath; }) .DisposeWith(CompositeDisposable); _modList = this.WhenAny(x => x.ModListLocation.TargetPath) .ObserveOn(RxApp.TaskpoolScheduler) .Select(modListPath => { if (modListPath == null) { return(default(ModListVM)); } if (!File.Exists(modListPath)) { return(default(ModListVM)); } return(new ModListVM(modListPath)); }) .ObserveOnGuiThread() .StartWith(default(ModListVM)) .ToProperty(this, nameof(ModList)); _htmlReport = this.WhenAny(x => x.ModList) .Select(modList => modList?.ReportHTML) .ToProperty(this, nameof(HTMLReport)); _installing = this.WhenAny(x => x.Installer.ActiveInstallation) .Select(i => i != null) .ObserveOnGuiThread() .ToProperty(this, nameof(Installing)); _TargetManager = this.WhenAny(x => x.ModList) .Select(modList => modList?.ModManager) .ToProperty(this, nameof(TargetManager)); // Add additional error check on modlist ModListLocation.AdditionalError = this.WhenAny(x => x.ModList) .Select <ModListVM, IErrorResponse>(modList => { if (modList == null) { return(ErrorResponse.Fail("Modlist path resulted in a null object.")); } if (modList.Error != null) { return(ErrorResponse.Fail("Modlist is corrupt", modList.Error)); } return(ErrorResponse.Success); }); BackCommand = ReactiveCommand.Create( execute: () => { StartedInstallation = false; Completed = null; mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; }, canExecute: this.WhenAny(x => x.Installing) .Select(x => !x)); _percentCompleted = this.WhenAny(x => x.Installer.ActiveInstallation) .StartWith(default(AInstaller)) .CombineLatest( this.WhenAny(x => x.Completed), (installer, completed) => { if (installer == null) { return(Observable.Return <float>(completed != null ? 1f : 0f)); } return(installer.PercentCompleted.StartWith(0f)); }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25)) .ToProperty(this, nameof(PercentCompleted)); Slideshow = new SlideShow(this); // Set display items to modlist if configuring or complete, // or to the current slideshow data if installing _image = Observable.CombineLatest( this.WhenAny(x => x.ModList.Error), this.WhenAny(x => x.ModList) .Select(x => x?.ImageObservable ?? Observable.Empty <BitmapImage>()) .Switch() .StartWith(WabbajackLogo), this.WhenAny(x => x.Slideshow.Image) .StartWith(default(BitmapImage)), this.WhenAny(x => x.Installing), resultSelector: (err, modList, slideshow, installing) => { if (err != null) { return(WabbajackErrLogo); } var ret = installing ? slideshow : modList; return(ret ?? WabbajackLogo); }) .Select <BitmapImage, ImageSource>(x => x) .ToProperty(this, nameof(Image)); _titleText = Observable.CombineLatest( this.WhenAny(x => x.ModList.Name), this.WhenAny(x => x.Slideshow.TargetMod.ModName) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(TitleText)); _authorText = Observable.CombineLatest( this.WhenAny(x => x.ModList.Author), this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(AuthorText)); _description = Observable.CombineLatest( this.WhenAny(x => x.ModList.Description), this.WhenAny(x => x.Slideshow.TargetMod.ModDescription) .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(Description)); _modListName = Observable.CombineLatest( this.WhenAny(x => x.ModList.Error) .Select(x => x != null), this.WhenAny(x => x.ModList) .Select(x => x?.Name), resultSelector: (err, name) => { if (err) { return("Corrupted Modlist"); } return(name); }) .ToProperty(this, nameof(ModListName)); // Define commands ShowReportCommand = ReactiveCommand.Create(ShowReport); OpenReadmeCommand = ReactiveCommand.Create( execute: () => this.ModList?.OpenReadmeWindow(), canExecute: this.WhenAny(x => x.ModList) .Select(modList => !string.IsNullOrEmpty(modList?.Readme)) .ObserveOnGuiThread()); VisitWebsiteCommand = ReactiveCommand.Create( execute: () => Process.Start(ModList.Website), canExecute: this.WhenAny(x => x.ModList.Website) .Select(x => x?.StartsWith("https://") ?? false) .ObserveOnGuiThread()); _progressTitle = Observable.CombineLatest( this.WhenAny(x => x.Installing), this.WhenAny(x => x.StartedInstallation), resultSelector: (installing, started) => { if (!installing) { return("Configuring"); } return(started ? "Installing" : "Installed"); }) .ToProperty(this, nameof(ProgressTitle)); Dictionary <int, CPUDisplayVM> cpuDisplays = new Dictionary <int, CPUDisplayVM>(); // Compile progress updates and populate ObservableCollection this.WhenAny(x => x.Installer.ActiveInstallation) .SelectMany(c => c?.QueueStatus ?? Observable.Empty <CPUStatus>()) .ObserveOn(RxApp.TaskpoolScheduler) // Attach start times to incoming CPU items .Scan( new CPUDisplayVM(), (_, cpu) => { var ret = cpuDisplays.TryCreate(cpu.ID); ret.AbsorbStatus(cpu); return(ret); }) .ToObservableChangeSet(x => x.Status.ID) .Batch(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer <CPUDisplayVM> .Ascending(s => s.StartTime)) .Bind(StatusList) .Subscribe() .DisposeWith(CompositeDisposable); BeginCommand = ReactiveCommand.CreateFromTask( canExecute: this.WhenAny(x => x.Installer.CanInstall) .Switch(), execute: async() => { try { await this.Installer.Install(); Completed = ErrorResponse.Success; try { this.ModList?.OpenReadmeWindow(); } catch (Exception ex) { Utils.Error(ex); } } 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"); Completed = ErrorResponse.Fail(ex); } }); // When sub installer begins an install, mark state variable BeginCommand.StartingExecution() .Subscribe(_ => { StartedInstallation = true; }) .DisposeWith(CompositeDisposable); // Listen for user interventions, and compile a dynamic list of all unhandled ones var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation) .SelectMany(c => c?.LogMessages ?? Observable.Empty <IStatusMessage>()) .WhereCastable <IStatusMessage, IUserIntervention>() .ToObservableChangeSet() .AutoRefresh(i => i.Handled) .Filter(i => !i.Handled) .AsObservableList(); // Find the top intervention /w no CPU ID to be marked as "global" _ActiveGlobalUserIntervention = activeInterventions.Connect() .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId) .QueryWhenChanged(query => query.FirstOrDefault()) .ObserveOnGuiThread() .ToProperty(this, nameof(ActiveGlobalUserIntervention)); CloseWhenCompleteCommand = ReactiveCommand.Create( canExecute: this.WhenAny(x => x.Completed) .Select(x => x != null), execute: () => { MWVM.ShutdownApplication(); }); GoToInstallCommand = ReactiveCommand.Create( canExecute: Observable.CombineLatest( this.WhenAny(x => x.Completed) .Select(x => x != null), this.WhenAny(x => x.Installer.SupportsAfterInstallNavigation), resultSelector: (complete, supports) => complete && supports), execute: () => { Installer.AfterInstallNavigation(); }); }
public CompilerVM(MainWindowVM mainWindowVM) { MWVM = mainWindowVM; OutputLocation = new FilePickerVM() { ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty, PathType = FilePickerVM.PathTypeOptions.Folder, PromptTitle = "Select the folder to place the resulting modlist.wabbajack file", }; // Load settings CompilerSettings settings = MWVM.Settings.Compiler; SelectedCompilerType = settings.LastCompiledModManager; OutputLocation.TargetPath = settings.OutputLocation; MWVM.Settings.SaveSignal .Subscribe(_ => { settings.LastCompiledModManager = SelectedCompilerType; settings.OutputLocation = OutputLocation.TargetPath; }) .DisposeWith(CompositeDisposable); // Swap to proper sub VM based on selected type _compiler = this.WhenAny(x => x.SelectedCompilerType) // Delay so the initial VM swap comes in immediately, subVM comes right after .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) .Select <ModManager, ISubCompilerVM>(type => { switch (type) { case ModManager.MO2: return(new MO2CompilerVM(this)); case ModManager.Vortex: return(new VortexCompilerVM(this)); default: return(null); } }) // Unload old VM .Pairwise() .Do(pair => { pair.Previous?.Unload(); }) .Select(p => p.Current) .ToProperty(this, nameof(Compiler)); // Let sub VM determine what settings we're displaying and when _currentModlistSettings = this.WhenAny(x => x.Compiler.ModlistSettings) .ToProperty(this, nameof(CurrentModlistSettings)); _image = this.WhenAny(x => x.CurrentModlistSettings.ImagePath.TargetPath) // Throttle so that it only loads image after any sets of swaps have completed .Throttle(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) .DistinctUntilChanged() .Select(path => { if (string.IsNullOrWhiteSpace(path)) { return(UIUtils.BitmapImageFromResource("Resources/Wabba_Mouth_No_Text.png")); } if (UIUtils.TryGetBitmapImageFromFile(path, out var image)) { return(image); } return(null); }) .ToProperty(this, nameof(Image)); _compiling = this.WhenAny(x => x.Compiler.ActiveCompilation) .Select(compilation => compilation != null) .ObserveOnGuiThread() .ToProperty(this, nameof(Compiling)); BackCommand = ReactiveCommand.Create( execute: () => { mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM; StartedCompilation = false; Completed = null; }, canExecute: this.WhenAny(x => x.Compiling) .Select(x => !x)); // Compile progress updates and populate ObservableCollection Dictionary <int, CPUDisplayVM> cpuDisplays = new Dictionary <int, CPUDisplayVM>(); this.WhenAny(x => x.Compiler.ActiveCompilation) .SelectMany(c => c?.QueueStatus ?? Observable.Empty <CPUStatus>()) .ObserveOn(RxApp.TaskpoolScheduler) // Attach start times to incoming CPU items .Scan( new CPUDisplayVM(), (_, cpu) => { var ret = cpuDisplays.TryCreate(cpu.ID); ret.AbsorbStatus(cpu); return(ret); }) .ToObservableChangeSet(x => x.Status.ID) .Batch(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer <CPUDisplayVM> .Ascending(s => s.StartTime)) .Bind(StatusList) .Subscribe() .DisposeWith(CompositeDisposable); _percentCompleted = this.WhenAny(x => x.Compiler.ActiveCompilation) .StartWith(default(ACompiler)) .CombineLatest( this.WhenAny(x => x.Completed), (compiler, completed) => { if (compiler == null) { return(Observable.Return <float>(completed != null ? 1f : 0f)); } return(compiler.PercentCompleted.StartWith(0)); }) .Switch() .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(); Completed = ErrorResponse.Success; } catch (Exception ex) { Completed = ErrorResponse.Fail(ex); while (ex.InnerException != null) { ex = ex.InnerException; } Utils.Error(ex, $"Compiler error"); } }); // When sub compiler begins a compile, mark state variable BeginCommand.StartingExecution() .Subscribe(_ => { StartedCompilation = true; }) .DisposeWith(CompositeDisposable); // Listen for user interventions, and compile a dynamic list of all unhandled ones var activeInterventions = this.WhenAny(x => x.Compiler.ActiveCompilation) .SelectMany(c => c?.LogMessages ?? Observable.Empty <IStatusMessage>()) .WhereCastable <IStatusMessage, IUserIntervention>() .ToObservableChangeSet() .AutoRefresh(i => i.Handled) .Filter(i => !i.Handled) .AsObservableList(); // Find the top intervention /w no CPU ID to be marked as "global" _ActiveGlobalUserIntervention = activeInterventions.Connect() .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId) .QueryWhenChanged(query => query.FirstOrDefault()) .ObserveOnGuiThread() .ToProperty(this, nameof(ActiveGlobalUserIntervention)); CloseWhenCompleteCommand = ReactiveCommand.Create( canExecute: this.WhenAny(x => x.Completed) .Select(x => x != null), execute: () => { MWVM.ShutdownApplication(); }); GoToModlistCommand = ReactiveCommand.Create( canExecute: this.WhenAny(x => x.Completed) .Select(x => x != null), execute: () => { if (string.IsNullOrWhiteSpace(OutputLocation.TargetPath)) { Process.Start("explorer.exe", Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)); } else { Process.Start("explorer.exe", OutputLocation.TargetPath); } }); }