diff --git a/Wabbajack.App/Configuration.cs b/Wabbajack.App/Configuration.cs index 57d88f29..5559d8cd 100644 --- a/Wabbajack.App/Configuration.cs +++ b/Wabbajack.App/Configuration.cs @@ -6,8 +6,8 @@ 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 ImageCacheLocation { get; set; } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/BrowseItemView.axaml.cs b/Wabbajack.App/Controls/BrowseItemView.axaml.cs index 3a29a307..fdf7b84a 100644 --- a/Wabbajack.App/Controls/BrowseItemView.axaml.cs +++ b/Wabbajack.App/Controls/BrowseItemView.axaml.cs @@ -42,6 +42,7 @@ public partial class BrowseItemView : ReactiveUserControl { return modListState switch { + ModListState.Disabled => MaterialIconKind.Error, ModListState.Downloaded => MaterialIconKind.PlayArrow, ModListState.Downloading => MaterialIconKind.LocalAreaNetworkPending, ModListState.NotDownloaded => MaterialIconKind.Download, diff --git a/Wabbajack.App/Controls/BrowseItemViewModel.cs b/Wabbajack.App/Controls/BrowseItemViewModel.cs index 0c5b34a4..bf05346f 100644 --- a/Wabbajack.App/Controls/BrowseItemViewModel.cs +++ b/Wabbajack.App/Controls/BrowseItemViewModel.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using ReactiveUI; using ReactiveUI.Fody.Helpers; using Wabbajack.App.Messages; +using Wabbajack.App.Models; using Wabbajack.App.ViewModels; using Wabbajack.Common; using Wabbajack.Downloaders; @@ -28,7 +29,8 @@ public enum ModListState { Downloaded, NotDownloaded, - Downloading + Downloading, + Disabled } public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel @@ -43,11 +45,12 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel private readonly ILogger _logger; private readonly ModlistMetadata _metadata; private readonly ModListSummary _summary; + private readonly ImageCache _imageCache; public BrowseItemViewModel(ModlistMetadata metadata, ModListSummary summary, HttpClient client, IResource limiter, FileHashCache hashCache, Configuration configuration, DownloadDispatcher dispatcher, - IResource downloadLimiter, GameLocator gameLocator, + IResource downloadLimiter, GameLocator gameLocator, ImageCache imageCache, DTOSerializer dtos, ILogger logger) { Activator = new ViewModelActivator(); @@ -59,6 +62,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel _configuration = configuration; _dispatcher = dispatcher; _downloadLimiter = downloadLimiter; + _imageCache = imageCache; _logger = logger; _dtos = dtos; @@ -87,7 +91,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel } }, this.ObservableForProperty(t => t.State) - .Select(c => c.Value != ModListState.Downloading) + .Select(c => c.Value != ModListState.Downloading && c.Value != ModListState.Disabled) .StartWith(true)); LoadListImage().FireAndForget(); @@ -96,7 +100,7 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel public string Title => _metadata.ImageContainsTitle ? "" : _metadata.Title; public string MachineURL => _metadata.Links.MachineURL; - public string Description => _metadata.Description; + public string Description => State == ModListState.Disabled ? "Disabled: Under Construction \n " + _metadata.Description : _metadata.Description; public Uri ImageUri => new(_metadata.Links.ImageUri); @@ -163,13 +167,14 @@ public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel 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)); + Image = await _imageCache.From(ImageUri); } public async Task GetState() { + if (_metadata.ForceDown || _summary.HasFailures) + return ModListState.Disabled; + var file = ModListLocation; if (!file.FileExists()) return ModListState.NotDownloaded; diff --git a/Wabbajack.App/Models/ImageCache.cs b/Wabbajack.App/Models/ImageCache.cs new file mode 100644 index 00000000..ac11adc3 --- /dev/null +++ b/Wabbajack.App/Models/ImageCache.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using SkiaSharp; +using Wabbajack.Hashing.xxHash64; +using Wabbajack.Paths.IO; +using Wabbajack.RateLimiter; + +namespace Wabbajack.App.Models; + +public class ImageCache +{ + private readonly Configuration _configuration; + private readonly HttpClient _client; + private readonly IResource _limiter; + + public ImageCache(Configuration configuration, HttpClient client, IResource limiter) + { + _configuration = configuration; + _configuration.ImageCacheLocation.CreateDirectory(); + _client = client; + _limiter = limiter; + } + + public async Task From(Uri uri) + { + var hash = (await Encoding.UTF8.GetBytes(uri.ToString()).Hash()).ToHex(); + var file = _configuration.ImageCacheLocation.Combine(hash); + if (!file.FileExists()) + { + using var job = await _limiter.Begin("Loading Image", 0, CancellationToken.None); + var wdata = await _client.GetByteArrayAsync(uri); + await file.WriteAllBytesAsync(wdata); + return new Bitmap(new MemoryStream(wdata)); + } + + var data = await file.ReadAllBytesAsync(); + return new Bitmap(new MemoryStream(data)); + } + +} \ No newline at end of file diff --git a/Wabbajack.App/Screens/BrowseView.axaml b/Wabbajack.App/Screens/BrowseView.axaml index 5ff7419c..2bd82ddc 100644 --- a/Wabbajack.App/Screens/BrowseView.axaml +++ b/Wabbajack.App/Screens/BrowseView.axaml @@ -63,18 +63,16 @@ - - - - - - - + + + + + - - + + diff --git a/Wabbajack.App/Screens/BrowseViewModel.cs b/Wabbajack.App/Screens/BrowseViewModel.cs index 9afd705b..04c51879 100644 --- a/Wabbajack.App/Screens/BrowseViewModel.cs +++ b/Wabbajack.App/Screens/BrowseViewModel.cs @@ -48,10 +48,12 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel private readonly SourceCache _gamesList = new(x => x.Name); private readonly SourceCache _modLists = new(x => x.MachineURL); + private readonly ImageCache _imageCache; public BrowseViewModel(ILogger logger, Client wjClient, HttpClient httpClient, IResource limiter, FileHashCache hashCache, IResource dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator, + ImageCache imageCache, DTOSerializer dtos, Configuration configuration) { LoadingLock = new LoadingLock(); @@ -65,6 +67,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel _dispatcher = dispatcher; _dispatcherLimiter = dispatcherLimiter; _gameLocator = gameLocator; + _imageCache = imageCache; _dtos = dtos; @@ -131,6 +134,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel .Filter(onlyInstalledGamesFilter) .Filter(onlyUtilityListsFilter) .Filter(showNSFWFilter) + .SortBy(x => x.State == ModListState.Disabled ? 1 : 0) .Bind(out _filteredModLists) .Subscribe(); @@ -191,7 +195,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel if (!summaries.TryGetValue(m.Links.MachineURL, out var summary)) summary = new ModListSummary(); return new BrowseItemViewModel(m, summary, _httpClient, _limiter, _hashCache, _configuration, _dispatcher, - _dispatcherLimiter, _gameLocator, _dtos, _logger); + _dispatcherLimiter, _gameLocator, _imageCache, _dtos, _logger); }); _modLists.Edit(lsts => diff --git a/Wabbajack.App/ServiceExtensions.cs b/Wabbajack.App/ServiceExtensions.cs index 68fe4ae7..c50ab48c 100644 --- a/Wabbajack.App/ServiceExtensions.cs +++ b/Wabbajack.App/ServiceExtensions.cs @@ -57,6 +57,7 @@ public static class ServiceExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -97,7 +98,8 @@ public static class ServiceExtensions EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"), - LogLocation = KnownFolders.EntryPoint.Combine("logs") + LogLocation = KnownFolders.EntryPoint.Combine("logs"), + ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache") }); services.AddSingleton();