diff --git a/Wabbajack.App.Test/Extensions.cs b/Wabbajack.App.Test/Extensions.cs new file mode 100644 index 00000000..de3b9eb2 --- /dev/null +++ b/Wabbajack.App.Test/Extensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Threading; +using ReactiveUI; +using Wabbajack.App.Models; + +namespace Wabbajack.App.Test; + +public static class Extensions +{ + public static async Task WaitUntil(this T src, Predicate check, Action? doFunc = null) + { + Dispatcher.UIThread.RunJobs(); + + while (!check(src)) + { + doFunc?.Invoke(); + await Task.Delay(100); + } + } + + public static async Task WaitForLock(this LoadingLock l) + { + Dispatcher.UIThread.RunJobs(); + while (!l.IsLoading) + { + Dispatcher.UIThread.RunJobs(); + await Task.Delay(100); + } + } + public static async Task WaitForUnlock(this LoadingLock l) + { + Dispatcher.UIThread.RunJobs(); + while (l.IsLoading) + { + Dispatcher.UIThread.RunJobs(); + await Task.Delay(100); + } + } +} \ No newline at end of file diff --git a/Wabbajack.App.Test/GalleryItemTests.cs b/Wabbajack.App.Test/GalleryItemTests.cs new file mode 100644 index 00000000..1c385191 --- /dev/null +++ b/Wabbajack.App.Test/GalleryItemTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using Wabbajack.App.Controls; +using Wabbajack.App.Messages; +using Wabbajack.App.Screens; +using Wabbajack.App.ViewModels; +using Wabbajack.Common; +using Wabbajack.Paths.IO; +using Wabbajack.RateLimiter; +using Xunit; + +namespace Wabbajack.App.Test; + +public class GalleryItemTests +{ + private readonly BrowseViewModel _gallery; + private readonly Configuration _config; + + 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(); + Assert.True(_gallery.ModLists.Count > 0); + + foreach (var item in _gallery.ModLists) + { + Assert.NotEqual(ModListState.Downloading, item.State); + if (item.State == ModListState.Downloaded) + Assert.True(item.ModListLocation.FileExists()); + else + Assert.False(item.ModListLocation.FileExists()); + + Assert.Equal(Percent.Zero, item.Progress); + } + + var modList = _gallery.ModLists.First(); + modList.ExecuteCommand.Execute().Subscribe().Dispose(); + + var progress = Percent.Zero; + await modList.WaitUntil(x => x.State == ModListState.Downloading); + await modList.WaitUntil(x => x.State != ModListState.Downloading, () => + { + Assert.True(modList.Progress >= progress); + progress = modList.Progress; + }); + + Assert.Equal(Percent.Zero, modList.Progress); + 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); + } +} \ No newline at end of file diff --git a/Wabbajack.App.Test/Startup.cs b/Wabbajack.App.Test/Startup.cs new file mode 100644 index 00000000..70c22ec8 --- /dev/null +++ b/Wabbajack.App.Test/Startup.cs @@ -0,0 +1,35 @@ +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(); + } + + public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) + { + 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 new file mode 100644 index 00000000..951bf570 --- /dev/null +++ b/Wabbajack.App.Test/Wabbajack.App.Test.csproj @@ -0,0 +1,27 @@ + + + + Exe + net6.0-windows + net6.0 + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/Wabbajack.App/Controls/BrowseItemViewModel.cs b/Wabbajack.App/Controls/BrowseItemViewModel.cs index 774bdda1..ea99c35a 100644 --- a/Wabbajack.App/Controls/BrowseItemViewModel.cs +++ b/Wabbajack.App/Controls/BrowseItemViewModel.cs @@ -159,6 +159,7 @@ namespace Wabbajack.App.Controls var metadataPath = ModListLocation.WithExtension(Ext.MetaData); await metadataPath.WriteAllTextAsync(_dtos.Serialize(_metadata)); + Progress = Percent.Zero; await UpdateState(); } diff --git a/Wabbajack.App/MessageBus.cs b/Wabbajack.App/MessageBus.cs index e88fb800..3a66db03 100644 --- a/Wabbajack.App/MessageBus.cs +++ b/Wabbajack.App/MessageBus.cs @@ -9,9 +9,14 @@ using Wabbajack.App.Messages; namespace Wabbajack.App { - public class MessageBus + public interface IMessageBus { - public static MessageBus Instance { get; private set; } + public void Send(T message); + } + + public class MessageBus : IMessageBus + { + public static IMessageBus Instance { get; set; } private readonly IReceiverMarker[] _receivers; private readonly ILogger _logger; diff --git a/Wabbajack.App/Models/LoadingLock.cs b/Wabbajack.App/Models/LoadingLock.cs new file mode 100644 index 00000000..3e8bc529 --- /dev/null +++ b/Wabbajack.App/Models/LoadingLock.cs @@ -0,0 +1,45 @@ +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; + +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) + .DisposeWith(_disposable); + + } + + public IDisposable WithLoading() + { + Dispatcher.UIThread.Post(() => { LoadLevel++;}, DispatcherPriority.Background); + return Disposable.Create(() => + { + Dispatcher.UIThread.Post(() => { LoadLevel--;}, DispatcherPriority.Background); + }); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _disposable.Dispose(); + } +} \ No newline at end of file diff --git a/Wabbajack.App/Screens/BrowseViewModel.cs b/Wabbajack.App/Screens/BrowseViewModel.cs index 749d68c4..5fc04c47 100644 --- a/Wabbajack.App/Screens/BrowseViewModel.cs +++ b/Wabbajack.App/Screens/BrowseViewModel.cs @@ -23,6 +23,7 @@ using Wabbajack.DTOs; using Wabbajack.Networking.WabbajackClientApi; using DynamicData.Binding; using Microsoft.Extensions.DependencyInjection; +using Wabbajack.App.Models; using Wabbajack.Downloaders; using Wabbajack.Downloaders.GameFile; using Wabbajack.DTOs.JsonConverters; @@ -68,9 +69,15 @@ namespace Wabbajack.App.Screens [Reactive] public bool ShowNSFW { get; set; } = false; + [Reactive] public bool IsLoading { get; set; } = false; + + [Reactive] + public LoadingLock LoadingLock { get; set; } + public BrowseViewModel(ILogger logger, Client wjClient, HttpClient httpClient, IResource limiter, FileHashCache hashCache, IResource dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator, DTOSerializer dtos, Configuration configuration) { + LoadingLock = new LoadingLock(); Activator = new ViewModelActivator(); _wjClient = wjClient; _logger = logger; @@ -187,11 +194,13 @@ namespace Wabbajack.App.Screens }); } + [Reactive] public ReactiveCommand ResetFiltersCommand { get; set; } private async Task LoadData() { + using var _ = LoadingLock.WithLoading(); var modlists = await _wjClient.LoadLists(); var summaries = (await _wjClient.GetListStatuses()).ToDictionary(m => m.MachineURL); var vms = modlists.Select(m => @@ -215,6 +224,7 @@ namespace Wabbajack.App.Screens private async Task LoadSettings() { + using var _ = LoadingLock.WithLoading(); try { if (SavedSettingsLocation.FileExists()) diff --git a/Wabbajack.sln b/Wabbajack.sln index f7f3c5e0..c1ef69d9 100644 --- a/Wabbajack.sln +++ b/Wabbajack.sln @@ -116,6 +116,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Downloaders.GameF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Launcher", "Wabbajack.Launcher\Wabbajack.Launcher.csproj", "{23D49FCC-A6CB-4873-879B-F90DA1871AA3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.App.Test", "Wabbajack.App.Test\Wabbajack.App.Test.csproj", "{05E7E8BB-1F44-4E3D-8443-110FD237AA92}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -322,6 +324,10 @@ Global {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {23D49FCC-A6CB-4873-879B-F90DA1871AA3}.Release|Any CPU.Build.0 = Release|Any CPU + {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05E7E8BB-1F44-4E3D-8443-110FD237AA92}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4057B668-8595-44FE-9805-007B284A838F} = {98B731EE-4FC0-4482-A069-BCBA25497871}