[skip ci] image cache

This commit is contained in:
DismissedLight
2024-07-04 17:31:03 +08:00
parent 2a6b386c2c
commit 81c5acb742
5 changed files with 135 additions and 51 deletions

View File

@@ -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
{
/// <summary>
/// Gets the file path containing cached item for given Uri
/// </summary>
/// <param name="uri">Uri of the item.</param>
/// <returns>a string path</returns>
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri);
/// <summary>
/// Removed items based on uri list passed
/// </summary>
/// <param name="uriForCachedItems">Enumerable uri list</param>
ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri, ElementTheme theme);
void Remove(in ReadOnlySpan<Uri> uriForCachedItems);
/// <summary>
/// Removed item based on uri passed
/// </summary>
/// <param name="uriForCachedItem">uri</param>
void Remove(Uri uriForCachedItem);
/// <summary>
/// Removes invalid cached files
/// </summary>
void RemoveInvalid();
}

View File

@@ -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<string, Task> concurrentTasks = new();
private readonly ConcurrentDictionary<ElementThemeValueFile, Task> themefileTasks = [];
private readonly ConcurrentDictionary<string, Task> downloadTasks = [];
private readonly IHttpRequestMessageBuilderFactory httpRequestMessageBuilderFactory;
private readonly IHttpClientFactory httpClientFactory;
@@ -52,19 +62,11 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
});
}
/// <inheritdoc/>
public void RemoveInvalid()
{
RemoveCore(Directory.GetFiles(CacheFolder).Where(file => IsFileInvalid(file, false)));
}
/// <inheritdoc/>
public void Remove(Uri uriForCachedItem)
{
Remove([uriForCachedItem]);
}
/// <inheritdoc/>
public void Remove(in ReadOnlySpan<Uri> uriForCachedItems)
{
if (uriForCachedItems.Length <= 0)
@@ -88,39 +90,78 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
RemoveCore(filesToDelete);
}
/// <inheritdoc/>
public async ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
public ValueTask<ValueFile> GetFileFromCacheAsync(Uri uri)
{
return GetFileFromCacheAsync(uri, ElementTheme.Default);
}
public async ValueTask<ValueFile> 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.");
}
/// <inheritdoc/>
@@ -236,4 +277,46 @@ internal sealed partial class ImageCache : IImageCache, IImageCacheFilePathOpera
HashSet<string>? set = memoryCache.GetOrCreate(CacheFailedDownloadTasksName, entry => new HashSet<string>());
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<IMemoryBufferByteAccess>();
// byteAccess.GetBuffer(out Span<Rgba32> 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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

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