using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Avalonia.Media.Imaging; using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; namespace AuroraDesk.Presentation.Services; /// /// 缩略图缓存服务(类似 Windows 11 的 Thumbnail Cache) /// 核心策略: /// 1. 内存缓存:快速访问最近使用的缩略图 /// 2. 磁盘缓存:持久化存储,避免重复生成 /// 3. 按需生成:只在需要时生成缩略图 /// 4. 异步加载:不阻塞 UI 线程 /// public class ThumbnailCacheService { private static ThumbnailCacheService? _instance; private static readonly object _lockObject = new(); // 内存缓存(LRU 策略,最近使用的缩略图) private readonly ConcurrentDictionary _memoryCache = new(); // 正在加载的任务字典,避免重复加载 private readonly ConcurrentDictionary> _loadingTasks = new(); // 缩略图大小(与 UI 中的 184x184 一致) private const int ThumbnailSize = 184; // 内存缓存最大大小(最多缓存 1000 张缩略图) private const int MaxMemoryCacheSize = 1000; // 磁盘缓存目录 private readonly string _cacheDirectory; // 缓存过期时间(7天) private static readonly TimeSpan CacheExpiration = TimeSpan.FromDays(7); private readonly ILogger? _logger; private ThumbnailCacheService(ILogger? logger = null) { _logger = logger; // 使用系统临时目录 + 应用名称作为缓存目录 var tempPath = Path.GetTempPath(); _cacheDirectory = Path.Combine(tempPath, "AuroraDesk", "ThumbnailCache"); // 确保缓存目录存在 if (!Directory.Exists(_cacheDirectory)) { Directory.CreateDirectory(_cacheDirectory); } // 启动后台清理任务(定期清理过期缓存) _ = Task.Run(CleanupExpiredCacheAsync); } public static ThumbnailCacheService Instance { get { if (_instance == null) { lock (_lockObject) { if (_instance == null) { _instance = new ThumbnailCacheService(); } } } return _instance; } } /// /// 获取缩略图(异步,按需加载) /// public async Task GetThumbnailAsync(string filePath) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return null; } // 检查内存缓存 if (_memoryCache.TryGetValue(filePath, out var cached) && cached.Thumbnail != null) { cached.LastAccessed = DateTime.UtcNow; return cached.Thumbnail; } // 检查是否正在加载 if (_loadingTasks.TryGetValue(filePath, out var loadingTask)) { return await loadingTask; } // 创建新的加载任务 var task = LoadThumbnailInternalAsync(filePath); _loadingTasks.TryAdd(filePath, task); try { var result = await task; // 添加到内存缓存 if (result != null) { AddToMemoryCache(filePath, result); } return result; } finally { _loadingTasks.TryRemove(filePath, out _); } } /// /// 内部加载缩略图方法(检查磁盘缓存,如果没有则生成) /// private async Task LoadThumbnailInternalAsync(string filePath) { try { // 1. 检查磁盘缓存 var cacheFilePath = GetCacheFilePath(filePath); if (File.Exists(cacheFilePath)) { var cacheFileInfo = new FileInfo(cacheFilePath); var originalFileInfo = new FileInfo(filePath); // 检查缓存是否过期(原文件修改时间更新或缓存文件过期) if (cacheFileInfo.LastWriteTime >= originalFileInfo.LastWriteTime && DateTime.UtcNow - cacheFileInfo.LastWriteTimeUtc < CacheExpiration) { try { // 从磁盘缓存加载 await using var cacheStream = File.OpenRead(cacheFilePath); var bitmap = new Bitmap(cacheStream); _logger?.LogDebug("从磁盘缓存加载缩略图: {FilePath}", filePath); return bitmap; } catch (Exception ex) { _logger?.LogWarning(ex, "从磁盘缓存加载缩略图失败: {FilePath}", filePath); // 删除损坏的缓存文件 try { File.Delete(cacheFilePath); } catch { } } } else { // 缓存过期,删除 try { File.Delete(cacheFilePath); } catch { } } } // 2. 生成新的缩略图 return await GenerateThumbnailAsync(filePath); } catch (Exception ex) { _logger?.LogError(ex, "加载缩略图失败: {FilePath}", filePath); return null; } } /// /// 生成缩略图并保存到磁盘缓存 /// private async Task GenerateThumbnailAsync(string filePath) { return await Task.Run(() => { try { // 使用 ImageSharp 加载并生成缩略图 using var image = Image.Load(filePath); // 计算缩略图尺寸(保持宽高比) var sourceWidth = image.Width; var sourceHeight = image.Height; int thumbnailWidth, thumbnailHeight; if (sourceWidth <= ThumbnailSize && sourceHeight <= ThumbnailSize) { // 如果原图小于缩略图尺寸,直接使用原图尺寸 thumbnailWidth = sourceWidth; thumbnailHeight = sourceHeight; } else { // 计算缩放比例,保持宽高比 var scale = Math.Min( (double)ThumbnailSize / sourceWidth, (double)ThumbnailSize / sourceHeight); thumbnailWidth = (int)(sourceWidth * scale); thumbnailHeight = (int)(sourceHeight * scale); } // 生成缩略图 image.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(thumbnailWidth, thumbnailHeight), Mode = ResizeMode.Max, // 保持宽高比,确保不超过指定尺寸 Sampler = KnownResamplers.Lanczos3 // 高质量缩放算法 })); // 将 ImageSharp 图像转换为 Avalonia Bitmap using var memoryStream = new MemoryStream(); image.SaveAsPng(memoryStream); memoryStream.Position = 0; var bitmap = new Bitmap(memoryStream); // 保存到磁盘缓存(异步,不阻塞) _ = SaveToDiskCacheAsync(filePath, memoryStream); return bitmap; } catch (Exception ex) { _logger?.LogError(ex, "生成缩略图失败: {FilePath}", filePath); return null; } }); } /// /// 保存缩略图到磁盘缓存 /// private async Task SaveToDiskCacheAsync(string filePath, MemoryStream thumbnailStream) { try { var cacheFilePath = GetCacheFilePath(filePath); var cacheDir = Path.GetDirectoryName(cacheFilePath); if (cacheDir != null && !Directory.Exists(cacheDir)) { Directory.CreateDirectory(cacheDir); } // 复制流到文件 await using var fileStream = File.Create(cacheFilePath); thumbnailStream.Position = 0; await thumbnailStream.CopyToAsync(fileStream); _logger?.LogDebug("缩略图已保存到磁盘缓存: {FilePath}", filePath); } catch (Exception ex) { _logger?.LogWarning(ex, "保存缩略图到磁盘缓存失败: {FilePath}", filePath); } } /// /// 获取缓存文件路径(基于文件路径的哈希值) /// private string GetCacheFilePath(string filePath) { // 使用文件路径的哈希值作为缓存文件名 var hash = ComputeHash(filePath); var extension = Path.GetExtension(filePath).ToLowerInvariant(); var cacheFileName = $"{hash}{extension}"; // 使用子目录分散文件(避免单目录文件过多) var subDir = hash.Substring(0, 2); return Path.Combine(_cacheDirectory, subDir, cacheFileName); } /// /// 计算文件路径的哈希值 /// private string ComputeHash(string filePath) { var bytes = Encoding.UTF8.GetBytes(filePath); using var sha256 = SHA256.Create(); var hashBytes = sha256.ComputeHash(bytes); return BitConverter.ToString(hashBytes).Replace("-", ""); } /// /// 添加到内存缓存(LRU 策略) /// private void AddToMemoryCache(string filePath, Bitmap thumbnail) { // 如果缓存已满,移除最久未使用的项 if (_memoryCache.Count >= MaxMemoryCacheSize) { var oldestKey = _memoryCache .OrderBy(x => x.Value.LastAccessed) .FirstOrDefault().Key; if (oldestKey != null && _memoryCache.TryRemove(oldestKey, out var oldest)) { oldest.Thumbnail?.Dispose(); } } _memoryCache.TryAdd(filePath, new CachedThumbnail { Thumbnail = thumbnail, LastAccessed = DateTime.UtcNow }); } /// /// 清理过期缓存 /// private async Task CleanupExpiredCacheAsync() { while (true) { try { await Task.Delay(TimeSpan.FromHours(1)); // 每小时清理一次 // 清理内存缓存中的过期项 var expiredKeys = _memoryCache .Where(x => DateTime.UtcNow - x.Value.LastAccessed > TimeSpan.FromHours(1)) .Select(x => x.Key) .ToList(); foreach (var key in expiredKeys) { if (_memoryCache.TryRemove(key, out var cached)) { cached.Thumbnail?.Dispose(); } } // 清理磁盘缓存中的过期文件 if (Directory.Exists(_cacheDirectory)) { var cutoffTime = DateTime.UtcNow - CacheExpiration; foreach (var file in Directory.EnumerateFiles(_cacheDirectory, "*.*", SearchOption.AllDirectories)) { try { var fileInfo = new FileInfo(file); if (fileInfo.LastWriteTimeUtc < cutoffTime) { File.Delete(file); } } catch { // 忽略删除失败的文件 } } } } catch (Exception ex) { _logger?.LogError(ex, "清理缓存时出错"); } } } /// /// 清除所有缓存 /// public void ClearCache() { // 清除内存缓存 foreach (var cached in _memoryCache.Values) { cached.Thumbnail?.Dispose(); } _memoryCache.Clear(); // 清除磁盘缓存 if (Directory.Exists(_cacheDirectory)) { try { Directory.Delete(_cacheDirectory, true); Directory.CreateDirectory(_cacheDirectory); } catch (Exception ex) { _logger?.LogError(ex, "清除磁盘缓存失败"); } } } /// /// 缓存的缩略图项 /// private class CachedThumbnail { public Bitmap? Thumbnail { get; set; } public DateTime LastAccessed { get; set; } } }