using System; using System.Linq; using System.Net.Http; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Avalonia.Media; using Avalonia.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; using Wabbajack.App.ViewModels.SubViewModels; using Wabbajack.Common; using Wabbajack.DTOs; using Wabbajack.DTOs.DownloadStates; using Wabbajack.DTOs.JsonConverters; using Wabbajack.Installer; using Wabbajack.RateLimiter; namespace Wabbajack.App.ViewModels { public class StandardInstallationViewModel : ViewModelBase, IReceiver { private readonly IServiceProvider _provider; private readonly GameLocator _locator; private IServiceScope _scope; private InstallerConfiguration _config; private StandardInstaller _installer; private readonly ILogger _logger; private readonly DTOSerializer _dtos; private SlideViewModel[] _slides = Array.Empty(); private readonly HttpClient _httpClient; private Timer _slideTimer; private int _currentSlideIndex; [Reactive] public SlideViewModel Slide { get; set; } [Reactive] public ReactiveCommand NextCommand { get; set; } [Reactive] public ReactiveCommand PrevCommand { get; set; } [Reactive] public ReactiveCommand PauseCommand { get; set; } [Reactive] public ReactiveCommand PlayCommand { get; set; } [Reactive] public bool IsPlaying { get; set; } = true; [Reactive] public string StatusText { get; set; } = ""; [Reactive] public Percent StepsProgress { get; set; } = Percent.Zero; [Reactive] public Percent StepProgress { get; set; } = Percent.Zero; public StandardInstallationViewModel(ILogger logger, IServiceProvider provider, GameLocator locator, DTOSerializer dtos, HttpClient httpClient) { _provider = provider; _locator = locator; _logger = logger; _dtos = dtos; _httpClient = httpClient; Activator = new ViewModelActivator(); this.WhenActivated(disposables => { _slideTimer = new Timer(_ => { if (IsPlaying) NextSlide(1); }, null, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(5)); _currentSlideIndex = 0; _slideTimer.DisposeWith(disposables); NextCommand = ReactiveCommand.Create(() => NextSlide(1)) .DisposeWith(disposables); PrevCommand = ReactiveCommand.Create(() => NextSlide(-1)) .DisposeWith(disposables); PauseCommand = ReactiveCommand.Create(() => IsPlaying = false, this.ObservableForProperty(vm => vm.IsPlaying, skipInitial:false) .Select(v => v.Value)) .DisposeWith(disposables); PlayCommand = ReactiveCommand.Create(() => IsPlaying = true, this.ObservableForProperty(vm => vm.IsPlaying, skipInitial:false) .Select(v => !v.Value)) .DisposeWith(disposables); }); } private void NextSlide(int direction) { if (_slides.Length == 0) return; _currentSlideIndex = InSlideRange(_currentSlideIndex + direction); var thisSlide = _slides[_currentSlideIndex]; if (thisSlide.Image == null) thisSlide.PreCache(_httpClient).FireAndForget(); // Cache the next image _slides[InSlideRange(_currentSlideIndex + 1)].PreCache(_httpClient).FireAndForget(); var prevSlide = _slides[InSlideRange(_currentSlideIndex - 2)]; if (prevSlide.Image != null) prevSlide.Image = null; Dispatcher.UIThread.InvokeAsync(() => { Slide = thisSlide; }); } private int InSlideRange(int i) { while (!(i >= 0 && i <= _slides.Length)) { if (i >= _slides.Length) i -= _slides.Length; if (i < 0) i += _slides.Length; } return i; } public void Receive(StartInstallation msg) { Install(msg).FireAndForget(); } private async Task Install(StartInstallation msg) { _scope = _provider.CreateScope(); _config = _provider.GetService()!; _config.Downloads = msg.Download; _config.Install = msg.Install; _config.ModlistArchive = msg.ModListPath; _logger.LogInformation("Loading ModList Data"); _config.ModList = await StandardInstaller.LoadFromFile(_dtos, msg.ModListPath); _config.Game = _config.ModList.GameType; _slides = _config.ModList.Archives.Select(a => a.State).OfType() .Select(m => new SlideViewModel { MetaState = m }) .Shuffle() .ToArray(); _slides[1].PreCache(_httpClient).FireAndForget(); Slide = _slides[1]; if (_config.GameFolder == default) { if (!_locator.TryFindLocation(_config.Game, out var found)) { _logger.LogCritical("Game {game} is not installed on this system", _config.Game.MetaData().HumanFriendlyGameName); throw new Exception("Game not found"); } _config.GameFolder = found; } _installer = _provider.GetService()!; _installer.OnStatusUpdate += (_, update) => { Dispatcher.UIThread.InvokeAsync(() => { StatusText = update.StatusText; StepsProgress = update.StepsProgress; StepProgress = update.StepProgress; }); }; _logger.LogInformation("Installer created, starting the installation process"); await _installer.Begin(CancellationToken.None); } } }