diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 6d7b3d5a..71b18ee9 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -43,7 +43,7 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore - name: Test - run: dotnet test --no-restore --filter "Category=!FlakeyNetwork" + run: dotnet test --no-restore --filter "Category!=FlakeyNetwork" publish: name: Publish Projects diff --git a/Wabbajack.App.Test/Extensions.cs b/Wabbajack.App.Test/Extensions.cs index de3b9eb2..48a3fb2a 100644 --- a/Wabbajack.App.Test/Extensions.cs +++ b/Wabbajack.App.Test/Extensions.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Avalonia.Threading; -using ReactiveUI; using Wabbajack.App.Models; namespace Wabbajack.App.Test; @@ -28,6 +27,7 @@ public static class Extensions await Task.Delay(100); } } + public static async Task WaitForUnlock(this LoadingLock l) { Dispatcher.UIThread.RunJobs(); diff --git a/Wabbajack.App.Test/GalleryItemTests.cs b/Wabbajack.App.Test/GalleryItemTests.cs index 1c385191..1a0e03bd 100644 --- a/Wabbajack.App.Test/GalleryItemTests.cs +++ b/Wabbajack.App.Test/GalleryItemTests.cs @@ -1,5 +1,4 @@ using System; -using System.Data; using System.Linq; using System.Threading.Tasks; using Wabbajack.App.Controls; @@ -15,23 +14,21 @@ namespace Wabbajack.App.Test; public class GalleryItemTests { - private readonly BrowseViewModel _gallery; private readonly Configuration _config; + private readonly BrowseViewModel _gallery; public GalleryItemTests(BrowseViewModel bvm, Configuration config) { _config = config; _gallery = bvm; } - + [Fact] public async Task CanDownloadGalleryItem() { foreach (var file in _config.ModListsDownloadLocation.EnumerateFiles().Where(f => f.Extension == Ext.Wabbajack)) - { file.Delete(); - } - + using var _ = _gallery.Activator.Activate(); await _gallery.LoadingLock.WaitForLock(); await _gallery.LoadingLock.WaitForUnlock(); @@ -44,7 +41,7 @@ public class GalleryItemTests Assert.True(item.ModListLocation.FileExists()); else Assert.False(item.ModListLocation.FileExists()); - + Assert.Equal(Percent.Zero, item.Progress); } @@ -58,18 +55,18 @@ public class GalleryItemTests Assert.True(modList.Progress >= progress); progress = modList.Progress; }); - + Assert.Equal(Percent.Zero, modList.Progress); - Assert.Equal(ModListState.Downloaded, modList.State); - - + Assert.Equal(ModListState.Downloaded, modList.State); + + modList.ExecuteCommand.Execute().Subscribe().Dispose(); var msgs = ((SimpleMessageBus) MessageBus.Instance).Messages.TakeLast(2).ToArray(); var configure = msgs.OfType().First(); Assert.Equal(modList.ModListLocation, configure.ModList); - + var navigate = msgs.OfType().First(); Assert.Equal(typeof(InstallConfigurationViewModel), navigate.ViewModel); } diff --git a/Wabbajack.App.Test/Startup.cs b/Wabbajack.App.Test/Startup.cs index 70c22ec8..8c5f4941 100644 --- a/Wabbajack.App.Test/Startup.cs +++ b/Wabbajack.App.Test/Startup.cs @@ -1,35 +1,31 @@ using System.Collections.Generic; -using Avalonia.Threading; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Wabbajack.App; -using Wabbajack.Networking.WabbajackClientApi; -using Wabbajack.Services.OSIntegrated; using Xunit.DependencyInjection; using Xunit.DependencyInjection.Logging; -namespace Wabbajack.App.Test -{ - public class Startup - { - public void ConfigureServices(IServiceCollection service) - { - service.AddAppServices(); - } +namespace Wabbajack.App.Test; - public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) - { - loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor, delegate { return true; })); - MessageBus.Instance = new SimpleMessageBus(); - } +public class Startup +{ + public void ConfigureServices(IServiceCollection service) + { + service.AddAppServices(); } - public class SimpleMessageBus : IMessageBus + public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) { - public List Messages { get; } = new(); - public void Send(T message) - { - Messages.Add(message); - } + loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor, delegate { return true; })); + MessageBus.Instance = new SimpleMessageBus(); + } +} + +public class SimpleMessageBus : IMessageBus +{ + public List Messages { get; } = new(); + + public void Send(T message) + { + Messages.Add(message); } } \ No newline at end of file diff --git a/Wabbajack.App.Test/Wabbajack.App.Test.csproj b/Wabbajack.App.Test/Wabbajack.App.Test.csproj index 951bf570..5f5ec84d 100644 --- a/Wabbajack.App.Test/Wabbajack.App.Test.csproj +++ b/Wabbajack.App.Test/Wabbajack.App.Test.csproj @@ -7,21 +7,21 @@ enable - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/Wabbajack.App/App.axaml b/Wabbajack.App/App.axaml index 5e6570a9..842e97aa 100644 --- a/Wabbajack.App/App.axaml +++ b/Wabbajack.App/App.axaml @@ -1,25 +1,24 @@ - + - - - + + + - - + + - + \ No newline at end of file diff --git a/Wabbajack.App/App.axaml.cs b/Wabbajack.App/App.axaml.cs index e39aa84d..05bab6da 100644 --- a/Wabbajack.App/App.axaml.cs +++ b/Wabbajack.App/App.axaml.cs @@ -3,80 +3,66 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using CefNet; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ReactiveUI; using Splat; -using Wabbajack.App.Controls; using Wabbajack.App.Converters; -using Wabbajack.App.Interfaces; -using Wabbajack.App.Models; using Wabbajack.App.Utilities; -using Wabbajack.App.ViewModels; using Wabbajack.App.Views; -using Wabbajack.DTOs.JsonConverters; -using Wabbajack.Networking.NexusApi; -using Wabbajack.Services.OSIntegrated; -namespace Wabbajack.App +namespace Wabbajack.App; + +public class App : Application { - public class App : Application + public static IServiceProvider Services { get; private set; } = null!; + public static Window? MainWindow { get; set; } + + public static event EventHandler FrameworkInitialized; + public static event EventHandler FrameworkShutdown; + + public override void Initialize() { - - public static event EventHandler FrameworkInitialized; - public static event EventHandler FrameworkShutdown; - public static IServiceProvider Services { get; private set; } = null!; - public static Window? MainWindow { get; set; } - public override void Initialize() + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + var host = Host.CreateDefaultBuilder(Array.Empty()) + .ConfigureLogging(c => { c.ClearProviders(); }) + .ConfigureServices((host, services) => { services.AddAppServices(); }).Build(); + Services = host.Services; + + SetupConverters(); + + // Need to startup the message bus; + Services.GetService(); + var app = Services.GetService(); + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - AvaloniaXamlLoader.Load(this); + desktop.MainWindow = new MainWindow(); + desktop.Startup += Startup; + desktop.Exit += Exit; + MainWindow = desktop.MainWindow; } - public override void OnFrameworkInitializationCompleted() - { - var host = Host.CreateDefaultBuilder(Array.Empty()) - .ConfigureLogging(c => - { - c.ClearProviders(); - }) - .ConfigureServices((host, services) => - { - services.AddAppServices(); - }).Build(); - Services = host.Services; + base.OnFrameworkInitializationCompleted(); + } - SetupConverters(); + private void Startup(object sender, ControlledApplicationLifetimeStartupEventArgs e) + { + FrameworkInitialized?.Invoke(this, EventArgs.Empty); + } - // Need to startup the message bus; - Services.GetService(); - var app = Services.GetService(); + private void Exit(object sender, ControlledApplicationLifetimeExitEventArgs e) + { + FrameworkShutdown?.Invoke(this, EventArgs.Empty); + } - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new MainWindow(); - desktop.Startup += Startup; - desktop.Exit += Exit; - MainWindow = desktop.MainWindow; - } - - base.OnFrameworkInitializationCompleted(); - } - - private void Startup(object sender, ControlledApplicationLifetimeStartupEventArgs e) - { - FrameworkInitialized?.Invoke(this, EventArgs.Empty); - } - - private void Exit(object sender, ControlledApplicationLifetimeExitEventArgs e) - { - FrameworkShutdown?.Invoke(this, EventArgs.Empty); - } - - private void SetupConverters() - { - Locator.CurrentMutable.RegisterConstant(new AbsoultePathBindingConverter()); - } + private void SetupConverters() + { + Locator.CurrentMutable.RegisterConstant(new AbsoultePathBindingConverter()); } } \ No newline at end of file diff --git a/Wabbajack.App/Assets/Wabbajack.axaml b/Wabbajack.App/Assets/Wabbajack.axaml index 8f5e0258..f1a943c4 100644 --- a/Wabbajack.App/Assets/Wabbajack.axaml +++ b/Wabbajack.App/Assets/Wabbajack.axaml @@ -7,36 +7,33 @@ - + - + - - - - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/Wabbajack.App/Assets/cloud-download-alt-solid.svg b/Wabbajack.App/Assets/cloud-download-alt-solid.svg index 5e47b456..e16eccaa 100644 --- a/Wabbajack.App/Assets/cloud-download-alt-solid.svg +++ b/Wabbajack.App/Assets/cloud-download-alt-solid.svg @@ -1 +1,6 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Wabbajack.App/Configuration.cs b/Wabbajack.App/Configuration.cs index 96f71dcf..57d88f29 100644 --- a/Wabbajack.App/Configuration.cs +++ b/Wabbajack.App/Configuration.cs @@ -1,14 +1,13 @@ using Wabbajack.Paths; -namespace Wabbajack.App +namespace Wabbajack.App; + +public class Configuration { - public class Configuration - { - public AbsolutePath ModListsDownloadLocation { get; set; } - public AbsolutePath SavedSettingsLocation { get; set; } - - public AbsolutePath EncryptedDataLocation { get; set; } - - public AbsolutePath LogLocation { get; set; } - } + public AbsolutePath ModListsDownloadLocation { get; set; } + public AbsolutePath SavedSettingsLocation { get; set; } + + public AbsolutePath EncryptedDataLocation { get; set; } + + public AbsolutePath LogLocation { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/BrowseItemView.axaml b/Wabbajack.App/Controls/BrowseItemView.axaml index 07017f5c..1d58bfcd 100644 --- a/Wabbajack.App/Controls/BrowseItemView.axaml +++ b/Wabbajack.App/Controls/BrowseItemView.axaml @@ -42,12 +42,12 @@ - + - + @@ -89,7 +89,7 @@ + x:Name="ExecuteIcon" /> diff --git a/Wabbajack.App/Controls/BrowseItemView.axaml.cs b/Wabbajack.App/Controls/BrowseItemView.axaml.cs index 4b48a9a8..3a29a307 100644 --- a/Wabbajack.App/Controls/BrowseItemView.axaml.cs +++ b/Wabbajack.App/Controls/BrowseItemView.axaml.cs @@ -1,56 +1,51 @@ using System; using System.Reactive.Disposables; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using Material.Icons; using ReactiveUI; -using Wabbajack.App.Views; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public partial class BrowseItemView : ReactiveUserControl { - public partial class BrowseItemView : ReactiveUserControl + public BrowseItemView() { - public BrowseItemView() + InitializeComponent(); + + this.WhenActivated(disposables => { - InitializeComponent(); + this.OneWayBind(ViewModel, vm => vm.Title, view => view.Title.Text) + .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Description, view => view.Description.Text) + .DisposeWith(disposables); - this.WhenActivated(disposables => - { - this.OneWayBind(ViewModel, vm => vm.Title, view => view.Title.Text) - .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Description, view => view.Description.Text) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source) - .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.OpenWebsiteCommand, view => view.OpenWebsiteButton) + .DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.OpenWebsiteCommand, view => view.OpenWebsiteButton) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.State, view => view.ExecuteIcon.Kind, s => StateToKind(s)); + this.BindCommand(ViewModel, vm => vm.ExecuteCommand, view => view.ExecuteButton) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.State, view => view.ExecuteIcon.Kind, s => StateToKind(s)); - this.BindCommand(ViewModel, vm => vm.ExecuteCommand, view => view.ExecuteButton) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Progress, view => view.DownloadProgressBar.Value, + s => s.Value * 1000) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Progress, view => view.DownloadProgressBar.Value, - s => s.Value * 1000) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Tags, view => view.TagsList.Items) + .DisposeWith(disposables); + }); + } - this.OneWayBind(ViewModel, vm => vm.Tags, view => view.TagsList.Items) - .DisposeWith(disposables); - }); - } - - private MaterialIconKind StateToKind(ModListState modListState) + private MaterialIconKind StateToKind(ModListState modListState) + { + return modListState switch { - return modListState switch - { - ModListState.Downloaded => MaterialIconKind.PlayArrow, - ModListState.Downloading => MaterialIconKind.LocalAreaNetworkPending, - ModListState.NotDownloaded => MaterialIconKind.Download, - _ => throw new ArgumentOutOfRangeException(nameof(modListState), modListState, null) - }; - } + ModListState.Downloaded => MaterialIconKind.PlayArrow, + ModListState.Downloading => MaterialIconKind.LocalAreaNetworkPending, + ModListState.NotDownloaded => MaterialIconKind.Download, + _ => throw new ArgumentOutOfRangeException(nameof(modListState), modListState, null) + }; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/BrowseItemViewModel.cs b/Wabbajack.App/Controls/BrowseItemViewModel.cs index ea99c35a..1c3c50e8 100644 --- a/Wabbajack.App/Controls/BrowseItemViewModel.cs +++ b/Wabbajack.App/Controls/BrowseItemViewModel.cs @@ -4,13 +4,10 @@ using System.Linq; using System.Net.Http; using System.Reactive; using System.Reactive.Linq; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Avalonia.Media.Imaging; using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic.CompilerServices; -using Octokit; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; @@ -20,90 +17,64 @@ using Wabbajack.Downloaders; using Wabbajack.Downloaders.GameFile; using Wabbajack.DTOs; using Wabbajack.DTOs.JsonConverters; -using Wabbajack.Installer; using Wabbajack.Paths; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; using Wabbajack.VFS; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public enum ModListState { - public enum ModListState + Downloaded, + NotDownloaded, + Downloading +} + +public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel +{ + private readonly HttpClient _client; + private readonly Configuration _configuration; + private readonly DownloadDispatcher _dispatcher; + private readonly IResource _downloadLimiter; + private readonly DTOSerializer _dtos; + private readonly FileHashCache _hashCache; + private readonly IResource _limiter; + private readonly ILogger _logger; + private readonly ModlistMetadata _metadata; + private readonly ModListSummary _summary; + + public BrowseItemViewModel(ModlistMetadata metadata, ModListSummary summary, HttpClient client, + IResource limiter, + FileHashCache hashCache, Configuration configuration, DownloadDispatcher dispatcher, + IResource downloadLimiter, GameLocator gameLocator, + DTOSerializer dtos, ILogger logger) { - Downloaded, - NotDownloaded, - Downloading - } + Activator = new ViewModelActivator(); + _metadata = metadata; + _summary = summary; + _client = client; + _limiter = limiter; + _hashCache = hashCache; + _configuration = configuration; + _dispatcher = dispatcher; + _downloadLimiter = downloadLimiter; + _logger = logger; + _dtos = dtos; - public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel - { - private readonly ModlistMetadata _metadata; - private readonly ModListSummary _summary; - private readonly HttpClient _client; - private readonly IResource _limiter; - private readonly FileHashCache _hashCache; - private readonly Configuration _configuration; - private readonly DownloadDispatcher _dispatcher; - private readonly ILogger _logger; - private readonly IResource _downloadLimiter; - private readonly DTOSerializer _dtos; + var haveGame = gameLocator.IsInstalled(_metadata.Game); + Tags = metadata.tags + .Select(t => new TagViewModel(t, "ModList")) + .Prepend(new TagViewModel(_metadata.Game.MetaData().HumanFriendlyGameName, + haveGame ? "Game" : "GameNotInstalled")) + .ToArray(); - public string Title => _metadata.ImageContainsTitle ? "" : _metadata.Title; - public string MachineURL => _metadata.Links.MachineURL; - public string Description => _metadata.Description; - - public Uri ImageUri => new(_metadata.Links.ImageUri); - - [Reactive] - public IBitmap Image { get; set; } - - [Reactive] - public ModListState State { get; set; } - - [Reactive] - public ReactiveCommand ExecuteCommand { get; set; } - - [Reactive] - public Percent Progress { get; set; } - - public AbsolutePath ModListLocation => _configuration.ModListsDownloadLocation.Combine(_metadata.Links.MachineURL).WithExtension(Ext.Wabbajack); - - public Game Game => _metadata.Game; - - public bool IsUtilityList => _metadata.UtilityList; - public bool IsNSFW => _metadata.NSFW; - - [Reactive] - public TagViewModel[] Tags { get; set; } - - public BrowseItemViewModel(ModlistMetadata metadata, ModListSummary summary, HttpClient client, IResource limiter, - FileHashCache hashCache, Configuration configuration, DownloadDispatcher dispatcher, IResource downloadLimiter, GameLocator gameLocator, - DTOSerializer dtos, ILogger logger) + OpenWebsiteCommand = ReactiveCommand.Create(() => { - Activator = new ViewModelActivator(); - _metadata = metadata; - _summary = summary; - _client = client; - _limiter = limiter; - _hashCache = hashCache; - _configuration = configuration; - _dispatcher = dispatcher; - _downloadLimiter = downloadLimiter; - _logger = logger; - _dtos = dtos; - - var haveGame = gameLocator.IsInstalled(_metadata.Game); - Tags = metadata.tags - .Select(t => new TagViewModel(t, "ModList")) - .Prepend(new TagViewModel(_metadata.Game.MetaData().HumanFriendlyGameName, haveGame ? "Game" : "GameNotInstalled")) - .ToArray(); - - OpenWebsiteCommand = ReactiveCommand.Create(() => - { - Utils.OpenWebsiteInExternalBrowser(new Uri(_metadata.Links.Readme)); - }); + Utils.OpenWebsiteInExternalBrowser(new Uri(_metadata.Links.Readme)); + }); - ExecuteCommand = ReactiveCommand.Create(() => + ExecuteCommand = ReactiveCommand.Create(() => { if (State == ModListState.Downloaded) { @@ -114,80 +85,103 @@ namespace Wabbajack.App.Controls { DownloadModList().FireAndForget(); } + }, + this.ObservableForProperty(t => t.State) + .Select(c => c.Value != ModListState.Downloading) + .StartWith(true)); - }, - this.ObservableForProperty(t => t.State) - .Select(c => c.Value != ModListState.Downloading) - .StartWith(true)); + LoadListImage().FireAndForget(); + UpdateState().FireAndForget(); + } - LoadListImage().FireAndForget(); - UpdateState().FireAndForget(); - } + public string Title => _metadata.ImageContainsTitle ? "" : _metadata.Title; + public string MachineURL => _metadata.Links.MachineURL; + public string Description => _metadata.Description; - private async Task DownloadModList() + public Uri ImageUri => new(_metadata.Links.ImageUri); + + [Reactive] public IBitmap Image { get; set; } + + [Reactive] public ModListState State { get; set; } + + [Reactive] public ReactiveCommand ExecuteCommand { get; set; } + + [Reactive] public Percent Progress { get; set; } + + public AbsolutePath ModListLocation => _configuration.ModListsDownloadLocation.Combine(_metadata.Links.MachineURL) + .WithExtension(Ext.Wabbajack); + + public Game Game => _metadata.Game; + + public bool IsUtilityList => _metadata.UtilityList; + public bool IsNSFW => _metadata.NSFW; + + [Reactive] public TagViewModel[] Tags { get; set; } + + + public ReactiveCommand OpenWebsiteCommand { get; set; } + + private async Task DownloadModList() + { + State = ModListState.Downloading; + var state = _dispatcher.Parse(new Uri(_metadata.Links.Download)); + var archive = new Archive { - State = ModListState.Downloading; - var state = _dispatcher.Parse(new Uri(_metadata.Links.Download)); - var archive = new Archive - { - State = state!, - Hash = _metadata.DownloadMetadata?.Hash ?? default, - Size = _metadata.DownloadMetadata?.Size ?? 0, - Name = ModListLocation.FileName.ToString() - }; + State = state!, + Hash = _metadata.DownloadMetadata?.Hash ?? default, + Size = _metadata.DownloadMetadata?.Size ?? 0, + Name = ModListLocation.FileName.ToString() + }; - using var job = await _downloadLimiter.Begin(state!.PrimaryKeyString, archive.Size, CancellationToken.None); - - var hashTask = _dispatcher.Download(archive, ModListLocation, job, CancellationToken.None); + using var job = await _downloadLimiter.Begin(state!.PrimaryKeyString, archive.Size, CancellationToken.None); - while (!hashTask.IsCompleted) - { - Progress = Percent.FactoryPutInRange(job.Current, job.Size ?? 0); - await Task.Delay(100); - } - - var hash = await hashTask; - if (hash != _metadata.DownloadMetadata?.Hash) - { - _logger.LogWarning("Hash files didn't match after downloading modlist, deleting modlist"); - if (ModListLocation.FileExists()) - ModListLocation.Delete(); - } - - _hashCache.FileHashWriteCache(ModListLocation, hash); + var hashTask = _dispatcher.Download(archive, ModListLocation, job, CancellationToken.None); - var metadataPath = ModListLocation.WithExtension(Ext.MetaData); - await metadataPath.WriteAllTextAsync(_dtos.Serialize(_metadata)); - - Progress = Percent.Zero; - await UpdateState(); - } - - - public ReactiveCommand OpenWebsiteCommand { get; set; } - - - public async Task LoadListImage() + while (!hashTask.IsCompleted) { - using var job = await _limiter.Begin("Loading modlist image", 0, CancellationToken.None); - var response = await _client.GetByteArrayAsync(ImageUri); - Image = new Bitmap(new MemoryStream(response)); + Progress = Percent.FactoryPutInRange(job.Current, job.Size ?? 0); + await Task.Delay(100); } - public async Task GetState() + var hash = await hashTask; + if (hash != _metadata.DownloadMetadata?.Hash) { - var file = ModListLocation; - if (!file.FileExists()) - return ModListState.NotDownloaded; - - return (await _hashCache.FileHashCachedAsync(file, CancellationToken.None)) != - _metadata.DownloadMetadata?.Hash ? ModListState.NotDownloaded : ModListState.Downloaded; + _logger.LogWarning("Hash files didn't match after downloading modlist, deleting modlist"); + if (ModListLocation.FileExists()) + ModListLocation.Delete(); } - public async Task UpdateState() - { - State = await GetState(); - } - + _hashCache.FileHashWriteCache(ModListLocation, hash); + + var metadataPath = ModListLocation.WithExtension(Ext.MetaData); + await metadataPath.WriteAllTextAsync(_dtos.Serialize(_metadata)); + + Progress = Percent.Zero; + await UpdateState(); + } + + + public async Task LoadListImage() + { + using var job = await _limiter.Begin("Loading modlist image", 0, CancellationToken.None); + var response = await _client.GetByteArrayAsync(ImageUri); + Image = new Bitmap(new MemoryStream(response)); + } + + public async Task GetState() + { + var file = ModListLocation; + if (!file.FileExists()) + return ModListState.NotDownloaded; + + return await _hashCache.FileHashCachedAsync(file, CancellationToken.None) != + _metadata.DownloadMetadata?.Hash + ? ModListState.NotDownloaded + : ModListState.Downloaded; + } + + public async Task UpdateState() + { + State = await GetState(); } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/FileSelectionBox.axaml b/Wabbajack.App/Controls/FileSelectionBox.axaml index 4b6c8f88..ca050094 100644 --- a/Wabbajack.App/Controls/FileSelectionBox.axaml +++ b/Wabbajack.App/Controls/FileSelectionBox.axaml @@ -5,37 +5,37 @@ xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.FileSelectionBox"> - + - + - + - + - + - + - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/FileSelectionBox.axaml.cs b/Wabbajack.App/Controls/FileSelectionBox.axaml.cs index 4e5c3a0f..92bd55e4 100644 --- a/Wabbajack.App/Controls/FileSelectionBox.axaml.cs +++ b/Wabbajack.App/Controls/FileSelectionBox.axaml.cs @@ -1,73 +1,70 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using Microsoft.Extensions.DependencyInjection; using ReactiveUI; using Wabbajack.Paths; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public partial class FileSelectionBox : ReactiveUserControl { - public partial class FileSelectionBox : ReactiveUserControl + public static readonly DirectProperty SelectedPathProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectedPath), o => o.SelectedPath); + + public static readonly StyledProperty AllowedExtensionsProperty = + AvaloniaProperty.Register(nameof(AllowedExtensions)); + + public static readonly StyledProperty SelectFolderProperty = + AvaloniaProperty.Register(nameof(SelectFolder)); + + private AbsolutePath _selectedPath; + + public FileSelectionBox() { - public FileSelectionBox() + DataContext = App.Services.GetService()!; + InitializeComponent(); + + this.WhenActivated(disposables => { - DataContext = App.Services.GetService()!; - InitializeComponent(); - - this.WhenActivated(disposables => - { - this.Bind(ViewModel, vm => vm.Path, view => view.SelectedPath) - .DisposeWith(disposables); - this.WhenAnyValue(view => view.SelectFolder) - .BindTo(ViewModel, vm => vm.SelectFolder) - .DisposeWith(disposables); - this.WhenAnyValue(view => view.AllowedExtensions) - .Where(exts => !string.IsNullOrWhiteSpace(exts)) - .Select(exts => - exts.Split("|", StringSplitOptions.RemoveEmptyEntries).Select(s => new Extension(s)).ToArray()) - .BindTo(ViewModel, vm => vm.Extensions) - .DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.Path, + this.Bind(ViewModel, vm => vm.Path, view => view.SelectedPath) + .DisposeWith(disposables); + this.WhenAnyValue(view => view.SelectFolder) + .BindTo(ViewModel, vm => vm.SelectFolder) + .DisposeWith(disposables); + this.WhenAnyValue(view => view.AllowedExtensions) + .Where(exts => !string.IsNullOrWhiteSpace(exts)) + .Select(exts => + exts.Split("|", StringSplitOptions.RemoveEmptyEntries).Select(s => new Extension(s)).ToArray()) + .BindTo(ViewModel, vm => vm.Extensions) + .DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.Path, view => view.TextBox.Text) - .DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.BrowseCommand, + .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.BrowseCommand, view => view.SelectButton) - .DisposeWith(disposables); - }); - } + .DisposeWith(disposables); + }); + } - public static readonly DirectProperty SelectedPathProperty = - AvaloniaProperty.RegisterDirect(nameof(SelectedPath), o => o.SelectedPath); + public AbsolutePath SelectedPath + { + get => _selectedPath; + set => SetAndRaise(SelectedPathProperty, ref _selectedPath, value); + } - private AbsolutePath _selectedPath; - public AbsolutePath SelectedPath - { - get => _selectedPath; - set => SetAndRaise(SelectedPathProperty, ref _selectedPath, value); - } + public string AllowedExtensions + { + get => GetValue(AllowedExtensionsProperty); + set => SetValue(AllowedExtensionsProperty, value); + } - public static readonly StyledProperty AllowedExtensionsProperty = - AvaloniaProperty.Register(nameof(AllowedExtensions)); - public string AllowedExtensions - { - get => GetValue(AllowedExtensionsProperty); - set => SetValue(AllowedExtensionsProperty, value); - } - - public static readonly StyledProperty SelectFolderProperty = - AvaloniaProperty.Register(nameof(SelectFolder)); - - public bool SelectFolder - { - get => GetValue(SelectFolderProperty); - set => SetValue(SelectFolderProperty, value); - } + public bool SelectFolder + { + get => GetValue(SelectFolderProperty); + set => SetValue(SelectFolderProperty, value); } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/FileSelectionBoxViewModel.cs b/Wabbajack.App/Controls/FileSelectionBoxViewModel.cs index 417a7282..92e688bf 100644 --- a/Wabbajack.App/Controls/FileSelectionBoxViewModel.cs +++ b/Wabbajack.App/Controls/FileSelectionBoxViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reactive; using System.Reactive.Disposables; @@ -9,57 +10,52 @@ using ReactiveUI.Fody.Helpers; using Wabbajack.App.ViewModels; using Wabbajack.Paths; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public class FileSelectionBoxViewModel : ViewModelBase { - public class FileSelectionBoxViewModel : ViewModelBase + public FileSelectionBoxViewModel() { - [Reactive] public AbsolutePath Path { get; set; } - - [Reactive] public Extension[] Extensions { get; set; } = Array.Empty(); - - [Reactive] public bool SelectFolder { get; set; } - - [Reactive] - public ReactiveCommand BrowseCommand { get; set; } = null!; - - public FileSelectionBoxViewModel() + Activator = new ViewModelActivator(); + this.WhenActivated(disposables => { - - Activator = new ViewModelActivator(); - this.WhenActivated(disposables => + BrowseCommand = ReactiveCommand.Create(async () => { - BrowseCommand = ReactiveCommand.Create(async () => + if (SelectFolder) { - if (SelectFolder) + var dialog = new OpenFolderDialog { - var dialog = new OpenFolderDialog() - { - Title = "Select a folder", - }; - var result = await dialog.ShowAsync(App.MainWindow); - if (result != null) - Path = result.ToAbsolutePath(); - } - else + Title = "Select a folder" + }; + var result = await dialog.ShowAsync(App.MainWindow); + if (result != null) + Path = result.ToAbsolutePath(); + } + else + { + var extensions = Extensions.Select(e => e.ToString()[1..]).ToList(); + var dialog = new OpenFileDialog { - var extensions = Extensions.Select(e => e.ToString()[1..]).ToList(); - var dialog = new OpenFileDialog + AllowMultiple = false, + Title = "Select a file", + Filters = new List { - AllowMultiple = false, - Title = "Select a file", - Filters = new() - { - new FileDialogFilter { Extensions = extensions, Name = "*" } - } - }; - var results = await dialog.ShowAsync(App.MainWindow); - if (results != null) - Path = results!.First().ToAbsolutePath(); - } - }).DisposeWith(disposables); - }); - - } - + new FileDialogFilter {Extensions = extensions, Name = "*"} + } + }; + var results = await dialog.ShowAsync(App.MainWindow); + if (results != null) + Path = results!.First().ToAbsolutePath(); + } + }).DisposeWith(disposables); + }); } + + [Reactive] public AbsolutePath Path { get; set; } + + [Reactive] public Extension[] Extensions { get; set; } = Array.Empty(); + + [Reactive] public bool SelectFolder { get; set; } + + [Reactive] public ReactiveCommand BrowseCommand { get; set; } = null!; } \ No newline at end of file diff --git a/Wabbajack.App/Controls/GameSelectorItemView.axaml b/Wabbajack.App/Controls/GameSelectorItemView.axaml index 3bf83ea1..644775a9 100644 --- a/Wabbajack.App/Controls/GameSelectorItemView.axaml +++ b/Wabbajack.App/Controls/GameSelectorItemView.axaml @@ -4,5 +4,5 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.GameSelectorItemView"> - - + + \ No newline at end of file diff --git a/Wabbajack.App/Controls/GameSelectorItemView.axaml.cs b/Wabbajack.App/Controls/GameSelectorItemView.axaml.cs index 77057689..3b8cbd73 100644 --- a/Wabbajack.App/Controls/GameSelectorItemView.axaml.cs +++ b/Wabbajack.App/Controls/GameSelectorItemView.axaml.cs @@ -1,24 +1,19 @@ using System.Reactive.Disposables; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; using ReactiveUI; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public partial class GameSelectorItemView : ReactiveUserControl { - public partial class GameSelectorItemView : ReactiveUserControl + public GameSelectorItemView() { - public GameSelectorItemView() + InitializeComponent(); + + this.WhenActivated(disposables => { - InitializeComponent(); - - this.WhenActivated(disposables => - { - this.OneWayBind(ViewModel, vm => vm.Name, view => view.GameName.Text) - .DisposeWith(disposables); - }); - } - + this.OneWayBind(ViewModel, vm => vm.Name, view => view.GameName.Text) + .DisposeWith(disposables); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/GameSelectorItemViewModel.cs b/Wabbajack.App/Controls/GameSelectorItemViewModel.cs index c738f98d..1d640c32 100644 --- a/Wabbajack.App/Controls/GameSelectorItemViewModel.cs +++ b/Wabbajack.App/Controls/GameSelectorItemViewModel.cs @@ -3,21 +3,18 @@ using ReactiveUI.Fody.Helpers; using Wabbajack.App.ViewModels; using Wabbajack.DTOs; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public class GameSelectorItemViewModel : ViewModelBase, IActivatableViewModel { - public class GameSelectorItemViewModel : ViewModelBase, IActivatableViewModel + public GameSelectorItemViewModel(Game game) { - [Reactive] - public Game Game { get; set; } - - [Reactive] - public string Name { get; set; } - - public GameSelectorItemViewModel(Game game) - { - Activator = new ViewModelActivator(); - Game = game; - Name = game.MetaData().HumanFriendlyGameName; - } + Activator = new ViewModelActivator(); + Game = game; + Name = game.MetaData().HumanFriendlyGameName; } + + [Reactive] public Game Game { get; set; } + + [Reactive] public string Name { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/InstalledListView.axaml b/Wabbajack.App/Controls/InstalledListView.axaml index 14e90f4f..fd19d10f 100644 --- a/Wabbajack.App/Controls/InstalledListView.axaml +++ b/Wabbajack.App/Controls/InstalledListView.axaml @@ -6,10 +6,10 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.InstalledListView"> - - + + \ No newline at end of file diff --git a/Wabbajack.App/Controls/InstalledListView.axaml.cs b/Wabbajack.App/Controls/InstalledListView.axaml.cs index 778f44da..25e86a64 100644 --- a/Wabbajack.App/Controls/InstalledListView.axaml.cs +++ b/Wabbajack.App/Controls/InstalledListView.axaml.cs @@ -1,7 +1,6 @@ using Avalonia.Controls.Mixins; using Avalonia.ReactiveUI; using ReactiveUI; -using Wabbajack.App.Utilities; namespace Wabbajack.App.Controls; @@ -15,14 +14,13 @@ public partial class InstalledListView : ReactiveUserControl vm.Name, view => view.Title.Text) .DisposeWith(disposables); - + this.OneWayBind(ViewModel, vm => vm.InstallPath, view => view.Title.Text, p => p.ToString()) .DisposeWith(disposables); - + this.BindCommand(ViewModel, vm => vm.Play, view => view.PlayButton) .DisposeWith(disposables); }); } - } \ No newline at end of file diff --git a/Wabbajack.App/Controls/InstalledListViewModel.cs b/Wabbajack.App/Controls/InstalledListViewModel.cs index 1b3b43c3..b82667f6 100644 --- a/Wabbajack.App/Controls/InstalledListViewModel.cs +++ b/Wabbajack.App/Controls/InstalledListViewModel.cs @@ -1,6 +1,5 @@ using System.Reactive; using ReactiveUI; -using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; using Wabbajack.App.Screens; using Wabbajack.App.ViewModels; @@ -12,10 +11,6 @@ namespace Wabbajack.App.Controls; public class InstalledListViewModel : ViewModelBase { private readonly InstallationConfigurationSetting _setting; - public AbsolutePath InstallPath => _setting.Install; - - public string Name => _setting.Metadata?.Title ?? ""; - public ReactiveCommand Play { get; } public InstalledListViewModel(InstallationConfigurationSetting setting) { @@ -28,5 +23,9 @@ public class InstalledListViewModel : ViewModelBase MessageBus.Instance.Send(new NavigateTo(typeof(LauncherViewModel))); }); } - + + public AbsolutePath InstallPath => _setting.Install; + + public string Name => _setting.Metadata?.Title ?? ""; + public ReactiveCommand Play { get; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/LargeIconButton.axaml b/Wabbajack.App/Controls/LargeIconButton.axaml index 4b79bd70..77d038c9 100644 --- a/Wabbajack.App/Controls/LargeIconButton.axaml +++ b/Wabbajack.App/Controls/LargeIconButton.axaml @@ -6,10 +6,10 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.LargeIconButton"> - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/LargeIconButton.axaml.cs b/Wabbajack.App/Controls/LargeIconButton.axaml.cs index 96ba2078..f38856fd 100644 --- a/Wabbajack.App/Controls/LargeIconButton.axaml.cs +++ b/Wabbajack.App/Controls/LargeIconButton.axaml.cs @@ -2,52 +2,46 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Material.Icons; -using Material.Icons.Avalonia; using ReactiveUI; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public partial class LargeIconButton : UserControl, IActivatableView { - public partial class LargeIconButton : UserControl, IActivatableView + public static readonly StyledProperty TextProperty = + AvaloniaProperty.Register(nameof(Text)); + + + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(IconProperty)); + + public LargeIconButton() { - public static readonly StyledProperty TextProperty = - AvaloniaProperty.Register(nameof(Text)); - - public string Text + InitializeComponent(); + this.WhenActivated(dispose => { - get => GetValue(TextProperty); - set => SetValue(TextProperty, value); - } + this.WhenAnyValue(x => x.Icon) + .Where(x => x != default) + .BindTo(IconControl, x => x.Kind) + .DisposeWith(dispose); - - public static readonly StyledProperty IconProperty = - AvaloniaProperty.Register(nameof(IconProperty)); + this.WhenAnyValue(x => x.Text) + .Where(x => x != default) + .BindTo(TextBlock, x => x.Text) + .DisposeWith(dispose); + }); + } - public MaterialIconKind Icon - { - get => GetValue(IconProperty); - set => SetValue(IconProperty, value); - } + public string Text + { + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); + } - public LargeIconButton() - { - InitializeComponent(); - this.WhenActivated(dispose => - { - this.WhenAnyValue(x => x.Icon) - .Where(x => x != default) - .BindTo(IconControl, x => x.Kind) - .DisposeWith(dispose); - - this.WhenAnyValue(x => x.Text) - .Where(x => x != default) - .BindTo(TextBlock, x => x.Text) - .DisposeWith(dispose); - }); - - - } - + public MaterialIconKind Icon + { + get => GetValue(IconProperty); + set => SetValue(IconProperty, value); } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/LogView.axaml b/Wabbajack.App/Controls/LogView.axaml index 5be40935..bb33f98d 100644 --- a/Wabbajack.App/Controls/LogView.axaml +++ b/Wabbajack.App/Controls/LogView.axaml @@ -13,22 +13,22 @@ - + - + diff --git a/Wabbajack.App/Controls/LogView.axaml.cs b/Wabbajack.App/Controls/LogView.axaml.cs index d0017291..33b5979e 100644 --- a/Wabbajack.App/Controls/LogView.axaml.cs +++ b/Wabbajack.App/Controls/LogView.axaml.cs @@ -3,7 +3,6 @@ using Avalonia.Controls.Mixins; using Avalonia.ReactiveUI; using Microsoft.Extensions.DependencyInjection; using ReactiveUI; -using Wabbajack.App.Utilities; namespace Wabbajack.App.Controls; diff --git a/Wabbajack.App/Controls/LogViewItem.axaml b/Wabbajack.App/Controls/LogViewItem.axaml index 95c6d825..8ba4a998 100644 --- a/Wabbajack.App/Controls/LogViewItem.axaml +++ b/Wabbajack.App/Controls/LogViewItem.axaml @@ -4,5 +4,5 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.LogViewItem"> - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/LogViewItem.axaml.cs b/Wabbajack.App/Controls/LogViewItem.axaml.cs index 70162cdb..5ac43e55 100644 --- a/Wabbajack.App/Controls/LogViewItem.axaml.cs +++ b/Wabbajack.App/Controls/LogViewItem.axaml.cs @@ -16,5 +16,4 @@ public partial class LogViewItem : ReactiveUserControl Messages => _provider.MessageLog; - - [Reactive] - public ReactiveCommand CopyLogFile { get; set; } public LogViewModel(LoggerProvider provider) { Activator = new ViewModelActivator(); _provider = provider; - + CopyLogFile = ReactiveCommand.Create(() => { var obj = new DataObject(); @@ -35,4 +26,8 @@ public class LogViewModel : ViewModelBase, IActivatableViewModel Application.Current.Clipboard.SetDataObjectAsync(obj); }); } + + public ReadOnlyObservableCollection Messages => _provider.MessageLog; + + [Reactive] public ReactiveCommand CopyLogFile { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/RemovableListItem.axaml b/Wabbajack.App/Controls/RemovableListItem.axaml index 0b575085..4848a0da 100644 --- a/Wabbajack.App/Controls/RemovableListItem.axaml +++ b/Wabbajack.App/Controls/RemovableListItem.axaml @@ -7,8 +7,8 @@ x:Class="Wabbajack.App.Controls.RemovableListItem"> - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/RemovableListItem.axaml.cs b/Wabbajack.App/Controls/RemovableListItem.axaml.cs index b05a688d..b91392f9 100644 --- a/Wabbajack.App/Controls/RemovableListItem.axaml.cs +++ b/Wabbajack.App/Controls/RemovableListItem.axaml.cs @@ -1,7 +1,6 @@ using Avalonia.Controls.Mixins; using Avalonia.ReactiveUI; using ReactiveUI; -using Wabbajack.App.ViewModels; namespace Wabbajack.App.Controls; @@ -17,8 +16,6 @@ public partial class RemovableListItem : ReactiveUserControl vm.DeleteCommand, view => view.DeleteButton) .DisposeWith(disposables); - }); } - } \ No newline at end of file diff --git a/Wabbajack.App/Controls/RemovableListItemViewModel.cs b/Wabbajack.App/Controls/RemovableListItemViewModel.cs index 7aca36b2..0318aa24 100644 --- a/Wabbajack.App/Controls/RemovableListItemViewModel.cs +++ b/Wabbajack.App/Controls/RemovableListItemViewModel.cs @@ -1,4 +1,3 @@ -using System; using System.Reactive; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -8,15 +7,12 @@ namespace Wabbajack.App.Controls; public class RemovableItemViewModel : ViewModelBase { - [Reactive] - public string Text { get; set; } - - [Reactive] - public ReactiveCommand DeleteCommand { get; set; } - public RemovableItemViewModel() { Activator = new ViewModelActivator(); - } + + [Reactive] public string Text { get; set; } + + [Reactive] public ReactiveCommand DeleteCommand { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/ResourceView.axaml b/Wabbajack.App/Controls/ResourceView.axaml index 7a3826a4..3097a736 100644 --- a/Wabbajack.App/Controls/ResourceView.axaml +++ b/Wabbajack.App/Controls/ResourceView.axaml @@ -5,12 +5,12 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.ResourceView"> - + - + - + - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/ResourceView.axaml.cs b/Wabbajack.App/Controls/ResourceView.axaml.cs index e5cd57ae..bcf518de 100644 --- a/Wabbajack.App/Controls/ResourceView.axaml.cs +++ b/Wabbajack.App/Controls/ResourceView.axaml.cs @@ -1,5 +1,3 @@ - - using System.Reactive.Disposables; using Avalonia.ReactiveUI; using FluentFTP.Helpers; @@ -16,17 +14,15 @@ public partial class ResourceView : ReactiveUserControl, IAct { this.OneWayBind(ViewModel, vm => vm.Name, view => view.ResourceName.Text) .DisposeWith(disposables); - + this.Bind(ViewModel, vm => vm.MaxTasks, view => view.MaxTasks.Text) .DisposeWith(disposables); this.Bind(ViewModel, vm => vm.MaxThroughput, view => view.MaxThroughput.Text) .DisposeWith(disposables); - + this.OneWayBind(ViewModel, vm => vm.CurrentThroughput, view => view.CurrentThrougput.Text, val => val.FileSizeToString()) .DisposeWith(disposables); - }); } - } \ No newline at end of file diff --git a/Wabbajack.App/Controls/ResourceViewModel.cs b/Wabbajack.App/Controls/ResourceViewModel.cs index f46999f6..d3b51c1d 100644 --- a/Wabbajack.App/Controls/ResourceViewModel.cs +++ b/Wabbajack.App/Controls/ResourceViewModel.cs @@ -1,7 +1,8 @@ using System; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Timers;using ReactiveUI; +using System.Timers; +using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.ViewModels; using Wabbajack.RateLimiter; @@ -12,18 +13,6 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab { private readonly IResource _resource; private readonly Timer _timer; - - [Reactive] - public int MaxTasks { get; set; } - - [Reactive] - public long MaxThroughput { get; set; } - - [Reactive] - public long CurrentThroughput { get; set; } - - [Reactive] - public string Name { get; set; } public ResourceViewModel(IResource resource) { @@ -32,12 +21,12 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab _timer = new Timer(1.0); Name = resource.Name; - + this.WhenActivated(disposables => { - _timer.Elapsed += TimerElapsed; + _timer.Elapsed += TimerElapsed; _timer.Start(); - + Disposable.Create(() => { _timer.Stop(); @@ -46,31 +35,32 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab this.WhenAnyValue(vm => vm.MaxThroughput) .Skip(1) - .Subscribe(v => - { - _resource.MaxThroughput = MaxThroughput; - }).DisposeWith(disposables); - + .Subscribe(v => { _resource.MaxThroughput = MaxThroughput; }).DisposeWith(disposables); + this.WhenAnyValue(vm => vm.MaxTasks) .Skip(1) - .Subscribe(v => - { - _resource.MaxTasks = MaxTasks; - }).DisposeWith(disposables); - + .Subscribe(v => { _resource.MaxTasks = MaxTasks; }).DisposeWith(disposables); }); } + [Reactive] public int MaxTasks { get; set; } + + [Reactive] public long MaxThroughput { get; set; } + + [Reactive] public long CurrentThroughput { get; set; } + + [Reactive] public string Name { get; set; } + + + public void Dispose() + { + _timer.Dispose(); + } + private void TimerElapsed(object? sender, ElapsedEventArgs e) { MaxTasks = _resource.MaxTasks; MaxThroughput = _resource.MaxThroughput; CurrentThroughput = _resource.StatusReport.Transferred; } - - - public void Dispose() - { - _timer.Dispose(); - } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/TagView.axaml b/Wabbajack.App/Controls/TagView.axaml index 22a70795..2faa38cc 100644 --- a/Wabbajack.App/Controls/TagView.axaml +++ b/Wabbajack.App/Controls/TagView.axaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.TagView"> - + FontSize="10" /> - + \ No newline at end of file diff --git a/Wabbajack.App/Controls/TagView.axaml.cs b/Wabbajack.App/Controls/TagView.axaml.cs index 8320eb61..3bb677e8 100644 --- a/Wabbajack.App/Controls/TagView.axaml.cs +++ b/Wabbajack.App/Controls/TagView.axaml.cs @@ -1,26 +1,22 @@ using System.Reactive.Disposables; -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -using DynamicData; using ReactiveUI; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public partial class TagView : ReactiveUserControl { - public partial class TagView : ReactiveUserControl + public TagView() { - public TagView() + InitializeComponent(); + this.WhenActivated(disposables => { - InitializeComponent(); - this.WhenActivated(disposables => - { - this.OneWayBind(ViewModel, vm => vm.Name, view => view.Text.Text) - .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Tag, view => view.Classes, + this.OneWayBind(ViewModel, vm => vm.Name, view => view.Text.Text) + .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Tag, view => view.Classes, c => c == null ? new Classes() : new Classes(c)) - .DisposeWith(disposables); - }); - } + .DisposeWith(disposables); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/TagViewModel.cs b/Wabbajack.App/Controls/TagViewModel.cs index 6fd8c5d4..166692b9 100644 --- a/Wabbajack.App/Controls/TagViewModel.cs +++ b/Wabbajack.App/Controls/TagViewModel.cs @@ -2,22 +2,18 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.ViewModels; -namespace Wabbajack.App.Controls +namespace Wabbajack.App.Controls; + +public class TagViewModel : ViewModelBase, IActivatableViewModel { - public class TagViewModel : ViewModelBase, IActivatableViewModel + public TagViewModel(string name, string tag) { - [Reactive] - public string Name { get; set; } - - [Reactive] - public string Tag { get; set; } - - public TagViewModel(string name, string tag) - { - Activator = new ViewModelActivator(); - Name = name; - Tag = tag; - } - + Activator = new ViewModelActivator(); + Name = name; + Tag = tag; } + + [Reactive] public string Name { get; set; } + + [Reactive] public string Tag { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Converters/AbsoultePathBindingConverter.cs b/Wabbajack.App/Converters/AbsoultePathBindingConverter.cs index 16fac2c7..b26efc00 100644 --- a/Wabbajack.App/Converters/AbsoultePathBindingConverter.cs +++ b/Wabbajack.App/Converters/AbsoultePathBindingConverter.cs @@ -2,32 +2,31 @@ using System; using ReactiveUI; using Wabbajack.Paths; -namespace Wabbajack.App.Converters -{ - public class AbsoultePathBindingConverter : IBindingTypeConverter - { - public int GetAffinityForObjects(Type fromType, Type toType) - { - if (fromType == typeof(string) && toType == typeof(AbsolutePath) || - fromType == typeof(AbsolutePath) && toType == typeof(string)) - return 100; - return 0; - } +namespace Wabbajack.App.Converters; - public bool TryConvert(object? @from, Type toType, object? conversionHint, out object? result) +public class AbsoultePathBindingConverter : IBindingTypeConverter +{ + public int GetAffinityForObjects(Type fromType, Type toType) + { + if (fromType == typeof(string) && toType == typeof(AbsolutePath) || + fromType == typeof(AbsolutePath) && toType == typeof(string)) + return 100; + return 0; + } + + public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result) + { + switch (from) { - switch (@from) - { - case string s: - result = (AbsolutePath)s; - return true; - case AbsolutePath ap: - result = ap.ToString(); - return true; - default: - result = null; - return false; - } + case string s: + result = (AbsolutePath) s; + return true; + case AbsolutePath ap: + result = ap.ToString(); + return true; + default: + result = null; + return false; } } } \ No newline at end of file diff --git a/Wabbajack.App/Extensions/IObservableExtensions.cs b/Wabbajack.App/Extensions/IObservableExtensions.cs index 4fe687eb..210a9d9d 100644 --- a/Wabbajack.App/Extensions/IObservableExtensions.cs +++ b/Wabbajack.App/Extensions/IObservableExtensions.cs @@ -3,24 +3,22 @@ using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Threading.Tasks; -namespace Wabbajack.App.Extensions +namespace Wabbajack.App.Extensions; + +public static class IObservableExtensions { - public static class IObservableExtensions + public static IObservable SelectAsync(this IObservable input, + CompositeDisposable disposable, + Func> func) { - public static IObservable SelectAsync(this IObservable input, - CompositeDisposable disposable, - Func> func) + Subject returnObs = new(); + + input.Subscribe(x => Task.Run(async () => { - Subject returnObs = new(); - - input.Subscribe(x => Task.Run(async () => - { - var result = await func(x); - returnObs.OnNext(result); - })).DisposeWith(disposable); - - return returnObs; - } + var result = await func(x); + returnObs.OnNext(result); + })).DisposeWith(disposable); + return returnObs; } } \ No newline at end of file diff --git a/Wabbajack.App/Extensions/ReactiveUIExtensions.cs b/Wabbajack.App/Extensions/ReactiveUIExtensions.cs index 60b0d626..50f47067 100644 --- a/Wabbajack.App/Extensions/ReactiveUIExtensions.cs +++ b/Wabbajack.App/Extensions/ReactiveUIExtensions.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq.Expressions; -using System.Reactive.Linq; -using ReactiveUI; +namespace Wabbajack.App.Extensions; -namespace Wabbajack.App.Extensions +public static class ReactiveUIExtensions { - public static class ReactiveUIExtensions - { - - } } \ No newline at end of file diff --git a/Wabbajack.App/Extensions/WebViewExtensions.cs b/Wabbajack.App/Extensions/WebViewExtensions.cs index a8b9212e..643b00af 100644 --- a/Wabbajack.App/Extensions/WebViewExtensions.cs +++ b/Wabbajack.App/Extensions/WebViewExtensions.cs @@ -7,57 +7,49 @@ using CefNet.Avalonia; using HtmlAgilityPack; using Wabbajack.DTOs.Logins; -namespace Wabbajack.App.Extensions +namespace Wabbajack.App.Extensions; + +public static class WebViewExtensions { - public static class WebViewExtensions + public static async Task WaitForReady(this WebView view) { - public static async Task WaitForReady(this WebView view) - { - while (view.BrowserObject == null) - { - await Task.Delay(200); - } - } - - /// - /// Navigates to the URL and waits until the page is finished loading - /// - /// - /// - public static async Task NavigateTo(this WebView view, Uri uri) - { - view.Navigate(uri.ToString()); - while (view.IsBusy) - { - await Task.Delay(200); - } - } + while (view.BrowserObject == null) await Task.Delay(200); + } - public static async Task Cookies(this WebView view, string domainEnding, CancellationToken token) - { - var results = CefCookieManager.GetGlobalManager(null)!; - var cookies = await results.GetCookiesAsync(c => c.Domain.EndsWith(domainEnding), token)!; - return cookies.Select(c => new Cookie - { - Domain = c.Domain, - Name = c.Name, - Path = c.Path, - Value = c.Value, - }).ToArray(); - } + /// + /// Navigates to the URL and waits until the page is finished loading + /// + /// + /// + public static async Task NavigateTo(this WebView view, Uri uri) + { + view.Navigate(uri.ToString()); + while (view.IsBusy) await Task.Delay(200); + } - public static async Task EvaluateJavaScript(this WebView view, string js) + public static async Task Cookies(this WebView view, string domainEnding, CancellationToken token) + { + var results = CefCookieManager.GetGlobalManager(null)!; + var cookies = await results.GetCookiesAsync(c => c.Domain.EndsWith(domainEnding), token)!; + return cookies.Select(c => new Cookie { - view.GetMainFrame().ExecuteJavaScript(js, "", 0); - } + Domain = c.Domain, + Name = c.Name, + Path = c.Path, + Value = c.Value + }).ToArray(); + } - public static async Task GetDom(this WebView view, CancellationToken token) - { - var source = await view.GetMainFrame().GetSourceAsync(token); - var doc = new HtmlDocument(); - doc.LoadHtml(source); - return doc; - } - + public static async Task EvaluateJavaScript(this WebView view, string js) + { + view.GetMainFrame().ExecuteJavaScript(js, "", 0); + } + + public static async Task GetDom(this WebView view, CancellationToken token) + { + var source = await view.GetMainFrame().GetSourceAsync(token); + var doc = new HtmlDocument(); + doc.LoadHtml(source); + return doc; } } \ No newline at end of file diff --git a/Wabbajack.App/FluentWindow.cs b/Wabbajack.App/FluentWindow.cs index 741e04a1..8834fbb6 100644 --- a/Wabbajack.App/FluentWindow.cs +++ b/Wabbajack.App/FluentWindow.cs @@ -5,43 +5,42 @@ using Avalonia.Controls.Primitives; using Avalonia.Platform; using Avalonia.Styling; -namespace Wabbajack.App +namespace Wabbajack.App; + +public class FluentWindow : Window, IStyleable { - public class FluentWindow : Window, IStyleable + public FluentWindow() { - Type IStyleable.StyleKey => typeof(Window); + ExtendClientAreaToDecorationsHint = true; + ExtendClientAreaTitleBarHeightHint = -1; - public FluentWindow() - { - ExtendClientAreaToDecorationsHint = true; - ExtendClientAreaTitleBarHeightHint = -1; + TransparencyLevelHint = WindowTransparencyLevel.AcrylicBlur; - TransparencyLevelHint = WindowTransparencyLevel.AcrylicBlur; + this.GetObservable(WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }); - this.GetObservable(WindowStateProperty) - .Subscribe(x => + this.GetObservable(IsExtendedIntoWindowDecorationsProperty) + .Subscribe(x => + { + if (!x) { - PseudoClasses.Set(":maximized", x == WindowState.Maximized); - PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }); + SystemDecorations = SystemDecorations.Full; + TransparencyLevelHint = WindowTransparencyLevel.Blur; + } + }); + } - this.GetObservable(IsExtendedIntoWindowDecorationsProperty) - .Subscribe(x => - { - if (!x) - { - SystemDecorations = SystemDecorations.Full; - TransparencyLevelHint = WindowTransparencyLevel.Blur; - } - }); - } + Type IStyleable.StyleKey => typeof(Window); - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - ExtendClientAreaChromeHints = - ExtendClientAreaChromeHints.PreferSystemChrome | - ExtendClientAreaChromeHints.OSXThickTitleBar; - } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + ExtendClientAreaChromeHints = + ExtendClientAreaChromeHints.PreferSystemChrome | + ExtendClientAreaChromeHints.OSXThickTitleBar; } } \ No newline at end of file diff --git a/Wabbajack.App/FodyWeavers.xml b/Wabbajack.App/FodyWeavers.xml index 63fc1484..a42ba18c 100644 --- a/Wabbajack.App/FodyWeavers.xml +++ b/Wabbajack.App/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/Wabbajack.App/Interfaces/INavigationParameter.cs b/Wabbajack.App/Interfaces/INavigationParameter.cs index 5be380c3..ab9e6c7b 100644 --- a/Wabbajack.App/Interfaces/INavigationParameter.cs +++ b/Wabbajack.App/Interfaces/INavigationParameter.cs @@ -1,9 +1,8 @@ using System.Threading.Tasks; -namespace Wabbajack.App.Interfaces +namespace Wabbajack.App.Interfaces; + +public interface INavigationParameter { - public interface INavigationParameter - { - public Task NavigatedTo(T param); - } + public Task NavigatedTo(T param); } \ No newline at end of file diff --git a/Wabbajack.App/Interfaces/IScreenView.cs b/Wabbajack.App/Interfaces/IScreenView.cs index f968654c..5ee01c93 100644 --- a/Wabbajack.App/Interfaces/IScreenView.cs +++ b/Wabbajack.App/Interfaces/IScreenView.cs @@ -1,10 +1,8 @@ using System; -namespace Wabbajack.App.Interfaces +namespace Wabbajack.App.Interfaces; + +public interface IScreenView { - public interface IScreenView - { - public Type ViewModelType { get; } - - } + public Type ViewModelType { get; } } \ No newline at end of file diff --git a/Wabbajack.App/MessageBus.cs b/Wabbajack.App/MessageBus.cs index 3a66db03..0a6a63e0 100644 --- a/Wabbajack.App/MessageBus.cs +++ b/Wabbajack.App/MessageBus.cs @@ -2,50 +2,49 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; -using System.Threading.Tasks; using Avalonia.Threading; using Microsoft.Extensions.Logging; using Wabbajack.App.Messages; -namespace Wabbajack.App +namespace Wabbajack.App; + +public interface IMessageBus { - public interface IMessageBus + public void Send(T message); +} + +public class MessageBus : IMessageBus +{ + private readonly ILogger _logger; + private readonly IReceiverMarker[] _receivers; + + public MessageBus(ILogger logger, IEnumerable receivers) { - public void Send(T message); + Instance = this; + _receivers = receivers.ToArray(); + _logger = logger; } - - public class MessageBus : IMessageBus + + public static IMessageBus Instance { get; set; } + + public void Send(T message) { - public static IMessageBus Instance { get; set; } - private readonly IReceiverMarker[] _receivers; - private readonly ILogger _logger; - - public MessageBus(ILogger logger, IEnumerable receivers) + AvaloniaScheduler.Instance.Schedule(message, TimeSpan.FromMilliseconds(200), (_, msg) => { - Instance = this; - _receivers = receivers.ToArray(); - _logger = logger; - } - - public void Send(T message) - { - AvaloniaScheduler.Instance.Schedule(message, TimeSpan.FromMilliseconds(200), (_, msg) => + foreach (var receiver in _receivers.OfType>()) { - foreach (var receiver in _receivers.OfType>()) + _logger.LogInformation("Sending {msg} to {receiver}", msg, receiver); + try { - _logger.LogInformation("Sending {msg} to {receiver}", msg, receiver); - try - { - receiver.Receive(msg); - } - catch (Exception ex) - { - _logger.LogCritical(ex, "Failed sending {msg} to {receiver}", msg, receiver); - } + receiver.Receive(msg); } + catch (Exception ex) + { + _logger.LogCritical(ex, "Failed sending {msg} to {receiver}", msg, receiver); + } + } - return Disposable.Empty; - }); - } + return Disposable.Empty; + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Messages/ConfigureLauncher.cs b/Wabbajack.App/Messages/ConfigureLauncher.cs index 1cbc0278..9ca6780d 100644 --- a/Wabbajack.App/Messages/ConfigureLauncher.cs +++ b/Wabbajack.App/Messages/ConfigureLauncher.cs @@ -1,9 +1,7 @@ using Wabbajack.Paths; -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public record ConfigureLauncher(AbsolutePath InstallFolder) { - public record ConfigureLauncher(AbsolutePath InstallFolder) - { - - } } \ No newline at end of file diff --git a/Wabbajack.App/Messages/Error.cs b/Wabbajack.App/Messages/Error.cs index c2008583..5fb45527 100644 --- a/Wabbajack.App/Messages/Error.cs +++ b/Wabbajack.App/Messages/Error.cs @@ -4,5 +4,4 @@ namespace Wabbajack.App.Messages; public record Error(string Prefix, Exception Exception) { - } \ No newline at end of file diff --git a/Wabbajack.App/Messages/IReceiver.cs b/Wabbajack.App/Messages/IReceiver.cs index b3c48291..3fc879a7 100644 --- a/Wabbajack.App/Messages/IReceiver.cs +++ b/Wabbajack.App/Messages/IReceiver.cs @@ -1,10 +1,10 @@ -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public interface IReceiverMarker { - public interface IReceiverMarker - { - } - public interface IReceiver : IReceiverMarker - { - public void Receive(T val); - } +} + +public interface IReceiver : IReceiverMarker +{ + public void Receive(T val); } \ No newline at end of file diff --git a/Wabbajack.App/Messages/NavigateBack.cs b/Wabbajack.App/Messages/NavigateBack.cs index fbe075a5..c5e5cde5 100644 --- a/Wabbajack.App/Messages/NavigateBack.cs +++ b/Wabbajack.App/Messages/NavigateBack.cs @@ -1,10 +1,5 @@ -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public class NavigateBack { - public class NavigateBack - { - public NavigateBack() - { - - } - } } \ No newline at end of file diff --git a/Wabbajack.App/Messages/NavigateTo.cs b/Wabbajack.App/Messages/NavigateTo.cs index 0e491c71..251859ac 100644 --- a/Wabbajack.App/Messages/NavigateTo.cs +++ b/Wabbajack.App/Messages/NavigateTo.cs @@ -1,10 +1,7 @@ using System; -using Wabbajack.App.ViewModels; -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public record NavigateTo(Type ViewModel) { - public record NavigateTo(Type ViewModel) - { - - } } \ No newline at end of file diff --git a/Wabbajack.App/Messages/StartCompilation.cs b/Wabbajack.App/Messages/StartCompilation.cs index a80186ad..fe88ff14 100644 --- a/Wabbajack.App/Messages/StartCompilation.cs +++ b/Wabbajack.App/Messages/StartCompilation.cs @@ -4,5 +4,4 @@ namespace Wabbajack.App.Messages; public record StartCompilation(CompilerSettings Settings) { - } \ No newline at end of file diff --git a/Wabbajack.App/Messages/StartInstallConfiguration.cs b/Wabbajack.App/Messages/StartInstallConfiguration.cs index e7805236..df55988d 100644 --- a/Wabbajack.App/Messages/StartInstallConfiguration.cs +++ b/Wabbajack.App/Messages/StartInstallConfiguration.cs @@ -1,8 +1,7 @@ using Wabbajack.Paths; -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public record StartInstallConfiguration(AbsolutePath ModList) { - public record StartInstallConfiguration(AbsolutePath ModList) - { - } } \ No newline at end of file diff --git a/Wabbajack.App/Messages/StartInstallation.cs b/Wabbajack.App/Messages/StartInstallation.cs index 15b5f3ef..9e138c76 100644 --- a/Wabbajack.App/Messages/StartInstallation.cs +++ b/Wabbajack.App/Messages/StartInstallation.cs @@ -1,9 +1,9 @@ using Wabbajack.DTOs; using Wabbajack.Paths; -namespace Wabbajack.App.Messages +namespace Wabbajack.App.Messages; + +public record StartInstallation(AbsolutePath ModListPath, AbsolutePath Install, AbsolutePath Download, + ModlistMetadata? Metadata) { - public record StartInstallation(AbsolutePath ModListPath, AbsolutePath Install, AbsolutePath Download, ModlistMetadata? Metadata) - { - } } \ No newline at end of file diff --git a/Wabbajack.App/Models/InstallationStateManager.cs b/Wabbajack.App/Models/InstallationStateManager.cs index 414a149e..a0c7e0d4 100644 --- a/Wabbajack.App/Models/InstallationStateManager.cs +++ b/Wabbajack.App/Models/InstallationStateManager.cs @@ -8,75 +8,74 @@ using Wabbajack.DTOs.SavedSettings; using Wabbajack.Paths; using Wabbajack.Paths.IO; -namespace Wabbajack.App.Models +namespace Wabbajack.App.Models; + +public class InstallationStateManager { - public class InstallationStateManager + private readonly DTOSerializer _dtos; + private readonly ILogger _logger; + + public InstallationStateManager(ILogger logger, DTOSerializer dtos) { - private static AbsolutePath Path => KnownFolders.WabbajackAppLocal.Combine("install-configuration-state.json"); - private readonly DTOSerializer _dtos; - private readonly ILogger _logger; + _dtos = dtos; + _logger = logger; + } - public InstallationStateManager(ILogger logger, DTOSerializer dtos) + private static AbsolutePath Path => KnownFolders.WabbajackAppLocal.Combine("install-configuration-state.json"); + + public async Task GetLastState() + { + var state = await GetAll(); + var result = state.Settings.FirstOrDefault(s => s.ModList == state.LastModlist) ?? + new InstallationConfigurationSetting(); + + if (!result.ModList.FileExists()) + return new InstallationConfigurationSetting(); + return result; + } + + public async Task SetLastState(InstallationConfigurationSetting setting) + { + if (!setting.ModList.FileExists()) { - _dtos = dtos; - _logger = logger; + _logger.LogCritical("ModList path doesn't exist, not saving settings"); + return; } - public async Task GetLastState() - { - var state = await GetAll(); - var result = state.Settings.FirstOrDefault(s => s.ModList == state.LastModlist) ?? - new InstallationConfigurationSetting(); + var state = await GetAll(); + state.LastModlist = setting.ModList; + state.Settings = state.Settings + .Where(s => s.ModList != setting.ModList) + .Append(setting) + .ToArray(); - if (!result.ModList.FileExists()) - return new InstallationConfigurationSetting(); - return result; + await using var fs = Path.Open(FileMode.Create, FileAccess.Write, FileShare.None); + await _dtos.Serialize(state, fs, true); + } + + public async Task GetAll() + { + if (!Path.FileExists()) return new InstallConfigurationState(); + + try + { + await using var fs = Path.Open(FileMode.Open); + return (await _dtos.DeserializeAsync(fs))!; } - - public async Task SetLastState(InstallationConfigurationSetting setting) + catch (Exception ex) { - if (!setting.ModList.FileExists()) - { - _logger.LogCritical("ModList path doesn't exist, not saving settings"); - return; - } - - var state = await GetAll(); - state.LastModlist = setting.ModList; - state.Settings = state.Settings - .Where(s => s.ModList != setting.ModList) - .Append(setting) - .ToArray(); - - await using var fs = Path.Open(FileMode.Create, FileAccess.Write, FileShare.None); - await _dtos.Serialize(state, fs, true); - } - - public async Task GetAll() - { - if (!Path.FileExists()) return new InstallConfigurationState(); - - try - { - await using var fs = Path.Open(FileMode.Open); - return (await _dtos.DeserializeAsync(fs))!; - } - catch (Exception ex) - { - _logger.LogError(ex, "While loading json"); - return new InstallConfigurationState(); - } - - } - - public async Task Get(AbsolutePath modListPath) - { - return (await GetAll()).Settings.FirstOrDefault(f => f.ModList == modListPath); - } - - public async Task GetByInstallFolder(AbsolutePath folder) - { - return (await GetAll()).Settings.FirstOrDefault(f => f.Install == folder); + _logger.LogError(ex, "While loading json"); + return new InstallConfigurationState(); } } + + public async Task Get(AbsolutePath modListPath) + { + return (await GetAll()).Settings.FirstOrDefault(f => f.ModList == modListPath); + } + + public async Task GetByInstallFolder(AbsolutePath folder) + { + return (await GetAll()).Settings.FirstOrDefault(f => f.Install == folder); + } } \ No newline at end of file diff --git a/Wabbajack.App/Models/LoadingLock.cs b/Wabbajack.App/Models/LoadingLock.cs index 3e8bc529..e5c26f1e 100644 --- a/Wabbajack.App/Models/LoadingLock.cs +++ b/Wabbajack.App/Models/LoadingLock.cs @@ -1,10 +1,8 @@ using System; using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Threading; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using Wabbajack.App.ViewModels; namespace Wabbajack.App.Models; @@ -12,34 +10,31 @@ public class LoadingLock : ReactiveObject, IDisposable { private readonly CompositeDisposable _disposable; - [Reactive] - public int LoadLevel { get; private set; } - - [Reactive] - public bool IsLoading { get; private set; } - public LoadingLock() { _disposable = new CompositeDisposable(); this.WhenAnyValue(vm => vm.LoadLevel) - .Subscribe(v => IsLoading = v > 0) + .Subscribe(v => IsLoading = v > 0) .DisposeWith(_disposable); - } - public IDisposable WithLoading() - { - Dispatcher.UIThread.Post(() => { LoadLevel++;}, DispatcherPriority.Background); - return Disposable.Create(() => - { - Dispatcher.UIThread.Post(() => { LoadLevel--;}, DispatcherPriority.Background); - }); - } + [Reactive] public int LoadLevel { get; private set; } + + [Reactive] public bool IsLoading { get; private set; } public void Dispose() { GC.SuppressFinalize(this); _disposable.Dispose(); } + + public IDisposable WithLoading() + { + Dispatcher.UIThread.Post(() => { LoadLevel++; }, DispatcherPriority.Background); + return Disposable.Create(() => + { + Dispatcher.UIThread.Post(() => { LoadLevel--; }, DispatcherPriority.Background); + }); + } } \ No newline at end of file diff --git a/Wabbajack.App/Models/SettingsManager.cs b/Wabbajack.App/Models/SettingsManager.cs index 14050872..15335685 100644 --- a/Wabbajack.App/Models/SettingsManager.cs +++ b/Wabbajack.App/Models/SettingsManager.cs @@ -3,7 +3,6 @@ using System.IO; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Wabbajack.Common; using Wabbajack.DTOs.JsonConverters; @@ -26,7 +25,10 @@ public class SettingsManager _configuration.SavedSettingsLocation.CreateDirectory(); } - private AbsolutePath GetPath(string key) => _configuration.SavedSettingsLocation.Combine(key).WithExtension(Ext.Json); + private AbsolutePath GetPath(string key) + { + return _configuration.SavedSettingsLocation.Combine(key).WithExtension(Ext.Json); + } public async Task Save(string key, T value) { @@ -35,22 +37,21 @@ public class SettingsManager { await JsonSerializer.SerializeAsync(s, value, _dtos.Options); } + await tmp.MoveToAsync(GetPath(key), true, CancellationToken.None); } public async Task Load(string key) - where T : new() + where T : new() { var path = GetPath(key); try { if (path.FileExists()) - { await using (var s = path.Open(FileMode.Open)) { return (await JsonSerializer.DeserializeAsync(s, _dtos.Options))!; } - } } catch (Exception ex) { diff --git a/Wabbajack.App/Program.cs b/Wabbajack.App/Program.cs index 5e96aa7f..e95e6df5 100644 --- a/Wabbajack.App/Program.cs +++ b/Wabbajack.App/Program.cs @@ -3,28 +3,31 @@ using Avalonia; using Avalonia.ReactiveUI; using CefNet; -namespace Wabbajack.App +namespace Wabbajack.App; + +internal class Program { - - class Program + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) { - // Initialization code. Don't use any Avalonia, third-party APIs or any - // SynchronizationContext-reliant code before AppMain is called: things aren't initialized - // yet and stuff might break. - [STAThread] - public static void Main(string[] args) => BuildAvaloniaApp() + BuildAvaloniaApp() .StartWithCefNetApplicationLifetime(args); - - // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .AfterSetup(AfterSetupCallback) - .LogToTrace() - .UseReactiveUI(); - - private static void AfterSetupCallback(AppBuilder obj) - { - } } -} + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + { + return AppBuilder.Configure() + .UsePlatformDetect() + .AfterSetup(AfterSetupCallback) + .LogToTrace() + .UseReactiveUI(); + } + + private static void AfterSetupCallback(AppBuilder obj) + { + } +} \ No newline at end of file diff --git a/Wabbajack.App/Screens/BrowseView.axaml b/Wabbajack.App/Screens/BrowseView.axaml index 7011e2cf..870b0c92 100644 --- a/Wabbajack.App/Screens/BrowseView.axaml +++ b/Wabbajack.App/Screens/BrowseView.axaml @@ -54,7 +54,7 @@ x:Name="OnlyInstalledCheckbox" Margin="10,0,10,0" VerticalAlignment="Center" - Content="Only Installed"/> + Content="Only Installed" /> - + - + - + @@ -29,9 +29,8 @@ - - - + - + \ No newline at end of file diff --git a/Wabbajack.App/Screens/LauncherView.axaml.cs b/Wabbajack.App/Screens/LauncherView.axaml.cs index 96f4477c..bf0ac326 100644 --- a/Wabbajack.App/Screens/LauncherView.axaml.cs +++ b/Wabbajack.App/Screens/LauncherView.axaml.cs @@ -1,33 +1,28 @@ using System.Reactive.Disposables; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; using ReactiveUI; using Wabbajack.App.Views; -namespace Wabbajack.App.Screens +namespace Wabbajack.App.Screens; + +public partial class LauncherView : ScreenBase { - public partial class LauncherView : ScreenBase + public LauncherView() { - public LauncherView() + InitializeComponent(); + this.WhenActivated(disposables => { - InitializeComponent(); - this.WhenActivated(disposables => - { - this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Title, view => view.ModList.Text) - .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Title, view => view.ModList.Text) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.InstallFolder, view => view.InstallPath.Text, - v => v.ToString()) - .DisposeWith(disposables); - - this.BindCommand(ViewModel, vm => vm.PlayButton, view => view.PlayGame.Button) - .DisposeWith(disposables); - }); - } + this.OneWayBind(ViewModel, vm => vm.InstallFolder, view => view.InstallPath.Text, + v => v.ToString()) + .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PlayButton, view => view.PlayGame.Button) + .DisposeWith(disposables); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/LauncherViewModel.cs b/Wabbajack.App/Screens/LauncherViewModel.cs index cde96f2d..ccfc499d 100644 --- a/Wabbajack.App/Screens/LauncherViewModel.cs +++ b/Wabbajack.App/Screens/LauncherViewModel.cs @@ -5,8 +5,6 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Media.Imaging; -using GameFinder.StoreHandlers.Origin.DTO; -using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -20,83 +18,68 @@ using Wabbajack.DTOs.SavedSettings; using Wabbajack.Paths; using Wabbajack.Paths.IO; -namespace Wabbajack.App.Screens +namespace Wabbajack.App.Screens; + +public class LauncherViewModel : ViewModelBase, IActivatableViewModel, IReceiver { - public class LauncherViewModel : ViewModelBase, IActivatableViewModel, IReceiver + private readonly ILogger _logger; + + public ReactiveCommand PlayButton; + + public LauncherViewModel(ILogger logger, InstallationStateManager manager) { + Activator = new ViewModelActivator(); + PlayButton = ReactiveCommand.Create(() => { StartGame().FireAndForget(); }); + _logger = logger; - [Reactive] - public AbsolutePath InstallFolder { get; set; } - - [Reactive] - public IBitmap Image { get; set; } - - [Reactive] - public InstallationConfigurationSetting? Setting { get; set; } - - [Reactive] - public string Title { get; set; } - - public ReactiveCommand PlayButton; - private readonly ILogger _logger; - - public LauncherViewModel(ILogger logger, InstallationStateManager manager) + this.WhenActivated(disposables => { - Activator = new ViewModelActivator(); - PlayButton = ReactiveCommand.Create(() => - { - StartGame().FireAndForget(); - }); - _logger = logger; - - this.WhenActivated(disposables => - { - this.WhenAnyValue(v => v.InstallFolder) - .SelectAsync(disposables, async folder => await manager.GetByInstallFolder(folder)) - .ObserveOn(RxApp.MainThreadScheduler) - .Where(v => v != null) - .BindTo(this, vm => vm.Setting) - .DisposeWith(disposables); + this.WhenAnyValue(v => v.InstallFolder) + .SelectAsync(disposables, async folder => await manager.GetByInstallFolder(folder)) + .ObserveOn(RxApp.MainThreadScheduler) + .Where(v => v != null) + .BindTo(this, vm => vm.Setting) + .DisposeWith(disposables); - this.WhenAnyValue(v => v.Setting) - .Where(v => v != default) - .Select(v => new Bitmap((v!.Image).ToString())) - .BindTo(this, vm => vm.Image) - .DisposeWith(disposables); + this.WhenAnyValue(v => v.Setting) + .Where(v => v != default) + .Select(v => new Bitmap(v!.Image.ToString())) + .BindTo(this, vm => vm.Image) + .DisposeWith(disposables); - this.WhenAnyValue(v => v.Setting) - .Where(v => v is { Metadata: { } }) - .Select(v => $"{v!.Metadata!.Title} v{v!.Metadata.Version}") - .BindTo(this, vm => vm.Title) - .DisposeWith(disposables); + this.WhenAnyValue(v => v.Setting) + .Where(v => v is {Metadata: { }}) + .Select(v => $"{v!.Metadata!.Title} v{v!.Metadata.Version}") + .BindTo(this, vm => vm.Title) + .DisposeWith(disposables); + }); + } - }); - } + [Reactive] public AbsolutePath InstallFolder { get; set; } - private async Task StartGame() - { - var mo2Path = InstallFolder.Combine("ModOrganizer.exe"); - var gamePath = GameRegistry.Games.Values.Select(g => g.MainExecutable) - .Where(ge => ge != null) - .Select(ge => InstallFolder.Combine(ge!)) - .FirstOrDefault(ge => ge.FileExists()); - if (mo2Path.FileExists()) - { - Process.Start(mo2Path.ToString()); - } - else if (gamePath.FileExists()) - { - Process.Start(gamePath.ToString()); - } - else - { - _logger.LogError("No way to launch game, no acceptable executable found"); - } - } + [Reactive] public IBitmap Image { get; set; } - public void Receive(ConfigureLauncher val) - { - InstallFolder = val.InstallFolder; - } + [Reactive] public InstallationConfigurationSetting? Setting { get; set; } + + [Reactive] public string Title { get; set; } + + public void Receive(ConfigureLauncher val) + { + InstallFolder = val.InstallFolder; + } + + private async Task StartGame() + { + var mo2Path = InstallFolder.Combine("ModOrganizer.exe"); + var gamePath = GameRegistry.Games.Values.Select(g => g.MainExecutable) + .Where(ge => ge != null) + .Select(ge => InstallFolder.Combine(ge!)) + .FirstOrDefault(ge => ge.FileExists()); + if (mo2Path.FileExists()) + Process.Start(mo2Path.ToString()); + else if (gamePath.FileExists()) + Process.Start(gamePath.ToString()); + else + _logger.LogError("No way to launch game, no acceptable executable found"); } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/LogScreenView.axaml b/Wabbajack.App/Screens/LogScreenView.axaml index b3c5f05f..12e4692d 100644 --- a/Wabbajack.App/Screens/LogScreenView.axaml +++ b/Wabbajack.App/Screens/LogScreenView.axaml @@ -6,6 +6,6 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Screens.LogScreenView"> - + \ No newline at end of file diff --git a/Wabbajack.App/Screens/LogScreenView.axaml.cs b/Wabbajack.App/Screens/LogScreenView.axaml.cs index f9c27a9a..85a85735 100644 --- a/Wabbajack.App/Screens/LogScreenView.axaml.cs +++ b/Wabbajack.App/Screens/LogScreenView.axaml.cs @@ -1,9 +1,8 @@ -using ReactiveUI; using Wabbajack.App.Views; namespace Wabbajack.App.Screens; -public partial class LogScreenView : ScreenBase +public partial class LogScreenView : ScreenBase { public LogScreenView() { diff --git a/Wabbajack.App/Screens/LogScreenViewModel.cs b/Wabbajack.App/Screens/LogScreenViewModel.cs index 94667370..2ec5d59f 100644 --- a/Wabbajack.App/Screens/LogScreenViewModel.cs +++ b/Wabbajack.App/Screens/LogScreenViewModel.cs @@ -1,8 +1,4 @@ -using System.Reactive; -using Avalonia; -using Avalonia.Input; using ReactiveUI; -using ReactiveUI.Fody.Helpers; using Wabbajack.App.Utilities; using Wabbajack.App.ViewModels; @@ -11,11 +7,10 @@ namespace Wabbajack.App.Screens; public class LogScreenViewModel : ViewModelBase, IActivatableViewModel { private readonly LoggerProvider _provider; + public LogScreenViewModel(LoggerProvider provider) { _provider = provider; Activator = new ViewModelActivator(); - } - } \ No newline at end of file diff --git a/Wabbajack.App/Screens/PlaySelectView.axaml b/Wabbajack.App/Screens/PlaySelectView.axaml index 93881c42..7e3c21a0 100644 --- a/Wabbajack.App/Screens/PlaySelectView.axaml +++ b/Wabbajack.App/Screens/PlaySelectView.axaml @@ -9,12 +9,12 @@ - + - + diff --git a/Wabbajack.App/Screens/PlaySelectView.axaml.cs b/Wabbajack.App/Screens/PlaySelectView.axaml.cs index c35e220a..28aa6e7f 100644 --- a/Wabbajack.App/Screens/PlaySelectView.axaml.cs +++ b/Wabbajack.App/Screens/PlaySelectView.axaml.cs @@ -15,5 +15,4 @@ public partial class PlaySelectView : ScreenBase .DisposeWith(disposables); }); } - } \ No newline at end of file diff --git a/Wabbajack.App/Screens/PlaySelectViewModel.cs b/Wabbajack.App/Screens/PlaySelectViewModel.cs index 3a3e5d1b..66110b87 100644 --- a/Wabbajack.App/Screens/PlaySelectViewModel.cs +++ b/Wabbajack.App/Screens/PlaySelectViewModel.cs @@ -8,22 +8,18 @@ using Wabbajack.App.Controls; using Wabbajack.App.Models; using Wabbajack.App.ViewModels; using Wabbajack.Common; -using Wabbajack.DTOs.SavedSettings; namespace Wabbajack.App.Screens; public class PlaySelectViewModel : ViewModelBase, IActivatableViewModel { private readonly InstallationStateManager _manager; - - [Reactive] - public IEnumerable Items { get; set; } public PlaySelectViewModel(InstallationStateManager manager) { _manager = manager; Activator = new ViewModelActivator(); - + this.WhenActivated(disposables => { LoadAndSetItems().FireAndForget(); @@ -31,10 +27,11 @@ public class PlaySelectViewModel : ViewModelBase, IActivatableViewModel }); } + [Reactive] public IEnumerable Items { get; set; } + public async Task LoadAndSetItems() { var items = await _manager.GetAll(); Items = items.Settings.Select(a => new InstalledListViewModel(a)).ToArray(); } - } \ No newline at end of file diff --git a/Wabbajack.App/Screens/SettingsView.axaml b/Wabbajack.App/Screens/SettingsView.axaml index 8a3f0167..6d34aab9 100644 --- a/Wabbajack.App/Screens/SettingsView.axaml +++ b/Wabbajack.App/Screens/SettingsView.axaml @@ -9,43 +9,49 @@ Logins - - - + + + - - - + + + - - - + + + - + - + Resource Limits - + - + - + - + - + \ No newline at end of file diff --git a/Wabbajack.App/Screens/SettingsView.axaml.cs b/Wabbajack.App/Screens/SettingsView.axaml.cs index 66257c94..0d7f7c1f 100644 --- a/Wabbajack.App/Screens/SettingsView.axaml.cs +++ b/Wabbajack.App/Screens/SettingsView.axaml.cs @@ -1,26 +1,22 @@ using System.Reactive.Disposables; using ReactiveUI; -using Wabbajack.App.ViewModels; using Wabbajack.App.Views; -namespace Wabbajack.App.Screens +namespace Wabbajack.App.Screens; + +public partial class SettingsView : ScreenBase { - public partial class SettingsView : ScreenBase + public SettingsView() { - public SettingsView() + InitializeComponent(); + this.WhenActivated(disposables => { - InitializeComponent(); - this.WhenActivated(disposables => - { - this.BindCommand(ViewModel, vm => vm.NexusLogin, view => view.NexusLogIn) - .DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut) - .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourceList.Items) - .DisposeWith(disposables); - - }); - } - + this.BindCommand(ViewModel, vm => vm.NexusLogin, view => view.NexusLogIn) + .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut) + .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourceList.Items) + .DisposeWith(disposables); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/SettingsViewModel.cs b/Wabbajack.App/Screens/SettingsViewModel.cs index 7c4c8add..f1e0fe26 100644 --- a/Wabbajack.App/Screens/SettingsViewModel.cs +++ b/Wabbajack.App/Screens/SettingsViewModel.cs @@ -15,56 +15,51 @@ using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; using Wabbajack.Services.OSIntegrated.TokenProviders; -namespace Wabbajack.App.Screens +namespace Wabbajack.App.Screens; + +public class SettingsViewModel : ViewModelBase, IReceiverMarker { - public class SettingsViewModel : ViewModelBase, IReceiverMarker + private readonly Subject _fileSystemEvents = new(); + private readonly ILogger _logger; + public readonly IEnumerable Resources; + + public SettingsViewModel(ILogger logger, Configuration configuration, + NexusApiTokenProvider nexusProvider, IEnumerable resources) { - private readonly ILogger _logger; - - public ReactiveCommand NexusLogin { get; set; } - public ReactiveCommand NexusLogout { get; set; } - - public FileSystemWatcher Watcher { get; set; } + _logger = logger; + Resources = resources.Select(r => new ResourceViewModel(r)).ToArray(); + Activator = new ViewModelActivator(); - private readonly Subject _fileSystemEvents = new(); - public readonly IEnumerable Resources; - - public SettingsViewModel(ILogger logger, Configuration configuration, NexusApiTokenProvider nexusProvider, IEnumerable resources) + this.WhenActivated(disposables => { - _logger = logger; - Resources = resources.Select(r => new ResourceViewModel(r)).ToArray(); - Activator = new ViewModelActivator(); - - this.WhenActivated(disposables => - { - configuration.EncryptedDataLocation.CreateDirectory(); - Watcher = new FileSystemWatcher(configuration.EncryptedDataLocation.ToString()); - Watcher.DisposeWith(disposables); - Watcher.Created += Pulse; - Watcher.Deleted += Pulse; - Watcher.Renamed += Pulse; - Watcher.Changed += Pulse; - - Watcher.EnableRaisingEvents = true; + configuration.EncryptedDataLocation.CreateDirectory(); + Watcher = new FileSystemWatcher(configuration.EncryptedDataLocation.ToString()); + Watcher.DisposeWith(disposables); + Watcher.Created += Pulse; + Watcher.Deleted += Pulse; + Watcher.Renamed += Pulse; + Watcher.Changed += Pulse; - var haveNexusToken = this._fileSystemEvents - .StartWith(AbsolutePath.Empty) - .Select(_ => nexusProvider.HaveToken()); + Watcher.EnableRaisingEvents = true; - NexusLogin = ReactiveCommand.Create(() => - { - MessageBus.Instance.Send(new NavigateTo(typeof(NexusLoginViewModel))); - }, haveNexusToken.Select(x => !x)); - NexusLogout = ReactiveCommand.Create(nexusProvider.DeleteToken, haveNexusToken.Select(x => x)); - + var haveNexusToken = _fileSystemEvents + .StartWith(AbsolutePath.Empty) + .Select(_ => nexusProvider.HaveToken()); + NexusLogin = + ReactiveCommand.Create(() => { MessageBus.Instance.Send(new NavigateTo(typeof(NexusLoginViewModel))); }, + haveNexusToken.Select(x => !x)); + NexusLogout = ReactiveCommand.Create(nexusProvider.DeleteToken, haveNexusToken.Select(x => x)); + }); + } - }); - } + public ReactiveCommand NexusLogin { get; set; } + public ReactiveCommand NexusLogout { get; set; } - private void Pulse(object sender, FileSystemEventArgs e) - { - _fileSystemEvents.OnNext(e.FullPath?.ToAbsolutePath() ?? default); - } + public FileSystemWatcher Watcher { get; set; } + + private void Pulse(object sender, FileSystemEventArgs e) + { + _fileSystemEvents.OnNext(e.FullPath?.ToAbsolutePath() ?? default); } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/StandardInstallationView.axaml b/Wabbajack.App/Screens/StandardInstallationView.axaml index 3dcad5bc..d7e35f37 100644 --- a/Wabbajack.App/Screens/StandardInstallationView.axaml +++ b/Wabbajack.App/Screens/StandardInstallationView.axaml @@ -7,18 +7,26 @@ x:Class="Wabbajack.App.Views.StandardInstallationView"> [20/30] Installing Files - - + + - + - - - - + + + + - + \ No newline at end of file diff --git a/Wabbajack.App/Screens/StandardInstallationView.axaml.cs b/Wabbajack.App/Screens/StandardInstallationView.axaml.cs index a3c6e0c6..60b12837 100644 --- a/Wabbajack.App/Screens/StandardInstallationView.axaml.cs +++ b/Wabbajack.App/Screens/StandardInstallationView.axaml.cs @@ -2,42 +2,39 @@ using System.Reactive.Disposables; using ReactiveUI; using Wabbajack.App.ViewModels; -namespace Wabbajack.App.Views +namespace Wabbajack.App.Views; + +public partial class StandardInstallationView : ScreenBase { - public partial class StandardInstallationView : ScreenBase + public StandardInstallationView() { - public StandardInstallationView() + InitializeComponent(); + + this.WhenActivated(disposables => { - InitializeComponent(); + this.Bind(ViewModel, vm => vm.Slide.Image, view => view.SlideImage.Source) + .DisposeWith(disposables); - this.WhenActivated(disposables => - { - this.Bind(ViewModel, vm => vm.Slide.Image, view => view.SlideImage.Source) - .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.NextCommand, view => view.NextSlide) + .DisposeWith(disposables); - this.BindCommand(ViewModel, vm => vm.NextCommand, view => view.NextSlide) - .DisposeWith(disposables); - - this.BindCommand(ViewModel, vm => vm.PrevCommand, view => view.PrevSlide) - .DisposeWith(disposables); - - this.BindCommand(ViewModel, vm => vm.PauseCommand, view => view.PauseSlides) - .DisposeWith(disposables); - - this.BindCommand(ViewModel, vm => vm.PlayCommand, view => view.PlaySlides) - .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PrevCommand, view => view.PrevSlide) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.StatusText, view => view.StatusText.Text) - .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.PauseCommand, view => view.PauseSlides) + .DisposeWith(disposables); - this.OneWayBind(ViewModel, vm => vm.StepsProgress, view => view.StepsProgress.Value, p => p.Value * 1000) - .DisposeWith(disposables); - - this.OneWayBind(ViewModel, vm => vm.StepProgress, view => view.StepProgress.Value, p => p.Value * 10000) - .DisposeWith(disposables); - - }); - } + this.BindCommand(ViewModel, vm => vm.PlayCommand, view => view.PlaySlides) + .DisposeWith(disposables); + this.OneWayBind(ViewModel, vm => vm.StatusText, view => view.StatusText.Text) + .DisposeWith(disposables); + + this.OneWayBind(ViewModel, vm => vm.StepsProgress, view => view.StepsProgress.Value, p => p.Value * 1000) + .DisposeWith(disposables); + + this.OneWayBind(ViewModel, vm => vm.StepProgress, view => view.StepProgress.Value, p => p.Value * 10000) + .DisposeWith(disposables); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/StandardInstallationViewModel.cs b/Wabbajack.App/Screens/StandardInstallationViewModel.cs index 88952306..a8ea6675 100644 --- a/Wabbajack.App/Screens/StandardInstallationViewModel.cs +++ b/Wabbajack.App/Screens/StandardInstallationViewModel.cs @@ -8,7 +8,6 @@ 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; @@ -29,206 +28,193 @@ using Wabbajack.Installer; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class StandardInstallationViewModel : ViewModelBase, IReceiver { - public class StandardInstallationViewModel : ViewModelBase, IReceiver + private readonly DTOSerializer _dtos; + private readonly HttpClient _httpClient; + private readonly InstallationStateManager _installStateManager; + private readonly GameLocator _locator; + private readonly ILogger _logger; + private readonly IServiceProvider _provider; + private InstallerConfiguration _config; + private int _currentSlideIndex; + private StandardInstaller _installer; + private IServiceScope _scope; + private SlideViewModel[] _slides = Array.Empty(); + private Timer _slideTimer; + + public StandardInstallationViewModel(ILogger logger, IServiceProvider provider, + GameLocator locator, DTOSerializer dtos, + HttpClient httpClient, InstallationStateManager manager) { - 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; - private readonly InstallationStateManager _installStateManager; + _provider = provider; + _locator = locator; + _logger = logger; + _dtos = dtos; + _httpClient = httpClient; + _installStateManager = manager; + Activator = new ViewModelActivator(); - [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, InstallationStateManager manager) + this.WhenActivated(disposables => { - _provider = provider; - _locator = locator; - _logger = logger; - _dtos = dtos; - _httpClient = httpClient; - _installStateManager = manager; - Activator = new ViewModelActivator(); + _slideTimer = new Timer(_ => + { + if (IsPlaying) NextSlide(1); + }, null, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(5)); - this.WhenActivated(disposables => { - _slideTimer = new Timer(_ => - { - if (IsPlaying) NextSlide(1); - }, null, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(5)); - - _currentSlideIndex = 0; - _slideTimer.DisposeWith(disposables); + _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); - }); + 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); + }); + } + + [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 void Receive(StartInstallation msg) + { + Install(msg).FireAndForget(); + } + + + 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; + } + private async Task Install(StartInstallation msg) + { + _scope = _provider.CreateScope(); + _config = _provider.GetService()!; + _config.Downloads = msg.Download; + _config.Install = msg.Install; + _config.ModlistArchive = msg.ModListPath; + _config.Metadata = msg.Metadata; + _logger.LogInformation("Loading ModList Data"); + _config.ModList = await StandardInstaller.LoadFromFile(_dtos, msg.ModListPath); + _config.Game = _config.ModList.GameType; - private void NextSlide(int direction) + _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 (_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(() => + if (!_locator.TryFindLocation(_config.Game, out var found)) { - Slide = thisSlide; - }); + _logger.LogCritical("Game {game} is not installed on this system", + _config.Game.MetaData().HumanFriendlyGameName); + throw new Exception("Game not found"); + } + + _config.GameFolder = found; } - private int InSlideRange(int i) - { - while (!(i >= 0 && i <= _slides.Length)) - { - if (i >= _slides.Length) i -= _slides.Length; - if (i < 0) i += _slides.Length; - } + _installer = _provider.GetService()!; - return i; + _installer.OnStatusUpdate = async update => + { + Trace.TraceInformation("Update...."); + await Dispatcher.UIThread.InvokeAsync(() => + { + StatusText = update.StatusText; + StepsProgress = update.StepsProgress; + StepProgress = update.StepProgress; + }, DispatcherPriority.Background); + }; + + _logger.LogInformation("Installer created, starting the installation process"); + try + { + var result = await _installer.Begin(CancellationToken.None); + if (!result) throw new Exception("Installation failed"); + + if (result) await SaveConfigAndContinue(_config); } - - public void Receive(StartInstallation msg) + catch (Exception ex) { - - 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; - _config.Metadata = msg.Metadata; - - _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 = async update => - { - Trace.TraceInformation("Update...."); - await Dispatcher.UIThread.InvokeAsync(() => - { - StatusText = update.StatusText; - StepsProgress = update.StepsProgress; - StepProgress = update.StepProgress; - }, DispatcherPriority.Background); - }; - - _logger.LogInformation("Installer created, starting the installation process"); - try - { - var result = await _installer.Begin(CancellationToken.None); - if (!result) throw new Exception("Installation failed"); - - if (result) - { - await SaveConfigAndContinue(_config); - } - } - catch (Exception ex) - { - ErrorPageViewModel.Display("During installation", ex); - } - } - - private async Task SaveConfigAndContinue(InstallerConfiguration config) - { - var path = config.Install.Combine("modlist-image.png"); - { - var image = await ModListUtilities.GetModListImageStream(config.ModlistArchive); - await using var os = path.Open(FileMode.Create, FileAccess.Write); - await image.CopyToAsync(os); - } - - await _installStateManager.SetLastState(new InstallationConfigurationSetting - { - Downloads = config.Downloads, - Install = config.Install, - Metadata = config.Metadata, - ModList = config.ModlistArchive, - Image = path - }); - - MessageBus.Instance.Send(new ConfigureLauncher(config.Install)); - MessageBus.Instance.Send(new NavigateTo(typeof(LauncherViewModel))); + ErrorPageViewModel.Display("During installation", ex); } } + + private async Task SaveConfigAndContinue(InstallerConfiguration config) + { + var path = config.Install.Combine("modlist-image.png"); + { + var image = await ModListUtilities.GetModListImageStream(config.ModlistArchive); + await using var os = path.Open(FileMode.Create, FileAccess.Write); + await image.CopyToAsync(os); + } + + await _installStateManager.SetLastState(new InstallationConfigurationSetting + { + Downloads = config.Downloads, + Install = config.Install, + Metadata = config.Metadata, + ModList = config.ModlistArchive, + Image = path + }); + + MessageBus.Instance.Send(new ConfigureLauncher(config.Install)); + MessageBus.Instance.Send(new NavigateTo(typeof(LauncherViewModel))); + } } \ No newline at end of file diff --git a/Wabbajack.App/ServiceExtensions.cs b/Wabbajack.App/ServiceExtensions.cs index 8d67e9bb..faca0c67 100644 --- a/Wabbajack.App/ServiceExtensions.cs +++ b/Wabbajack.App/ServiceExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Net.Http; -using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Avalonia.Threading; @@ -20,127 +19,123 @@ using Wabbajack.Downloaders.Interfaces; using Wabbajack.DTOs; using Wabbajack.DTOs.DownloadStates; using Wabbajack.DTOs.JsonConverters; -using Wabbajack.Networking.NexusApi; -using Wabbajack.Paths; using Wabbajack.Paths.IO; using Wabbajack.Services.OSIntegrated; -using SettingsView = Wabbajack.App.Screens.SettingsView; -namespace Wabbajack.App +namespace Wabbajack.App; + +public static class ServiceExtensions { - public static class ServiceExtensions + private const int messagePumpDelay = 10; + + + private static CefAppImpl app; + private static Timer messagePump; + + public static IServiceCollection AddAppServices(this IServiceCollection services) { - public static IServiceCollection AddAppServices(this IServiceCollection services) + services.AddAllSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + + services.AddDTOConverters(); + services.AddDTOSerializer(); + services.AddSingleton(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + services.AddAllSingleton(); + + // Services + services.AddAllSingleton, ManualDownloader>(); + + var resources = KnownFolders.EntryPoint; + services.AddSingleton(s => new CefSettings { - services.AddAllSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); + NoSandbox = true, + PersistSessionCookies = true, + MultiThreadedMessageLoop = false, + WindowlessRenderingEnabled = true, + ExternalMessagePump = true, + LocalesDirPath = resources.Combine("locales").ToString(), + ResourcesDirPath = resources.ToString(), + UserAgent = "", + CachePath = KnownFolders.WabbajackAppLocal.Combine("cef_cache").ToString() + }); - services.AddTransient(); - - services.AddDTOConverters(); - services.AddDTOSerializer(); - services.AddSingleton(); - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - services.AddAllSingleton(); - - // Services - services.AddAllSingleton, ManualDownloader>(); - - var resources = KnownFolders.EntryPoint; - services.AddSingleton(s => new CefSettings() - { - NoSandbox = true, - PersistSessionCookies = true, - MultiThreadedMessageLoop = false, - WindowlessRenderingEnabled = true, - ExternalMessagePump = true, - LocalesDirPath = resources.Combine("locales").ToString(), - ResourcesDirPath = resources.ToString(), - UserAgent = "", - CachePath = KnownFolders.WabbajackAppLocal.Combine("cef_cache").ToString() - }); - - services.AddSingleton(s => new Configuration - { - EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), - ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), - SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"), - LogLocation = KnownFolders.EntryPoint.Combine("logs") - }); - - services.AddSingleton(); - - services.AddSingleton(s => - { - App.FrameworkInitialized += App_FrameworkInitialized; - - var app = new CefAppImpl(); - app.ScheduleMessagePumpWorkCallback = OnScheduleMessagePumpWork; - - app.CefProcessMessageReceived += App_CefProcessMessageReceived; - app.Initialize(resources.ToString(), s.GetService()); - - - return app; - }); - - services.AddOSIntegrated(); - return services; - } - - private static async void OnScheduleMessagePumpWork(long delayMs) + services.AddSingleton(s => new Configuration { - await Task.Delay((int)delayMs); - Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork); - } + EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), + ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), + SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"), + LogLocation = KnownFolders.EntryPoint.Combine("logs") + }); - private static void App_CefProcessMessageReceived(object? sender, CefProcessMessageReceivedEventArgs e) + services.AddSingleton(); + + services.AddSingleton(s => { - var msg = e.Name; + App.FrameworkInitialized += App_FrameworkInitialized; - } + var app = new CefAppImpl(); + app.ScheduleMessagePumpWorkCallback = OnScheduleMessagePumpWork; + + app.CefProcessMessageReceived += App_CefProcessMessageReceived; + app.Initialize(resources.ToString(), s.GetService()); - private static CefAppImpl app; - private static Timer messagePump; - private const int messagePumpDelay = 10; - private static void App_FrameworkInitialized(object? sender, EventArgs e) - { - if (CefNetApplication.Instance.UsesExternalMessageLoop) - { - messagePump = new Timer(_ => Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork), null, messagePumpDelay, messagePumpDelay); - } - } + return app; + }); + + services.AddOSIntegrated(); + return services; + } + + private static async void OnScheduleMessagePumpWork(long delayMs) + { + await Task.Delay((int) delayMs); + Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork); + } + + private static void App_CefProcessMessageReceived(object? sender, CefProcessMessageReceivedEventArgs e) + { + var msg = e.Name; + } + + private static void App_FrameworkInitialized(object? sender, EventArgs e) + { + if (CefNetApplication.Instance.UsesExternalMessageLoop) + messagePump = new Timer(_ => Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork), null, messagePumpDelay, + messagePumpDelay); } } \ No newline at end of file diff --git a/Wabbajack.App/Services/ManualDownloader.cs b/Wabbajack.App/Services/ManualDownloader.cs index 6dfa750c..1df68c7f 100644 --- a/Wabbajack.App/Services/ManualDownloader.cs +++ b/Wabbajack.App/Services/ManualDownloader.cs @@ -11,44 +11,42 @@ using Wabbajack.Hashing.xxHash64; using Wabbajack.Paths; using Wabbajack.RateLimiter; -namespace Wabbajack.App.Services +namespace Wabbajack.App.Services; + +public class ManualDownloader : ADownloader { - public class ManualDownloader : ADownloader + public override Priority Priority { get; } + + public override Task Download(Archive archive, Manual state, AbsolutePath destination, IJob job, + CancellationToken token) { - public override Task Download(Archive archive, Manual state, AbsolutePath destination, IJob job, CancellationToken token) - { - throw new System.NotImplementedException(); - } + throw new NotImplementedException(); + } - public override Task Prepare() - { - return Task.FromResult(true); - } + public override Task Prepare() + { + return Task.FromResult(true); + } - public override bool IsAllowed(ServerAllowList allowList, IDownloadState state) - { - var manual = (Manual)state; - return allowList.AllowedPrefixes.Any(p => manual.Url.ToString().StartsWith(p)); - } + public override bool IsAllowed(ServerAllowList allowList, IDownloadState state) + { + var manual = (Manual) state; + return allowList.AllowedPrefixes.Any(p => manual.Url.ToString().StartsWith(p)); + } - public override IDownloadState? Resolve(IReadOnlyDictionary iniData) - { - if (iniData.TryGetValue("manualURL", out var manual)) - { - return new Manual { Url = new Uri(manual) }; - } - return null; - } + public override IDownloadState? Resolve(IReadOnlyDictionary iniData) + { + if (iniData.TryGetValue("manualURL", out var manual)) return new Manual {Url = new Uri(manual)}; + return null; + } - public override Priority Priority { get; } - public override Task Verify(Archive archive, Manual archiveState, IJob job, CancellationToken token) - { - return Task.FromResult(true); - } + public override Task Verify(Archive archive, Manual archiveState, IJob job, CancellationToken token) + { + return Task.FromResult(true); + } - public override IEnumerable MetaIni(Archive a, Manual state) - { - return new[] { $"manualURL={state.Url}" }; - } + public override IEnumerable MetaIni(Archive a, Manual state) + { + return new[] {$"manualURL={state.Url}"}; } } \ No newline at end of file diff --git a/Wabbajack.App/Utilities/CefAppImpl.cs b/Wabbajack.App/Utilities/CefAppImpl.cs index 07a28031..7891bf68 100644 --- a/Wabbajack.App/Utilities/CefAppImpl.cs +++ b/Wabbajack.App/Utilities/CefAppImpl.cs @@ -2,72 +2,70 @@ using System; using System.Runtime.InteropServices; using CefNet; -namespace Wabbajack.App.Utilities +namespace Wabbajack.App.Utilities; + +internal class CefAppImpl : CefNetApplication { - class CefAppImpl : CefNetApplication + public Action ScheduleMessagePumpWorkCallback { get; set; } + + protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine) { - protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine) + base.OnBeforeCommandLineProcessing(processType, commandLine); + + Console.WriteLine("ChromiumWebBrowser_OnBeforeCommandLineProcessing"); + Console.WriteLine(commandLine.CommandLineString); + + //commandLine.AppendSwitchWithValue("proxy-server", "127.0.0.1:8888"); + + + commandLine.AppendSwitchWithValue("remote-debugging-port", "9222"); + + //enable-devtools-experiments + commandLine.AppendSwitch("enable-devtools-experiments"); + + //e.CommandLine.AppendSwitchWithValue("user-agent", "Mozilla/5.0 (Windows 10.0) WebKa/" + DateTime.UtcNow.Ticks); + + //("force-device-scale-factor", "1"); + + //commandLine.AppendSwitch("disable-gpu"); + //commandLine.AppendSwitch("disable-gpu-compositing"); + //commandLine.AppendSwitch("disable-gpu-vsync"); + + commandLine.AppendSwitch("enable-begin-frame-scheduling"); + commandLine.AppendSwitch("enable-media-stream"); + + commandLine.AppendSwitchWithValue("enable-blink-features", "CSSPseudoHas"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - base.OnBeforeCommandLineProcessing(processType, commandLine); - - Console.WriteLine("ChromiumWebBrowser_OnBeforeCommandLineProcessing"); - Console.WriteLine(commandLine.CommandLineString); - - //commandLine.AppendSwitchWithValue("proxy-server", "127.0.0.1:8888"); - - - commandLine.AppendSwitchWithValue("remote-debugging-port", "9222"); - - //enable-devtools-experiments - commandLine.AppendSwitch("enable-devtools-experiments"); - - //e.CommandLine.AppendSwitchWithValue("user-agent", "Mozilla/5.0 (Windows 10.0) WebKa/" + DateTime.UtcNow.Ticks); - - //("force-device-scale-factor", "1"); - - //commandLine.AppendSwitch("disable-gpu"); - //commandLine.AppendSwitch("disable-gpu-compositing"); - //commandLine.AppendSwitch("disable-gpu-vsync"); - - commandLine.AppendSwitch("enable-begin-frame-scheduling"); - commandLine.AppendSwitch("enable-media-stream"); - - commandLine.AppendSwitchWithValue("enable-blink-features", "CSSPseudoHas"); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - commandLine.AppendSwitch("no-zygote"); - commandLine.AppendSwitch("no-sandbox"); - } + commandLine.AppendSwitch("no-zygote"); + commandLine.AppendSwitch("no-sandbox"); } + } - protected override void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context) - { - base.OnContextCreated(browser, frame, context); - frame.ExecuteJavaScript(@" + protected override void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context) + { + base.OnContextCreated(browser, frame, context); + frame.ExecuteJavaScript(@" { const newProto = navigator.__proto__; delete newProto.webdriver; navigator.__proto__ = newProto; }", frame.Url, 0); + } - } + protected override void OnCefProcessMessageReceived(CefProcessMessageReceivedEventArgs e) + { + base.OnCefProcessMessageReceived(e); + } - protected override void OnCefProcessMessageReceived(CefProcessMessageReceivedEventArgs e) - { - base.OnCefProcessMessageReceived(e); - } + protected override CefRenderProcessHandler GetRenderProcessHandler() + { + return base.GetRenderProcessHandler(); + } - public Action ScheduleMessagePumpWorkCallback { get; set; } - - protected override CefRenderProcessHandler GetRenderProcessHandler() - { - return base.GetRenderProcessHandler(); - } - - protected override void OnScheduleMessagePumpWork(long delayMs) - { - ScheduleMessagePumpWorkCallback(delayMs); - } + protected override void OnScheduleMessagePumpWork(long delayMs) + { + ScheduleMessagePumpWorkCallback(delayMs); } } \ No newline at end of file diff --git a/Wabbajack.App/Utilities/LoggerProvider.cs b/Wabbajack.App/Utilities/LoggerProvider.cs index ceabfa67..4d3e7ad5 100644 --- a/Wabbajack.App/Utilities/LoggerProvider.cs +++ b/Wabbajack.App/Utilities/LoggerProvider.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading; using DynamicData; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; using Wabbajack.Paths; using Wabbajack.Paths.IO; @@ -16,36 +15,33 @@ namespace Wabbajack.App.Utilities; public class LoggerProvider : ILoggerProvider { - private Subject _messages = new(); - public IObservable Messages => _messages; - - private long _messageId = 0; - private SourceCache _messageLog = new(m => m.MessageId); - - public readonly ReadOnlyObservableCollection _messagesFiltered; - private readonly CompositeDisposable _disposables; - private readonly Configuration _configuration; - private readonly DateTime _startupTime; private readonly RelativePath _appName; - public AbsolutePath LogPath { get; } + private readonly Configuration _configuration; + private readonly CompositeDisposable _disposables; private readonly Stream _logFile; private readonly StreamWriter _logStream; - public ReadOnlyObservableCollection MessageLog => _messagesFiltered; + + public readonly ReadOnlyObservableCollection _messagesFiltered; + private readonly DateTime _startupTime; + + private long _messageId; + private readonly SourceCache _messageLog = new(m => m.MessageId); + private readonly Subject _messages = new(); public LoggerProvider(Configuration configuration) { _startupTime = DateTime.UtcNow; _configuration = configuration; _configuration.LogLocation.CreateDirectory(); - + _disposables = new CompositeDisposable(); - + Messages.Subscribe(m => _messageLog.AddOrUpdate(m)) .DisposeWith(_disposables); Messages.Subscribe(m => LogToFile(m)) .DisposeWith(_disposables); - + _messageLog.Connect() .Bind(out _messagesFiltered) .Subscribe() @@ -55,11 +51,24 @@ public class LoggerProvider : ILoggerProvider _appName = typeof(LoggerProvider).Assembly.Location.ToAbsolutePath().FileName; LogPath = _configuration.LogLocation.Combine($"{_appName}.current.log"); - _logFile = LogPath.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + _logFile = LogPath.Open(FileMode.Append, FileAccess.Write); _logFile.DisposeWith(_disposables); _logStream = new StreamWriter(_logFile, Encoding.UTF8); + } + public IObservable Messages => _messages; + public AbsolutePath LogPath { get; } + public ReadOnlyObservableCollection MessageLog => _messagesFiltered; + + public void Dispose() + { + _disposables.Dispose(); + } + + public ILogger CreateLogger(string categoryName) + { + return new Logger(this, categoryName); } private void LogToFile(ILogMessage logMessage) @@ -77,21 +86,11 @@ public class LoggerProvider : ILoggerProvider return Interlocked.Increment(ref _messageId); } - public void Dispose() - { - _disposables.Dispose(); - } - - public ILogger CreateLogger(string categoryName) - { - return new Logger(this, categoryName); - } - public class Logger : ILogger { + private readonly string _categoryName; private readonly LoggerProvider _provider; private ImmutableList Scopes = ImmutableList.Empty; - private readonly string _categoryName; public Logger(LoggerProvider provider, string categoryName) { @@ -99,9 +98,11 @@ public class LoggerProvider : ILoggerProvider _provider = provider; } - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, + Func formatter) { - _provider._messages.OnNext(new LogMessage(DateTime.UtcNow, _provider.NextMessageId(), logLevel, eventId, state, exception, formatter)); + _provider._messages.OnNext(new LogMessage(DateTime.UtcNow, _provider.NextMessageId(), logLevel, + eventId, state, exception, formatter)); } public bool IsEnabled(LogLevel logLevel) @@ -125,10 +126,11 @@ public class LoggerProvider : ILoggerProvider string LongMessage { get; } } - record LogMessage(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId, TState State, Exception? Exception, Func Formatter) : ILogMessage + private record LogMessage(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId, + TState State, Exception? Exception, Func Formatter) : ILogMessage { public string ShortMessage => Formatter(State, Exception); - + public string LongMessage { get diff --git a/Wabbajack.App/Utilities/ModListUtilities.cs b/Wabbajack.App/Utilities/ModListUtilities.cs index b7072115..7884bbc2 100644 --- a/Wabbajack.App/Utilities/ModListUtilities.cs +++ b/Wabbajack.App/Utilities/ModListUtilities.cs @@ -5,17 +5,16 @@ using Wabbajack.Common; using Wabbajack.Paths; using Wabbajack.Paths.IO; -namespace Wabbajack.App.Utilities +namespace Wabbajack.App.Utilities; + +public class ModListUtilities { - public class ModListUtilities + public static async Task GetModListImageStream(AbsolutePath modList) { - public static async Task GetModListImageStream(AbsolutePath modList) - { - await using var fs = modList.Open(FileMode.Open, FileAccess.Read, FileShare.Read); - using var ar = new ZipArchive(fs, ZipArchiveMode.Read); - var entry = ar.GetEntry("modlist-image.png"); - await using var stream = entry!.Open(); - return new MemoryStream(await stream.ReadAllAsync()); - } + await using var fs = modList.Open(FileMode.Open, FileAccess.Read, FileShare.Read); + using var ar = new ZipArchive(fs, ZipArchiveMode.Read); + var entry = ar.GetEntry("modlist-image.png"); + await using var stream = entry!.Open(); + return new MemoryStream(await stream.ReadAllAsync()); } } \ No newline at end of file diff --git a/Wabbajack.App/Utils.cs b/Wabbajack.App/Utils.cs index c6c9b00d..14b6b23a 100644 --- a/Wabbajack.App/Utils.cs +++ b/Wabbajack.App/Utils.cs @@ -1,13 +1,12 @@ using System; -using Wabbajack.Common; +using System.Diagnostics; -namespace Wabbajack.App +namespace Wabbajack.App; + +public static class Utils { - public static class Utils + public static void OpenWebsiteInExternalBrowser(Uri uri) { - public static void OpenWebsiteInExternalBrowser(Uri uri) - { - System.Diagnostics.Process.Start(uri.ToString()); - } + Process.Start(uri.ToString()); } } \ No newline at end of file diff --git a/Wabbajack.App/ViewLocator.cs b/Wabbajack.App/ViewLocator.cs index 75e9e02a..a295d10a 100644 --- a/Wabbajack.App/ViewLocator.cs +++ b/Wabbajack.App/ViewLocator.cs @@ -3,30 +3,24 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using Wabbajack.App.ViewModels; -namespace Wabbajack.App +namespace Wabbajack.App; + +public class ViewLocator : IDataTemplate { - public class ViewLocator : IDataTemplate + public bool SupportsRecycling => false; + + public IControl Build(object data) { - public bool SupportsRecycling => false; + var name = data.GetType().FullName!.Replace("ViewModel", "View"); + var type = Type.GetType(name); - public IControl Build(object data) - { - var name = data.GetType().FullName!.Replace("ViewModel", "View"); - var type = Type.GetType(name); + if (type != null) + return (Control) Activator.CreateInstance(type)!; + return new TextBlock {Text = "Not Found: " + name}; + } - if (type != null) - { - return (Control)Activator.CreateInstance(type)!; - } - else - { - return new TextBlock { Text = "Not Found: " + name }; - } - } - - public bool Match(object data) - { - return data is ViewModelBase; - } + public bool Match(object data) + { + return data is ViewModelBase; } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/GuidedWebViewModel.cs b/Wabbajack.App/ViewModels/GuidedWebViewModel.cs index 10d1f5cb..2c42c65b 100644 --- a/Wabbajack.App/ViewModels/GuidedWebViewModel.cs +++ b/Wabbajack.App/ViewModels/GuidedWebViewModel.cs @@ -1,36 +1,29 @@ using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; -using CefNet; using CefNet.Avalonia; using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; -using Wabbajack.Common; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public abstract class GuidedWebViewModel : ViewModelBase, IReceiverMarker { - public abstract class GuidedWebViewModel : ViewModelBase, IReceiverMarker + protected ILogger _logger; + + public GuidedWebViewModel(ILogger logger) { - protected ILogger _logger; + _logger = logger; + Activator = new ViewModelActivator(); - [Reactive] - public string Instructions { get; set; } - - public GuidedWebViewModel(ILogger logger) - { - _logger = logger; - Activator = new ViewModelActivator(); - - this.WhenActivated(disposables => - { - Disposable.Empty.DisposeWith(disposables); - }); - } - - public WebView Browser { get; set; } - - public abstract Task Run(CancellationToken token); + this.WhenActivated(disposables => { Disposable.Empty.DisposeWith(disposables); }); } + + [Reactive] public string Instructions { get; set; } + + public WebView Browser { get; set; } + + public abstract Task Run(CancellationToken token); } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs b/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs index 1df6bd74..a3af5ee6 100644 --- a/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs +++ b/Wabbajack.App/ViewModels/InstallConfigurationViewModel.cs @@ -1,5 +1,3 @@ -using System.IO; -using System.IO.Compression; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -21,121 +19,107 @@ using Wabbajack.Installer; using Wabbajack.Paths; using Wabbajack.Paths.IO; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewModel, IReceiver { - public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewModel, IReceiver + private readonly DTOSerializer _dtos; + private readonly InstallationStateManager _stateManager; + + + public InstallConfigurationViewModel(DTOSerializer dtos, InstallationStateManager stateManager) { - private readonly DTOSerializer _dtos; - private readonly InstallationStateManager _stateManager; + _stateManager = stateManager; - [Reactive] - public AbsolutePath ModListPath { get; set; } - - [Reactive] - public AbsolutePath Install { get; set; } - - [Reactive] - public AbsolutePath Download { get; set; } - - [Reactive] - public ModList? ModList { get; set; } - - [Reactive] - public IBitmap? ModListImage { get; set; } - - [Reactive] - public bool IsReady { get; set; } - - [Reactive] - public ReactiveCommand BeginCommand { get; set; } - - - - public InstallConfigurationViewModel(DTOSerializer dtos, InstallationStateManager stateManager) + _dtos = dtos; + Activator = new ViewModelActivator(); + this.WhenActivated(disposables => { - _stateManager = stateManager; + this.ValidationRule(x => x.ModListPath, p => p.FileExists(), "Wabbajack file must exist"); + this.ValidationRule(x => x.Install, p => p.DirectoryExists(), "Install folder file must exist"); + this.ValidationRule(x => x.Download, p => p != default, "Download folder must be set"); - _dtos = dtos; - Activator = new ViewModelActivator(); - this.WhenActivated(disposables => - { - - this.ValidationRule(x => x.ModListPath, p => p.FileExists(), "Wabbajack file must exist"); - this.ValidationRule(x => x.Install, p => p.DirectoryExists(), "Install folder file must exist"); - this.ValidationRule(x => x.Download, p => p != default, "Download folder must be set"); - - BeginCommand = ReactiveCommand.Create(() => {StartInstall().FireAndForget();}, this.IsValid()); - - - this.WhenAnyValue(t => t.ModListPath) - .Where(t => t != default) - .SelectAsync(disposables, async x => await LoadModList(x)) - .Select(x => x) - .ObserveOn(AvaloniaScheduler.Instance) - .BindTo(this, t => t.ModList) - .DisposeWith(disposables); - - this.WhenAnyValue(t => t.ModListPath) - .Where(t => t != default) - .SelectAsync(disposables, async x => await LoadModListImage(x)) - .ObserveOn(AvaloniaScheduler.Instance) - .BindTo(this, t => t.ModListImage) - .DisposeWith(disposables); - - var settings = this.WhenAnyValue(t => t.ModListPath) - .SelectAsync(disposables, async v => await _stateManager.Get(v)) - .ObserveOn(RxApp.MainThreadScheduler) - .Where(s => s != null); - - settings.Select(s => s!.Install) - .BindTo(this, vm => vm.Install) - .DisposeWith(disposables); - - settings.Select(s => s!.Downloads) - .BindTo(this, vm => vm.Download) - .DisposeWith(disposables); - }); + BeginCommand = ReactiveCommand.Create(() => { StartInstall().FireAndForget(); }, this.IsValid()); - } + this.WhenAnyValue(t => t.ModListPath) + .Where(t => t != default) + .SelectAsync(disposables, async x => await LoadModList(x)) + .Select(x => x) + .ObserveOn(AvaloniaScheduler.Instance) + .BindTo(this, t => t.ModList) + .DisposeWith(disposables); - private async Task StartInstall() + this.WhenAnyValue(t => t.ModListPath) + .Where(t => t != default) + .SelectAsync(disposables, async x => await LoadModListImage(x)) + .ObserveOn(AvaloniaScheduler.Instance) + .BindTo(this, t => t.ModListImage) + .DisposeWith(disposables); + + var settings = this.WhenAnyValue(t => t.ModListPath) + .SelectAsync(disposables, async v => await _stateManager.Get(v)) + .ObserveOn(RxApp.MainThreadScheduler) + .Where(s => s != null); + + settings.Select(s => s!.Install) + .BindTo(this, vm => vm.Install) + .DisposeWith(disposables); + + settings.Select(s => s!.Downloads) + .BindTo(this, vm => vm.Download) + .DisposeWith(disposables); + }); + } + + [Reactive] public AbsolutePath ModListPath { get; set; } + + [Reactive] public AbsolutePath Install { get; set; } + + [Reactive] public AbsolutePath Download { get; set; } + + [Reactive] public ModList? ModList { get; set; } + + [Reactive] public IBitmap? ModListImage { get; set; } + + [Reactive] public bool IsReady { get; set; } + + [Reactive] public ReactiveCommand BeginCommand { get; set; } + + public ViewModelActivator Activator { get; } + + public void Receive(StartInstallConfiguration val) + { + ModListPath = val.ModList; + } + + private async Task StartInstall() + { + ModlistMetadata? metadata = null; + var metadataPath = ModListPath.WithExtension(Ext.MetaData); + if (metadataPath.FileExists()) + metadata = _dtos.Deserialize(await metadataPath.ReadAllTextAsync()); + + _stateManager.SetLastState(new InstallationConfigurationSetting { - ModlistMetadata? metadata = null; - var metadataPath = ModListPath.WithExtension(Ext.MetaData); - if (metadataPath.FileExists()) - { - metadata = _dtos.Deserialize(await metadataPath.ReadAllTextAsync()); - } + ModList = ModListPath, + Downloads = Download, + Install = Install, + Metadata = metadata + }).FireAndForget(); - _stateManager.SetLastState(new InstallationConfigurationSetting - { - ModList = ModListPath, - Downloads = Download, - Install = Install, - Metadata = metadata - }).FireAndForget(); - - MessageBus.Instance.Send(new NavigateTo(typeof(StandardInstallationViewModel))); - MessageBus.Instance.Send(new StartInstallation(ModListPath, Install, Download, metadata)); - } + MessageBus.Instance.Send(new NavigateTo(typeof(StandardInstallationViewModel))); + MessageBus.Instance.Send(new StartInstallation(ModListPath, Install, Download, metadata)); + } - private async Task LoadModListImage(AbsolutePath path) - { - return new Bitmap(await ModListUtilities.GetModListImageStream(path)); - } + private async Task LoadModListImage(AbsolutePath path) + { + return new Bitmap(await ModListUtilities.GetModListImageStream(path)); + } - private async Task LoadModList(AbsolutePath modlist) - { - var definition= await StandardInstaller.LoadFromFile(_dtos, modlist); - return definition; - } - - public ViewModelActivator Activator { get; } - - public void Receive(StartInstallConfiguration val) - { - ModListPath = val.ModList; - } + private async Task LoadModList(AbsolutePath modlist) + { + var definition = await StandardInstaller.LoadFromFile(_dtos, modlist); + return definition; } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/LoversLabOAuthLoginViewModel.cs b/Wabbajack.App/ViewModels/LoversLabOAuthLoginViewModel.cs index 19867da0..a34784bc 100644 --- a/Wabbajack.App/ViewModels/LoversLabOAuthLoginViewModel.cs +++ b/Wabbajack.App/ViewModels/LoversLabOAuthLoginViewModel.cs @@ -1,20 +1,15 @@ -using System; using System.Net.Http; -using CefNet; -using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Wabbajack.DTOs.Logins; -using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated.TokenProviders; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class LoversLabOAuthLoginViewModel : OAuthLoginViewModel { - public class LoversLabOAuthLoginViewModel : OAuthLoginViewModel + public LoversLabOAuthLoginViewModel(ILogger logger, HttpClient client, + LoversLabTokenProvider tokenProvider) + : base(logger, client, tokenProvider) { - public LoversLabOAuthLoginViewModel(ILogger logger, HttpClient client, - LoversLabTokenProvider tokenProvider) - : base(logger, client, tokenProvider) - { - } } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/MainWindowViewModel.cs b/Wabbajack.App/ViewModels/MainWindowViewModel.cs index 8f942b71..436aa4e0 100644 --- a/Wabbajack.App/ViewModels/MainWindowViewModel.cs +++ b/Wabbajack.App/ViewModels/MainWindowViewModel.cs @@ -8,7 +8,6 @@ using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using Avalonia.Controls; -using Microsoft.Extensions.DependencyInjection; using ReactiveUI; using ReactiveUI.Fody.Helpers; using ReactiveUI.Validation.Helpers; @@ -21,142 +20,121 @@ using Wabbajack.Common; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class MainWindowViewModel : ReactiveValidationObject, IActivatableViewModel, IReceiver, + IReceiver { - public class MainWindowViewModel : ReactiveValidationObject, IActivatableViewModel, IReceiver, IReceiver + private readonly InstallationStateManager _manager; + private readonly IServiceProvider _provider; + private readonly Task _resourcePoller; + private readonly IResource[] _resources; + private readonly IEnumerable _screens; + private StatusReport[] _prevReport; + + public MainWindowViewModel(IEnumerable screens, IEnumerable resources, + IServiceProvider provider, + InstallationStateManager manager) { - private readonly IEnumerable _screens; - private readonly IServiceProvider _provider; - private readonly IResource[] _resources; - private StatusReport[] _prevReport; - private readonly Task _resourcePoller; - private readonly InstallationStateManager _manager; + _provider = provider; + _screens = screens; + _resources = resources.ToArray(); + _manager = manager; - [Reactive] - public Control CurrentScreen { get; set; } - - [Reactive] - private ImmutableStack BreadCrumbs { get; set; } = ImmutableStack.Empty; + _prevReport = NextReport(); - [Reactive] - public ReactiveCommand BackButton { get; set; } - - [Reactive] - public ReactiveCommand SettingsButton { get; set; } - - [Reactive] - public ReactiveCommand LogViewButton { get; set; } + Activator = new ViewModelActivator(); - [Reactive] - public string ResourceStatus { get; set; } - - public MainWindowViewModel(IEnumerable screens, IEnumerable resources, IServiceProvider provider, - InstallationStateManager manager) + _resourcePoller = StartResourcePoller(TimeSpan.FromSeconds(0.25)); + + this.WhenActivated(disposables => { - _provider = provider; - _screens = screens; - _resources = resources.ToArray(); - _manager = manager; + BackButton = ReactiveCommand.Create(() => { Receive(new NavigateBack()); }, + this.ObservableForProperty(vm => vm.BreadCrumbs) + .Select(bc => bc.Value.Count() > 1)) + .DisposeWith(disposables); - _prevReport = NextReport(); - - Activator = new ViewModelActivator(); - - _resourcePoller = StartResourcePoller(TimeSpan.FromSeconds(0.25)); - - this.WhenActivated(disposables => - { - BackButton = ReactiveCommand.Create(() => - { - Receive(new NavigateBack()); - }, - this.ObservableForProperty(vm => vm.BreadCrumbs) - .Select(bc => bc.Value.Count() > 1)) - .DisposeWith(disposables); - - SettingsButton = ReactiveCommand.Create(() => - { - Receive(new NavigateTo(typeof(SettingsViewModel))); - }) - .DisposeWith(disposables); - - LogViewButton = ReactiveCommand.Create(() => - { - Receive(new NavigateTo(typeof(LogScreenViewModel))); - }) - .DisposeWith(disposables); - - }); - CurrentScreen = (Control)_screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel)); + SettingsButton = ReactiveCommand.Create(() => { Receive(new NavigateTo(typeof(SettingsViewModel))); }) + .DisposeWith(disposables); - LoadFirstScreen().FireAndForget(); + LogViewButton = ReactiveCommand.Create(() => { Receive(new NavigateTo(typeof(LogScreenViewModel))); }) + .DisposeWith(disposables); + }); + CurrentScreen = (Control) _screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel)); + LoadFirstScreen().FireAndForget(); + } + + [Reactive] public Control CurrentScreen { get; set; } + + [Reactive] private ImmutableStack BreadCrumbs { get; set; } = ImmutableStack.Empty; + + [Reactive] public ReactiveCommand BackButton { get; set; } + + [Reactive] public ReactiveCommand SettingsButton { get; set; } + + [Reactive] public ReactiveCommand LogViewButton { get; set; } + + [Reactive] public string ResourceStatus { get; set; } + + public ViewModelActivator Activator { get; } + + public void Receive(NavigateBack val) + { + CurrentScreen = BreadCrumbs.Peek(); + BreadCrumbs = BreadCrumbs.Pop(); + } + + public void Receive(NavigateTo val) + { + BreadCrumbs = BreadCrumbs.Push(CurrentScreen); + + if (val.ViewModel.IsAssignableTo(typeof(GuidedWebViewModel))) + CurrentScreen = new GuidedWebView {ViewModel = (GuidedWebViewModel) _provider.GetService(val.ViewModel)!}; + else + CurrentScreen = (Control) _screens.First(s => s.ViewModelType == val.ViewModel); + } + + private async Task LoadFirstScreen() + { + var setting = await _manager.GetLastState(); + if (setting.Install != default && setting.Install.DirectoryExists()) + { + BreadCrumbs = + BreadCrumbs.Push((Control) _screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel))); + + MessageBus.Instance.Send(new ConfigureLauncher(setting.Install)); + Receive(new NavigateTo(typeof(LauncherViewModel))); } - - private async Task LoadFirstScreen() + else { - var setting = await _manager.GetLastState(); - if (setting.Install != default && setting.Install.DirectoryExists()) - { - BreadCrumbs = - BreadCrumbs.Push((Control)_screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel))); - - MessageBus.Instance.Send(new ConfigureLauncher(setting.Install)); - Receive(new NavigateTo(typeof(LauncherViewModel))); - } - else - { - Receive(new NavigateTo(typeof(ModeSelectionViewModel))); - } - } - - private StatusReport[] NextReport() - { - return _resources.Select(r => r.StatusReport).ToArray(); - } - - private async Task StartResourcePoller(TimeSpan span) - { - while (true) - { - var report = NextReport(); - var sb = new StringBuilder(); - foreach (var (prev, next, limiter) in _prevReport.Zip(report, _resources)) - { - var throughput = next.Transferred - prev.Transferred; - if (throughput != 0) - { - sb.Append( - $"{limiter.Name}: [{next.Running}/{next.Pending + next.Running}] {throughput.ToFileSizeString()}/sec "); - } - } - - ResourceStatus = sb.ToString(); - _prevReport = report; - - await Task.Delay(TimeSpan.FromSeconds(0.5)); - } - } - - public ViewModelActivator Activator { get; } - public void Receive(NavigateTo val) - { - BreadCrumbs = BreadCrumbs.Push(CurrentScreen); - - if (val.ViewModel.IsAssignableTo(typeof(GuidedWebViewModel))) - { - CurrentScreen = new GuidedWebView() { ViewModel = (GuidedWebViewModel)_provider.GetService(val.ViewModel)! }; - } - else - { - CurrentScreen = (Control)_screens.First(s => s.ViewModelType == val.ViewModel); - } - } - - public void Receive(NavigateBack val) - { - CurrentScreen = BreadCrumbs.Peek(); - BreadCrumbs = BreadCrumbs.Pop(); + Receive(new NavigateTo(typeof(ModeSelectionViewModel))); } } -} + + private StatusReport[] NextReport() + { + return _resources.Select(r => r.StatusReport).ToArray(); + } + + private async Task StartResourcePoller(TimeSpan span) + { + while (true) + { + var report = NextReport(); + var sb = new StringBuilder(); + foreach (var (prev, next, limiter) in _prevReport.Zip(report, _resources)) + { + var throughput = next.Transferred - prev.Transferred; + if (throughput != 0) + sb.Append( + $"{limiter.Name}: [{next.Running}/{next.Pending + next.Running}] {throughput.ToFileSizeString()}/sec "); + } + + ResourceStatus = sb.ToString(); + _prevReport = report; + + await Task.Delay(TimeSpan.FromSeconds(0.5)); + } + } +} \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/ModeSelectionViewModel.cs b/Wabbajack.App/ViewModels/ModeSelectionViewModel.cs index 0bc86199..abfc99ba 100644 --- a/Wabbajack.App/ViewModels/ModeSelectionViewModel.cs +++ b/Wabbajack.App/ViewModels/ModeSelectionViewModel.cs @@ -1,14 +1,11 @@ using ReactiveUI; -using ReactiveUI.Fody.Helpers; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class ModeSelectionViewModel : ViewModelBase { - public class ModeSelectionViewModel : ViewModelBase + public ModeSelectionViewModel() { - public ModeSelectionViewModel() - { - Activator = new ViewModelActivator(); - - } + Activator = new ViewModelActivator(); } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/NexusLoginViewModel.cs b/Wabbajack.App/ViewModels/NexusLoginViewModel.cs index 8fe5b29a..485edd30 100644 --- a/Wabbajack.App/ViewModels/NexusLoginViewModel.cs +++ b/Wabbajack.App/ViewModels/NexusLoginViewModel.cs @@ -9,97 +9,92 @@ using Wabbajack.App.Messages; using Wabbajack.DTOs.Logins; using Wabbajack.Services.OSIntegrated.TokenProviders; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class NexusLoginViewModel : GuidedWebViewModel { - public class NexusLoginViewModel : GuidedWebViewModel + private readonly NexusApiTokenProvider _tokenProvider; + + public NexusLoginViewModel(ILogger logger, NexusApiTokenProvider tokenProvider) : base(logger) { - private readonly NexusApiTokenProvider _tokenProvider; + _tokenProvider = tokenProvider; + } - public NexusLoginViewModel(ILogger logger, NexusApiTokenProvider tokenProvider) : base(logger) - { - _tokenProvider = tokenProvider; - } + public override async Task Run(CancellationToken token) + { + token.ThrowIfCancellationRequested(); - public override async Task Run(CancellationToken token) + Instructions = "Please log into the Nexus"; + + await Browser.WaitForReady(); + + await Browser.NavigateTo(new Uri( + "https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com")); + + + Cookie[] cookies = { }; + while (true) { + cookies = await Browser.Cookies("nexusmods.com", token); + if (cookies.Any(c => c.Name == "member_id")) + break; + token.ThrowIfCancellationRequested(); - - Instructions = "Please log into the Nexus"; - - await Browser.WaitForReady(); - - await Browser.NavigateTo(new Uri("https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com")); - - - Cookie[] cookies = {}; - while (true) - { - cookies = await Browser.Cookies("nexusmods.com", token); - if (cookies.Any(c => c.Name == "member_id")) - break; - - token.ThrowIfCancellationRequested(); - await Task.Delay(500, token); - } - - Instructions = "Getting API Key..."; - - await Browser.NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api")); - - var key = ""; - - while (true) - { - - try - { - key = (await Browser.GetDom(token)) - .DocumentNode - .QuerySelectorAll("input[value=wabbajack]") - .SelectMany(p => p.ParentNode.ParentNode.QuerySelectorAll("textarea.application-key")) - .Select(node => node.InnerHtml) - .FirstOrDefault() ?? ""; - } - catch (Exception) - { - // ignored - } - - if (!string.IsNullOrEmpty(key)) - break; - - try - { - await Browser.EvaluateJavaScript( - "var found = document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"form button[type=submit]\");" + - "found.onclick= function() {return true;};" + - "found.class = \" \"; " + - "found.click();" + - "found.remove(); found = undefined;" - ); - Instructions = "Generating API Key, Please Wait..."; - - - } - catch (Exception) - { - // ignored - } - - token.ThrowIfCancellationRequested(); - await Task.Delay(500, token); - - MessageBus.Instance.Send(new NavigateBack()); - - } - - Instructions = "Success, saving information..."; - await _tokenProvider.SetToken(new NexusApiState - { - Cookies = cookies, - ApiKey = key - }); - + await Task.Delay(500, token); } + + Instructions = "Getting API Key..."; + + await Browser.NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api")); + + var key = ""; + + while (true) + { + try + { + key = (await Browser.GetDom(token)) + .DocumentNode + .QuerySelectorAll("input[value=wabbajack]") + .SelectMany(p => p.ParentNode.ParentNode.QuerySelectorAll("textarea.application-key")) + .Select(node => node.InnerHtml) + .FirstOrDefault() ?? ""; + } + catch (Exception) + { + // ignored + } + + if (!string.IsNullOrEmpty(key)) + break; + + try + { + await Browser.EvaluateJavaScript( + "var found = document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"form button[type=submit]\");" + + "found.onclick= function() {return true;};" + + "found.class = \" \"; " + + "found.click();" + + "found.remove(); found = undefined;" + ); + Instructions = "Generating API Key, Please Wait..."; + } + catch (Exception) + { + // ignored + } + + token.ThrowIfCancellationRequested(); + await Task.Delay(500, token); + + MessageBus.Instance.Send(new NavigateBack()); + } + + Instructions = "Success, saving information..."; + await _tokenProvider.SetToken(new NexusApiState + { + Cookies = cookies, + ApiKey = key + }); } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/OAuthLoginViewModel.cs b/Wabbajack.App/ViewModels/OAuthLoginViewModel.cs index 9f9e53b7..f3b9d519 100644 --- a/Wabbajack.App/ViewModels/OAuthLoginViewModel.cs +++ b/Wabbajack.App/ViewModels/OAuthLoginViewModel.cs @@ -12,118 +12,109 @@ using Wabbajack.App.Extensions; using Wabbajack.DTOs.Logins; using Wabbajack.Services.OSIntegrated; -namespace Wabbajack.App.ViewModels -{ - public abstract class OAuthLoginViewModel : GuidedWebViewModel +namespace Wabbajack.App.ViewModels; + +public abstract class OAuthLoginViewModel : GuidedWebViewModel where TLoginType : OAuth2LoginState, new() +{ + private readonly HttpClient _httpClient; + private readonly EncryptedJsonTokenProvider _tokenProvider; + + public OAuthLoginViewModel(ILogger logger, HttpClient httpClient, + EncryptedJsonTokenProvider tokenProvider) : base(logger) { - private readonly HttpClient _httpClient; - private readonly EncryptedJsonTokenProvider _tokenProvider; - - public OAuthLoginViewModel(ILogger logger, HttpClient httpClient, EncryptedJsonTokenProvider tokenProvider) : base(logger) - { - _logger = logger; - _httpClient = httpClient; - _tokenProvider = tokenProvider; - - } - - private class AsyncSchemeHandler : CefSchemeHandlerFactory - { - private TaskCompletionSource _tcs = new(); - public Task Task => _tcs.Task; - - public AsyncSchemeHandler() - { - - } - - protected override CefResourceHandler Create(CefBrowser browser, CefFrame frame, string schemeName, - CefRequest request) - { - return new Handler(_tcs); - } - } - - private class Handler : CefResourceHandler - { - private readonly TaskCompletionSource _tcs; - - public Handler(TaskCompletionSource tcs) - { - _tcs = tcs; - } - - protected override bool ProcessRequest(CefRequest request, CefCallback callback) - { - _tcs.TrySetResult(new Uri(request.Url)); - return false; - } - } - - public override async Task Run(CancellationToken token) - { - var tlogin = new TLoginType(); - - await Browser.WaitForReady(); - - var handler = new AsyncSchemeHandler(); - Browser.RequestContext.RegisterSchemeHandlerFactory("wabbajack", "", handler); - - Instructions = $"Please log in and allow Wabbajack to access your {tlogin.SiteName} account"; - - var scopes = string.Join(" ", tlogin.Scopes); - var state = Guid.NewGuid().ToString(); - - await Browser.NavigateTo(new Uri(tlogin.AuthorizationEndpoint + - $"?response_type=code&client_id={tlogin.ClientID}&state={state}&scope={scopes}")); - - var uri = await handler.Task.WaitAsync(token); - - var cookies = await Browser.Cookies(tlogin.AuthorizationEndpoint.Host, token); - - var parsed = HttpUtility.ParseQueryString(uri.Query); - if (parsed.Get("state") != state) - { - _logger.LogCritical("Bad OAuth state, this shouldn't happen"); - throw new Exception("Bad OAuth State"); - } - - if (parsed.Get("code") == null) - { - _logger.LogCritical("Bad code result from OAuth"); - throw new Exception("Bad code result from OAuth"); - } - - var authCode = parsed.Get("code"); - - var formData = new KeyValuePair[] - { - new("grant_type", "authorization_code"), - new("code", authCode), - new("client_id", tlogin.ClientID) - }; - - var msg = new HttpRequestMessage(); - msg.Method = HttpMethod.Post; - msg.RequestUri = tlogin.TokenEndpoint; - msg.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"); - msg.Headers.Add("Cookie", string.Join(";", cookies.Select(c => $"{c.Name}={c.Value}"))); - msg.Content = new FormUrlEncodedContent(formData.ToList()); - - using var response = await _httpClient.SendAsync(msg, token); - var data = await response.Content.ReadFromJsonAsync(cancellationToken: token); - - await _tokenProvider.SetToken(new TLoginType - { - Cookies = cookies, - ResultState = data! - }); - - } - - + _logger = logger; + _httpClient = httpClient; + _tokenProvider = tokenProvider; } + public override async Task Run(CancellationToken token) + { + var tlogin = new TLoginType(); + await Browser.WaitForReady(); + + var handler = new AsyncSchemeHandler(); + Browser.RequestContext.RegisterSchemeHandlerFactory("wabbajack", "", handler); + + Instructions = $"Please log in and allow Wabbajack to access your {tlogin.SiteName} account"; + + var scopes = string.Join(" ", tlogin.Scopes); + var state = Guid.NewGuid().ToString(); + + await Browser.NavigateTo(new Uri(tlogin.AuthorizationEndpoint + + $"?response_type=code&client_id={tlogin.ClientID}&state={state}&scope={scopes}")); + + var uri = await handler.Task.WaitAsync(token); + + var cookies = await Browser.Cookies(tlogin.AuthorizationEndpoint.Host, token); + + var parsed = HttpUtility.ParseQueryString(uri.Query); + if (parsed.Get("state") != state) + { + _logger.LogCritical("Bad OAuth state, this shouldn't happen"); + throw new Exception("Bad OAuth State"); + } + + if (parsed.Get("code") == null) + { + _logger.LogCritical("Bad code result from OAuth"); + throw new Exception("Bad code result from OAuth"); + } + + var authCode = parsed.Get("code"); + + var formData = new KeyValuePair[] + { + new("grant_type", "authorization_code"), + new("code", authCode), + new("client_id", tlogin.ClientID) + }; + + var msg = new HttpRequestMessage(); + msg.Method = HttpMethod.Post; + msg.RequestUri = tlogin.TokenEndpoint; + msg.Headers.Add("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"); + msg.Headers.Add("Cookie", string.Join(";", cookies.Select(c => $"{c.Name}={c.Value}"))); + msg.Content = new FormUrlEncodedContent(formData.ToList()); + + using var response = await _httpClient.SendAsync(msg, token); + var data = await response.Content.ReadFromJsonAsync(cancellationToken: token); + + await _tokenProvider.SetToken(new TLoginType + { + Cookies = cookies, + ResultState = data! + }); + } + + private class AsyncSchemeHandler : CefSchemeHandlerFactory + { + private readonly TaskCompletionSource _tcs = new(); + + public Task Task => _tcs.Task; + + protected override CefResourceHandler Create(CefBrowser browser, CefFrame frame, string schemeName, + CefRequest request) + { + return new Handler(_tcs); + } + } + + private class Handler : CefResourceHandler + { + private readonly TaskCompletionSource _tcs; + + public Handler(TaskCompletionSource tcs) + { + _tcs = tcs; + } + + protected override bool ProcessRequest(CefRequest request, CefCallback callback) + { + _tcs.TrySetResult(new Uri(request.Url)); + return false; + } + } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/SubViewModels/SlideViewModel.cs b/Wabbajack.App/ViewModels/SubViewModels/SlideViewModel.cs index 796449f3..367144f6 100644 --- a/Wabbajack.App/ViewModels/SubViewModels/SlideViewModel.cs +++ b/Wabbajack.App/ViewModels/SubViewModels/SlideViewModel.cs @@ -8,37 +8,30 @@ using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.DTOs.DownloadStates; -namespace Wabbajack.App.ViewModels.SubViewModels +namespace Wabbajack.App.ViewModels.SubViewModels; + +public class SlideViewModel : ViewModelBase { - public class SlideViewModel : ViewModelBase + public SlideViewModel() { - [Reactive] - public IMetaState MetaState { get; set; } - - [Reactive] - public IImage? Image { get; set; } + Activator = new ViewModelActivator(); + Image = null; + } - public bool Loading { get; set; } = false; + [Reactive] public IMetaState MetaState { get; set; } - public SlideViewModel() - { - Activator = new ViewModelActivator(); - Image = null; - } + [Reactive] public IImage? Image { get; set; } - public async Task PreCache(HttpClient client) - { - Loading = true; - var url = await client.GetByteArrayAsync(MetaState.ImageURL); - var img = new Bitmap(new MemoryStream(url)); - - await Dispatcher.UIThread.InvokeAsync(() => - { - Image = img; - }); - - Loading = false; - } + public bool Loading { get; set; } + public async Task PreCache(HttpClient client) + { + Loading = true; + var url = await client.GetByteArrayAsync(MetaState.ImageURL); + var img = new Bitmap(new MemoryStream(url)); + + await Dispatcher.UIThread.InvokeAsync(() => { Image = img; }); + + Loading = false; } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/VectorPlexusOAuthLoginViewModel.cs b/Wabbajack.App/ViewModels/VectorPlexusOAuthLoginViewModel.cs index dc4c70d9..6054e252 100644 --- a/Wabbajack.App/ViewModels/VectorPlexusOAuthLoginViewModel.cs +++ b/Wabbajack.App/ViewModels/VectorPlexusOAuthLoginViewModel.cs @@ -1,19 +1,15 @@ -using System; using System.Net.Http; -using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Wabbajack.DTOs.Logins; -using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated.TokenProviders; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class VectorPlexusOAuthLoginViewModel : OAuthLoginViewModel { - public class VectorPlexusOAuthLoginViewModel : OAuthLoginViewModel + public VectorPlexusOAuthLoginViewModel(ILogger logger, HttpClient client, + VectorPlexusTokenProvider tokenProvider) + : base(logger, client, tokenProvider) { - public VectorPlexusOAuthLoginViewModel(ILogger logger, HttpClient client, - VectorPlexusTokenProvider tokenProvider) - : base(logger, client, tokenProvider) - { - } } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/ViewModelBase.cs b/Wabbajack.App/ViewModels/ViewModelBase.cs index 8a12d8b4..ddb83d69 100644 --- a/Wabbajack.App/ViewModels/ViewModelBase.cs +++ b/Wabbajack.App/ViewModels/ViewModelBase.cs @@ -1,10 +1,9 @@ using ReactiveUI; using ReactiveUI.Validation.Helpers; -namespace Wabbajack.App.ViewModels +namespace Wabbajack.App.ViewModels; + +public class ViewModelBase : ReactiveValidationObject, IActivatableViewModel { - public class ViewModelBase : ReactiveValidationObject, IActivatableViewModel - { - public ViewModelActivator Activator { get; protected set; } - } -} + public ViewModelActivator Activator { get; protected set; } +} \ No newline at end of file diff --git a/Wabbajack.App/Views/GuidedWebView.axaml b/Wabbajack.App/Views/GuidedWebView.axaml index 41d50941..9adb73e3 100644 --- a/Wabbajack.App/Views/GuidedWebView.axaml +++ b/Wabbajack.App/Views/GuidedWebView.axaml @@ -7,13 +7,20 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Views.GuidedWebView"> - - - - - - - - + + + + + + + + - + \ No newline at end of file diff --git a/Wabbajack.App/Views/GuidedWebView.axaml.cs b/Wabbajack.App/Views/GuidedWebView.axaml.cs index 43fb95ee..b9e00b00 100644 --- a/Wabbajack.App/Views/GuidedWebView.axaml.cs +++ b/Wabbajack.App/Views/GuidedWebView.axaml.cs @@ -1,30 +1,23 @@ using System.Reactive.Disposables; -using System.Text; using System.Threading; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using CefNet; using ReactiveUI; using Wabbajack.App.ViewModels; using Wabbajack.Common; -namespace Wabbajack.App.Views +namespace Wabbajack.App.Views; + +public partial class GuidedWebView : ScreenBase { - public partial class GuidedWebView : ScreenBase + public GuidedWebView() : base(false) { - public GuidedWebView() : base(false) + InitializeComponent(); + + this.WhenActivated(disposables => { - InitializeComponent(); - - this.WhenActivated(disposables => - { - ViewModel.Browser = WebView; - this.Bind(ViewModel, vm => vm.Instructions, view => view.Instructions.Text) - .DisposeWith(disposables); - ViewModel.Run(CancellationToken.None).FireAndForget(); - }); - } - + ViewModel.Browser = WebView; + this.Bind(ViewModel, vm => vm.Instructions, view => view.Instructions.Text) + .DisposeWith(disposables); + ViewModel.Run(CancellationToken.None).FireAndForget(); + }); } } \ No newline at end of file diff --git a/Wabbajack.App/Views/InstallConfigurationView.axaml b/Wabbajack.App/Views/InstallConfigurationView.axaml index c664d076..a7d90590 100644 --- a/Wabbajack.App/Views/InstallConfigurationView.axaml +++ b/Wabbajack.App/Views/InstallConfigurationView.axaml @@ -14,22 +14,21 @@ - + - - + + - + - + - - - + - + \ No newline at end of file diff --git a/Wabbajack.App/Views/InstallConfigurationView.axaml.cs b/Wabbajack.App/Views/InstallConfigurationView.axaml.cs index e8e24578..e7de5283 100644 --- a/Wabbajack.App/Views/InstallConfigurationView.axaml.cs +++ b/Wabbajack.App/Views/InstallConfigurationView.axaml.cs @@ -7,46 +7,44 @@ using ReactiveUI; using Wabbajack.App.Interfaces; using Wabbajack.App.ViewModels; -namespace Wabbajack.App.Views +namespace Wabbajack.App.Views; + +public partial class InstallConfigurationView : ReactiveUserControl, IScreenView { - public partial class InstallConfigurationView : ReactiveUserControl, IScreenView + public InstallConfigurationView() { - public InstallConfigurationView() + InitializeComponent(); + DataContext = App.Services.GetService()!; + + this.WhenActivated(disposables => { - InitializeComponent(); - DataContext = App.Services.GetService()!; + this.Bind(ViewModel, x => x.ModListPath, + view => view.ModListFile.SelectedPath) + .DisposeWith(disposables); + this.Bind(ViewModel, x => x.Download, + view => view.DownloadPath.SelectedPath) + .DisposeWith(disposables); + this.Bind(ViewModel, x => x.Install, + view => view.InstallPath.SelectedPath) + .DisposeWith(disposables); - this.WhenActivated(disposables => - { + ViewModel.WhenAnyValue(x => x.BeginCommand) + .Where(x => x != default) + .BindTo(BeginInstall, x => x.Button.Command) + .DisposeWith(disposables); - this.Bind(ViewModel, x => x.ModListPath, - view => view.ModListFile.SelectedPath) - .DisposeWith(disposables); - this.Bind(ViewModel, x => x.Download, - view => view.DownloadPath.SelectedPath) - .DisposeWith(disposables); - this.Bind(ViewModel, x => x.Install, - view => view.InstallPath.SelectedPath) - .DisposeWith(disposables); + ViewModel.WhenAnyValue(x => x.ModList) + .Where(x => x != default) + .Select(x => x!.Name) + .BindTo(ModListName, x => x.Text) + .DisposeWith(disposables); - ViewModel.WhenAnyValue(x => x.BeginCommand) - .Where(x => x != default) - .BindTo(BeginInstall, x => x.Button.Command) - .DisposeWith(disposables); - - ViewModel.WhenAnyValue(x => x.ModList) - .Where(x => x != default) - .Select(x => x!.Name) - .BindTo(ModListName, x => x.Text) - .DisposeWith(disposables); - - ViewModel.WhenAnyValue(x => x.ModListImage) - .Where(x => x != default) - .BindTo(ModListImage, x => x.Source) - .DisposeWith(disposables); - }); - } - - public Type ViewModelType => typeof(InstallConfigurationViewModel); + ViewModel.WhenAnyValue(x => x.ModListImage) + .Where(x => x != default) + .BindTo(ModListImage, x => x.Source) + .DisposeWith(disposables); + }); } + + public Type ViewModelType => typeof(InstallConfigurationViewModel); } \ No newline at end of file diff --git a/Wabbajack.App/Views/MainWindow.axaml b/Wabbajack.App/Views/MainWindow.axaml index 1c4eb209..e35e6b12 100644 --- a/Wabbajack.App/Views/MainWindow.axaml +++ b/Wabbajack.App/Views/MainWindow.axaml @@ -1,28 +1,26 @@ - + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:vm="using:Wabbajack.App.ViewModels" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:app="clr-namespace:Wabbajack.App" + + xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="Wabbajack.App.Views.MainWindow" + Icon="{x:Null}" + WindowStartupLocation="CenterScreen" + Background="#121212" + + MaxWidth="1920" + MaxHeight="1080" + + ExtendClientAreaToDecorationsHint="True" + ExtendClientAreaChromeHints="NoChrome" + ExtendClientAreaTitleBarHeightHint="-1" + + Title="Wabbajack 3.0 - WIP"> +