public ModListGalleryVM(MainWindowVM mainWindowVM)
        {
            MWVM        = mainWindowVM;
            BackCommand = ReactiveCommand.Create(
                execute: () => mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM);
            RefreshCommand = ReactiveCommand.Create(() => { });

            RefreshCommand.StartingExecution()
            .StartWith(Unit.Default)
            .ObserveOn(RxApp.TaskpoolScheduler)
            .SelectTask(async _ =>
            {
                return((await ModlistMetadata.LoadFromGithub())
                       .AsObservableChangeSet(x => x.DownloadMetadata?.Hash ?? $"Fallback{missingHashFallbackCounter++}"));
            })
            .Switch()
            .ObserveOnGuiThread()
            .Transform(m => new ModListMetadataVM(this, m))
            .Bind(ModLists)
            .Subscribe()
            .DisposeWith(CompositeDisposable);
        }
Example #2
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();
            });
        }