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)
{