From 81c5acb742e369d6ce4ee13223071e71a010d48e Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 4 Jul 2024 17:31:03 +0800 Subject: [PATCH] [skip ci] image cache --- .../Snap.Hutao/Core/Caching/IImageCache.cs | 21 +-- .../Snap.Hutao/Core/Caching/ImageCache.cs | 145 ++++++++++++++---- .../Threading/ScopedTaskCompletionSource.cs | 16 ++ .../BackgroundImage/BackgroundImageService.cs | 2 +- src/Snap.Hutao/Snap.Hutao/UI/Bgra32.cs | 2 +- 5 files changed, 135 insertions(+), 51 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/ScopedTaskCompletionSource.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs index 51800703..938dc3da 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/IImageCache.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml; using Snap.Hutao.Core.IO; namespace Snap.Hutao.Core.Caching; @@ -11,27 +12,11 @@ namespace Snap.Hutao.Core.Caching; [HighQuality] internal interface IImageCache { - /// - /// Gets the file path containing cached item for given Uri - /// - /// Uri of the item. - /// a string path ValueTask GetFileFromCacheAsync(Uri uri); - /// - /// Removed items based on uri list passed - /// - /// Enumerable uri list + ValueTask GetFileFromCacheAsync(Uri uri, ElementTheme theme); + void Remove(in ReadOnlySpan uriForCachedItems); - /// - /// Removed item based on uri passed - /// - /// uri void Remove(Uri uriForCachedItem); - - /// - /// Removes invalid cached files - /// - void RemoveInvalid(); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 9c939791..0fe317a7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -2,19 +2,28 @@ // Licensed under the MIT license. using Microsoft.Extensions.Caching.Memory; +using Microsoft.UI.Xaml; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Core.Logging; +using Snap.Hutao.UI; using Snap.Hutao.ViewModel.Guide; +using Snap.Hutao.Web; using Snap.Hutao.Web.Request.Builder; using Snap.Hutao.Web.Request.Builder.Abstraction; +using Snap.Hutao.Win32.System.WinRT; using System.Collections.Concurrent; using System.Collections.Frozen; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; +using Windows.ApplicationModel.Appointments; +using Windows.Foundation; +using Windows.Graphics.Imaging; +using WinRT; namespace Snap.Hutao.Core.Caching; @@ -34,7 +43,8 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera KeyValuePair.Create(2, TimeSpan.FromSeconds(64)), ]); - private readonly ConcurrentDictionary concurrentTasks = new(); + private readonly ConcurrentDictionary themefileTasks = []; + private readonly ConcurrentDictionary downloadTasks = []; private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory; private readonly IHttpClientFactory httpClientFactory; @@ -52,19 +62,11 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera }); } - /// - public void RemoveInvalid() - { - RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false))); - } - - /// public void Remove(Uri uriForCachedItem) { Remove([uriForCachedItem]); } - /// public void Remove(in ReadOnlySpan uriForCachedItems) { if (uriForCachedItems.Length <= 0) @@ -88,39 +90,78 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera RemoveCore(filesToDelete); } - /// - public async ValueTask GetFileFromCacheAsync(Uri uri) + public ValueTask GetFileFromCacheAsync(Uri uri) + { + return GetFileFromCacheAsync(uri, ElementTheme.Default); + } + + public async ValueTask GetFileFromCacheAsync(Uri uri, ElementTheme theme) { string fileName = GetCacheFileName(uri); - string filePath = Path.Combine(CacheFolder, fileName); - if (!IsFileInvalid(filePath)) + using (ScopedTaskCompletionSource themeFileScope = new()) { - return filePath; - } - - TaskCompletionSource taskCompletionSource = new(); - try - { - if (concurrentTasks.TryAdd(fileName, taskCompletionSource.Task)) + ElementThemeValueFile key = new(fileName, theme); + if (themefileTasks.TryAdd(key, themeFileScope.Task)) { - logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (filePath, ConsoleColor.Cyan)); - await DownloadFileAsync(uri, filePath).ConfigureAwait(false); + try + { + string defaultFilePath = Path.Combine(CacheFolder, fileName); + string themeOrDefaultFilePath = theme is ElementTheme.Dark or ElementTheme.Light ? Path.Combine(CacheFolder, $"{theme}", fileName) : defaultFilePath; + + if (!IsFileInvalid(themeOrDefaultFilePath)) + { + return themeOrDefaultFilePath; + } + + if (!IsFileInvalid(defaultFilePath)) + { + await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false); + return themeOrDefaultFilePath; + } + + using (ScopedTaskCompletionSource downloadScope = new()) + { + if (downloadTasks.TryAdd(fileName, downloadScope.Task)) + { + try + { + logger.LogColorizedInformation("Begin to download file from '{Uri}' to '{File}'", (uri, ConsoleColor.Cyan), (defaultFilePath, ConsoleColor.Cyan)); + await DownloadFileAsync(uri, defaultFilePath).ConfigureAwait(false); + } + finally + { + downloadTasks.TryRemove(fileName, out _); + } + } + else if (downloadTasks.TryGetValue(fileName, out Task? task)) + { + logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan)); + await task.ConfigureAwait(false); + } + } + + if (!IsFileInvalid(defaultFilePath)) + { + await ConvertAndSaveFileToMonoChromeAsync(defaultFilePath, themeOrDefaultFilePath, theme).ConfigureAwait(false); + return themeOrDefaultFilePath; + } + + return themeOrDefaultFilePath; + } + finally + { + themefileTasks.TryRemove(key, out _); + } } - else if (concurrentTasks.TryGetValue(fileName, out Task? task)) + else if (themefileTasks.TryGetValue(key, out Task? task)) { - logger.LogDebug("Waiting for a queued image download task to complete for '{Uri}'", (uri, ConsoleColor.Cyan)); await task.ConfigureAwait(false); + return key.File; } - - concurrentTasks.TryRemove(fileName, out _); - } - finally - { - taskCompletionSource.TrySetResult(); } - return filePath; + throw HutaoException.NotSupported("The task should not be null."); } /// @@ -236,4 +277,46 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera HashSet? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => new HashSet()); set?.Add(uri.ToString()); } + + private static async ValueTask ConvertAndSaveFileToMonoChromeAsync(string sourceFile, string themeFile, ElementTheme theme) + { + if (string.Equals(sourceFile, themeFile, StringComparison.OrdinalIgnoreCase)) + { + return; + } + + using (FileStream sourceStream = File.OpenRead(sourceFile)) + { + BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream.AsRandomAccessStream()); + using (SoftwareBitmap sourceBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied)) + { + using (BitmapBuffer sourceBuffer = sourceBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite)) + { + using (IMemoryBufferReference reference = sourceBuffer.CreateReference()) + { + IMemoryBufferByteAccess byteAccess = reference.As(); + // byteAccess.GetBuffer(out Span span); + } + } + } + } + + } + + private readonly struct ElementThemeValueFile + { + public readonly ValueFile File; + public readonly ElementTheme Theme; + + public ElementThemeValueFile(ValueFile file, ElementTheme theme) + { + File = file; + Theme = theme; + } + + public override int GetHashCode() + { + return HashCode.Combine(File, Theme); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ScopedTaskCompletionSource.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ScopedTaskCompletionSource.cs new file mode 100644 index 00000000..d6a4bf3e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ScopedTaskCompletionSource.cs @@ -0,0 +1,16 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Threading; + +internal sealed class ScopedTaskCompletionSource : IDisposable +{ + private readonly TaskCompletionSource tcs = new(); + + public Task Task { get => tcs.Task; } + + public void Dispose() + { + tcs.TrySetResult(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs index 02def2e5..66b24c8a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs @@ -73,7 +73,7 @@ internal sealed partial class BackgroundImageService : IBackgroundImageService Path = path, ImageSource = new(path.ToUri()), AccentColor = accentColor, - Luminance = accentColor.Brightness, + Luminance = accentColor.Luminance, }; return new(true, background); diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Bgra32.cs b/src/Snap.Hutao/Snap.Hutao/UI/Bgra32.cs index 73f30a30..061a433c 100644 --- a/src/Snap.Hutao/Snap.Hutao/UI/Bgra32.cs +++ b/src/Snap.Hutao/Snap.Hutao/UI/Bgra32.cs @@ -21,7 +21,7 @@ internal struct Bgra32 A = a; } - public readonly double Brightness { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; } + public readonly double Luminance { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; } public static unsafe implicit operator Bgra32(Color color) {