mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
[skip ci] image cache
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user