diff --git a/src/NexusMods.App.UI/Controls/MarkdownRenderer/MarkdownRendererViewModel.cs b/src/NexusMods.App.UI/Controls/MarkdownRenderer/MarkdownRendererViewModel.cs index 3efcd5247e..22ffaa3077 100644 --- a/src/NexusMods.App.UI/Controls/MarkdownRenderer/MarkdownRendererViewModel.cs +++ b/src/NexusMods.App.UI/Controls/MarkdownRenderer/MarkdownRendererViewModel.cs @@ -1,15 +1,17 @@ using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Text; using Avalonia.Media; +using Avalonia.Media.Imaging; using JetBrains.Annotations; using Markdown.Avalonia.Plugins; using Markdown.Avalonia.Utils; using Microsoft.Extensions.Logging; +using NexusMods.Abstractions.Resources; using NexusMods.Abstractions.UI; using NexusMods.App.UI.Extensions; using NexusMods.CrossPlatform.Process; -using NexusMods.Hashing.xxHash3; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -19,8 +21,8 @@ namespace NexusMods.App.UI.Controls.MarkdownRenderer; public class MarkdownRendererViewModel : AViewModel, IMarkdownRendererViewModel { private readonly ILogger _logger; - private readonly IImageCache _imageCache; private readonly HttpClient _httpClient; + private readonly IResourceLoader _remoteImagePipeline; [Reactive] public string Contents { get; set; } = string.Empty; [Reactive] public Uri? MarkdownUri { get; set; } @@ -31,14 +33,14 @@ public class MarkdownRendererViewModel : AViewModel, public ReactiveCommand OpenLinkCommand { get; } public MarkdownRendererViewModel( + IServiceProvider serviceProvider, ILogger logger, IOSInterop osInterop, - IImageCache imageCache, HttpClient httpClient) { _logger = logger; - _imageCache = imageCache; _httpClient = httpClient; + _remoteImagePipeline = ImagePipelines.GetMarkdownRendererRemoteImagePipeline(serviceProvider); PathResolver = new PathResolverImpl(this); ImageResolverPlugin = new ImageResolvePluginImpl(new ImageResolverImpl(this)); @@ -120,14 +122,11 @@ private async Task FetchMarkdown(Uri uri, CancellationToken cancellation return await FetchRemoteImage(uri, cancellationToken); } - private async Task FetchRemoteImage(Uri uri, CancellationToken cancellationToken = default) + private Task FetchRemoteImage(Uri uri, CancellationToken cancellationToken = default) { - var hash = await _imageCache.Prefetch(new ImageIdentifier(uri), cancellationToken); - var hashValue = hash.Value; - - var bytes = BitConverter.GetBytes(hashValue); + var bytes = Encoding.UTF8.GetBytes(uri.ToString()); var ms = new MemoryStream(bytes, writable: false); - return ms; + return Task.FromResult(ms); } private class ImageResolvePluginImpl : IMdAvPlugin @@ -191,14 +190,12 @@ public ImageResolverImpl(MarkdownRendererViewModel parent) public async Task Load(Stream stream) { - var bytes = GC.AllocateUninitializedArray(sizeof(ulong)); - stream.ReadExactly(bytes); - - var hashValue = BitConverter.ToUInt64(bytes); - var hash = Hash.FromULong(hashValue); + using var sr = new StreamReader(stream, Encoding.UTF8); + var url = await sr.ReadToEndAsync(); + var uri = new Uri(url, UriKind.Absolute); - var image = await _parent._imageCache.GetImage(new ImageIdentifier(hash), CancellationToken.None); - return image; + var resource = await _parent._remoteImagePipeline.LoadResourceAsync(uri, CancellationToken.None); + return resource.Data; } } } diff --git a/src/NexusMods.App.UI/ImagePipelines.cs b/src/NexusMods.App.UI/ImagePipelines.cs index e5a87fc68e..9884693bf5 100644 --- a/src/NexusMods.App.UI/ImagePipelines.cs +++ b/src/NexusMods.App.UI/ImagePipelines.cs @@ -25,6 +25,7 @@ public static class ImagePipelines private const string UserAvatarPipelineKey = nameof(UserAvatarPipelineKey); private const string GuidedInstallerRemoteImagePipelineKey = nameof(GuidedInstallerRemoteImagePipelineKey); private const string GuidedInstallerFileImagePipelineKey = nameof(GuidedInstallerFileImagePipelineKey); + private const string MarkdownRendererRemoteImagePipelineKey = nameof(MarkdownRendererRemoteImagePipelineKey); private static readonly Bitmap CollectionTileFallback = new(AssetLoader.Open(new Uri("avares://NexusMods.App.UI/Assets/collection-tile-fallback.png"))); private static readonly Bitmap CollectionBackgroundFallback = new(AssetLoader.Open(new Uri("avares://NexusMods.App.UI/Assets/black-box.png"))); @@ -74,6 +75,12 @@ public static IServiceCollection AddImagePipelines(this IServiceCollection servi implementationFactory: static (serviceProvider, _) => CreateGuidedInstallerFileImagePipeline( fileStore: serviceProvider.GetRequiredService() ) + ) + .AddKeyedSingleton>( + serviceKey: MarkdownRendererRemoteImagePipelineKey, + implementationFactory: static (serviceProvider, _) => CreateMarkdownRendererRemoteImagePipeline( + httpClient: serviceProvider.GetRequiredService() + ) ); } @@ -102,6 +109,11 @@ public static IResourceLoader> GetGuidedInstallerFileImag return serviceProvider.GetRequiredKeyedService>>(serviceKey: GuidedInstallerFileImagePipelineKey); } + public static IResourceLoader GetMarkdownRendererRemoteImagePipeline(IServiceProvider serviceProvider) + { + return serviceProvider.GetRequiredKeyedService>(serviceKey: MarkdownRendererRemoteImagePipelineKey); + } + private static IResourceLoader CreateUserAvatarPipeline( HttpClient httpClient, IConnection connection) @@ -185,6 +197,15 @@ private static IResourceLoader> CreateGuidedInstallerRemot return pipeline; } + private static IResourceLoader CreateMarkdownRendererRemoteImagePipeline(HttpClient httpClient) + { + var pipeline = new HttpLoader(httpClient) + .Decode(decoderType: DecoderType.Skia) + .ToAvaloniaBitmap(); + + return pipeline; + } + private static IResourceLoader> CreateGuidedInstallerFileImagePipeline(IFileStore fileStore) { var pipeline = new FileStoreLoader(fileStore)