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();
        }
Exemple #5
0
        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));
        }
Exemple #6
0
        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));
        }
Exemple #7
0
        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);
        }
Exemple #8
0
        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));
        }
Exemple #9
0
        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);
        }
Exemple #10
0
        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();
            });
        }
Exemple #11
0
        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);
                }
            });
        }