From a235e0bd574dedf0f21794b13ae91c5ee224ebf1 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 16 Dec 2024 15:21:48 +0100 Subject: [PATCH] Remove IImageCache --- src/NexusMods.App.UI/IImageCache.cs | 34 ----- src/NexusMods.App.UI/ImageCache.cs | 143 ------------------- src/NexusMods.App.UI/NexusMods.App.UI.csproj | 3 - src/NexusMods.App.UI/Services.cs | 1 - tests/NexusMods.UI.Tests/ImageCacheTests.cs | 80 ----------- 5 files changed, 261 deletions(-) delete mode 100644 src/NexusMods.App.UI/IImageCache.cs delete mode 100644 src/NexusMods.App.UI/ImageCache.cs delete mode 100644 tests/NexusMods.UI.Tests/ImageCacheTests.cs diff --git a/src/NexusMods.App.UI/IImageCache.cs b/src/NexusMods.App.UI/IImageCache.cs deleted file mode 100644 index 4bdfd24baf..0000000000 --- a/src/NexusMods.App.UI/IImageCache.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Avalonia.Media; -using JetBrains.Annotations; -using NexusMods.Hashing.xxHash3; -using OneOf; - -namespace NexusMods.App.UI; - -/// -/// Represents an image cache. -/// -[PublicAPI] -[Obsolete("To be replaced with resource pipelines")] -public interface IImageCache : IDisposable -{ - /// - /// Gets an image from cache or loads the image. - /// - Task GetImage(ImageIdentifier imageIdentifier, CancellationToken cancellationToken); - - /// - /// Prefetches the provided image. - /// - Task Prefetch(ImageIdentifier imageIdentifier, CancellationToken cancellationToken); -} - -public readonly struct ImageIdentifier -{ - public readonly OneOf Union; - - public ImageIdentifier(OneOf union) - { - Union = union; - } -} diff --git a/src/NexusMods.App.UI/ImageCache.cs b/src/NexusMods.App.UI/ImageCache.cs deleted file mode 100644 index db66babcfe..0000000000 --- a/src/NexusMods.App.UI/ImageCache.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Xml; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Svg.Skia; -using Microsoft.Extensions.Logging; -using NexusMods.Abstractions.IO; -using NexusMods.Hashing.xxHash3; -using Svg.Model; - -namespace NexusMods.App.UI; - -internal sealed class ImageCache : IImageCache -{ - private readonly ILogger _logger; - private readonly IFileStore _fileStore; - private readonly HttpClient _client; - - private readonly Dictionary _cache = new(); - - public ImageCache( - ILogger logger, - IFileStore fileStore, - HttpClient client) - { - _logger = logger; - _fileStore = fileStore; - _client = client; - } - - public async Task GetImage(ImageIdentifier imageIdentifier, CancellationToken cancellationToken) - { - var hash = await Prefetch(imageIdentifier, cancellationToken); - return hash == Hash.Zero ? null : _cache.GetValueOrDefault(hash); - } - - public async Task Prefetch( - ImageIdentifier imageIdentifier, - CancellationToken cancellationToken) - { - var hash = GetHash(imageIdentifier); - if (_cache.TryGetValue(hash, out _)) return hash; - - var image = await Load(imageIdentifier, cancellationToken); - if (image is null) return Hash.Zero; - - _cache.TryAdd(hash, image); - return hash; - } - - private static Hash GetHash(ImageIdentifier imageIdentifier) - { - return imageIdentifier.Union.Match( - f0: uri => uri.ToString().xxHash3AsUtf8(), - f1: hash => hash - ); - } - - private Task Load(ImageIdentifier imageIdentifier, CancellationToken cancellationToken) - { - return imageIdentifier.Union.Match( - f0: uri => LoadFromUri(uri, cancellationToken), - f1: hash => LoadFromHash(hash, cancellationToken) - ); - } - - private async Task LoadFromUri(Uri uri, CancellationToken cancellationToken) - { - try - { - _logger.LogDebug("Fetching image from {Uri}", uri); - var bytes = await _client.GetByteArrayAsync(uri, cancellationToken); - var stream = new MemoryStream(bytes); - return StreamToImage(stream); - } - catch (Exception e) - { - _logger.LogError(e, "Exception while loading image from {Uri}", uri); - return null; - } - } - - private async Task LoadFromHash( - Hash hash, - CancellationToken cancellationToken) - { - try - { - await using var stream = await _fileStore.GetFileStream(hash, cancellationToken); - return StreamToImage(stream); - } - catch (Exception e) - { - _logger.LogError(e, "Exception while loading image from file store with hash {Hash}", hash); - return null; - } - } - - private static IImage? StreamToImage(Stream stream) - { - return IsSvg(stream) ? FromSvg(stream) : new Bitmap(stream); - } - - private static SvgImage? FromSvg(Stream stream) - { - var source = SvgSource.LoadFromStream(stream); - var image = new SvgImage - { - Source = source, - }; - - return image; - } - - private static bool IsSvg(Stream stream) - { - try - { - var firstByte = stream.ReadByte(); - if (firstByte != ('<' & 0xFF)) return false; - - stream.Seek(0, SeekOrigin.Begin); - using var xmlReader = XmlReader.Create(stream); - return xmlReader.MoveToContent() == XmlNodeType.Element && "svg".Equals(xmlReader.Name, StringComparison.OrdinalIgnoreCase); - } - catch - { - return false; - } - finally - { - stream.Seek(0, SeekOrigin.Begin); - } - } - - public void Dispose() - { - foreach (var kv in _cache) - { - var value = kv.Value; - if (value is IDisposable disposable) disposable.Dispose(); - } - } -} diff --git a/src/NexusMods.App.UI/NexusMods.App.UI.csproj b/src/NexusMods.App.UI/NexusMods.App.UI.csproj index bc0bb86454..88be91e768 100644 --- a/src/NexusMods.App.UI/NexusMods.App.UI.csproj +++ b/src/NexusMods.App.UI/NexusMods.App.UI.csproj @@ -512,9 +512,6 @@ INewTabPageSectionViewModel.cs - - IImageCache.cs - ITextEditorPageViewModel.cs diff --git a/src/NexusMods.App.UI/Services.cs b/src/NexusMods.App.UI/Services.cs index 0e118eb980..e67159855b 100644 --- a/src/NexusMods.App.UI/Services.cs +++ b/src/NexusMods.App.UI/Services.cs @@ -89,7 +89,6 @@ public static IServiceCollection AddUI(this IServiceCollection c) // Services .AddSingleton() - .AddTransient() // View Models .AddTransient() diff --git a/tests/NexusMods.UI.Tests/ImageCacheTests.cs b/tests/NexusMods.UI.Tests/ImageCacheTests.cs deleted file mode 100644 index fc06839bbb..0000000000 --- a/tests/NexusMods.UI.Tests/ImageCacheTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using FluentAssertions; -using Microsoft.Extensions.DependencyInjection; -using NexusMods.Abstractions.IO; -using NexusMods.Abstractions.IO.StreamFactories; -using NexusMods.App.UI; -using NexusMods.Hashing.xxHash3; -using NexusMods.Paths; -using NexusMods.Paths.Extensions; - -namespace NexusMods.UI.Tests; - -// NOTE(erri120): This inherits from AUiTest because the Avalonia -// Bitmap class requires Avalonia to be initialized beforehand. -public class ImageCacheTests : AUiTest -{ - private readonly IServiceProvider _serviceProvider; - - public ImageCacheTests(IServiceProvider serviceProvider) : base(serviceProvider) - { - _serviceProvider = serviceProvider; - } - - [Fact] - public async Task Test_LoadAndCache_RemoteImage() - { - const string url = "https://http.cat/418.jpg"; - var uri = new Uri(url); - - using var scope = _serviceProvider.CreateScope(); - using var imageCache = scope.ServiceProvider.GetRequiredService(); - - var image1 = await imageCache.GetImage(new ImageIdentifier(uri), cancellationToken: default); - image1.Should().NotBeNull(); - - var image2 = await imageCache.GetImage(new ImageIdentifier(uri), cancellationToken: default); - image2.Should().NotBeNull(); - - image1.Should().BeSameAs(image2); - } - - [Fact] - public async Task Test_LoadAndCache_ImageStoredFile() - { - var hash = await PrepareImage(); - - using var scope = _serviceProvider.CreateScope(); - using var imageCache = scope.ServiceProvider.GetRequiredService(); - - var image1 = await imageCache.GetImage(new ImageIdentifier(hash), cancellationToken: default); - image1.Should().NotBeNull(); - - var image2 = await imageCache.GetImage(new ImageIdentifier(hash), cancellationToken: default); - image2.Should().NotBeNull(); - - image1.Should().BeSameAs(image2); - } - - private async Task PrepareImage() - { - var archiveManager = _serviceProvider.GetRequiredService(); - - const string url = "https://http.cat/418.jpg"; - var httpClient = new HttpClient(); - var bytes = await httpClient.GetByteArrayAsync(url); - - var hash = bytes.AsSpan().xxHash3(); - var size = Size.FromLong(bytes.LongLength); - var streamFactory = new MemoryStreamFactory("cat.jpg".ToRelativePath(), new MemoryStream(bytes)); - - await archiveManager.BackupFiles(new ArchivedFileEntry[] - { - new(streamFactory, hash, size) - }); - - var hasFile = await archiveManager.HaveFile(hash); - hasFile.Should().BeTrue(); - - return hash; - } -}