diff --git a/src/Files.App/Data/Models/ItemViewModel.cs b/src/Files.App/Data/Models/ItemViewModel.cs index 5ccd5979f1464..b426ab7373043 100644 --- a/src/Files.App/Data/Models/ItemViewModel.cs +++ b/src/Files.App/Data/Models/ItemViewModel.cs @@ -914,31 +914,28 @@ private async Task GetShieldIcon() return shieldIcon; } - /// - /// Loads basic icon using ReturnIconOnly flag - /// - /// - /// - private async Task LoadBasicIconAsync(ListedItem item) + private async Task LoadThumbnailAsync(ListedItem item) { - // Get icon + // Cancel if thumbnails aren't enabled + var thumbnailSize = folderSettings.GetRoundedIconSize(); + var returnIconOnly = UserSettingsService.FoldersSettingsService.ShowThumbnails == false || thumbnailSize < 48; + + // Get thumbnail var icon = await FileThumbnailHelper.GetIconAsync( item.ItemPath, - folderSettings.GetRoundedIconSize(), + thumbnailSize, item.IsFolder, false, - IconOptions.ReturnIconOnly); + returnIconOnly ? IconOptions.ReturnIconOnly : IconOptions.None); if (icon.IconData is not null) { await dispatcherQueue.EnqueueOrInvokeAsync(async () => { + // Assign FileImage property var image = await icon.IconData.ToBitmapAsync(); if (image is not null) - { - // Assign FileImage property item.FileImage = image; - } }, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low); } @@ -954,39 +951,6 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () => } } - /// - /// Loads thumbnail without any flags - /// Returns early if thumbnails aren't needed for this item (eg. if thumbnails are disabled or size is too small) - /// - /// - /// - private async Task LoadThumbnailAsync(ListedItem item) - { - // Cancel if thumbnails aren't enabled - var thumbnailSize = folderSettings.GetRoundedIconSize(); - if (UserSettingsService.FoldersSettingsService.ShowThumbnails == false || thumbnailSize < 48) - return; - - // Get thumbnail - var icon = await FileThumbnailHelper.GetIconAsync( - item.ItemPath, - thumbnailSize, - item.IsFolder, - false, - IconOptions.None); - - if (icon.IconData is not null) - { - await dispatcherQueue.EnqueueOrInvokeAsync(async () => - { - // Assign FileImage property - var image = await icon.IconData.ToBitmapAsync(); - if (image is not null) - item.FileImage = image; - }, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low); - } - } - /// /// Tries getting a cached thumbnail from the system. This is usually only needed for cloud locations, otherwise LoadThumbnailAsync does the job. /// @@ -1081,7 +1045,7 @@ await Task.Run(async () => } cts.Token.ThrowIfCancellationRequested(); - await LoadBasicIconAsync(item); + await LoadThumbnailAsync(item); if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive) { @@ -1192,8 +1156,6 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => // Load cached thumbnail for cloud files if (item.SyncStatusUI.SyncStatus != CloudDriveSyncStatus.NotSynced && item.SyncStatusUI.SyncStatus != CloudDriveSyncStatus.Unknown) _ = LoadCachedThumbnailAsync(item); - else - _ = LoadThumbnailAsync(item); } if (loadGroupHeaderInfo) diff --git a/src/Files.App/Utils/Shell/Win32API.cs b/src/Files.App/Utils/Shell/Win32API.cs index 0e9d835942d73..6406de58988df 100644 --- a/src/Files.App/Utils/Shell/Win32API.cs +++ b/src/Files.App/Utils/Shell/Win32API.cs @@ -273,6 +273,15 @@ public static string ExtractStringFromDLL(string file, int number) return overlayData; } + private class IconCacheEntry + { + public byte[]? Icon { get; set; } + } + + private static readonly ConcurrentDictionary> _iconOnlyCache = new(); + + private static readonly ConcurrentDictionary> _thumbnailCache = new(); + private static readonly object _iconLock = new object(); /// @@ -297,6 +306,26 @@ public static (byte[]? icon, bool isIconCached) GetIcon( byte[]? iconData = null; bool isIconCached = false; + + + var iconOnlyEntry = _iconOnlyCache.GetOrAdd(path, _ => new()); + if (iconOnlyEntry.TryGetValue(thumbnailSize, out var iconOnlyCacheEntry)) + { + iconData = iconOnlyCacheEntry.Icon; + + if (getIconOnly && iconOnlyCacheEntry is not null) + return (iconData, true); + } + + var thumbnailEntry = _thumbnailCache.GetOrAdd(path, _ => new()); + if (thumbnailEntry.TryGetValue(thumbnailSize, out var thumbnailCacheEntry)) + { + iconData = thumbnailCacheEntry.Icon; + + if (!getIconOnly && iconData is not null) + return (iconData, true); + } + try { // Attempt to get file icon/thumbnail using IShellItemImageFactory GetImage @@ -326,7 +355,7 @@ public static (byte[]? icon, bool isIconCached) GetIcon( } if (iconData is not null) - return (iconData, isIconCached); + return (iconData, isIconCached); else { var shfi = new Shell32.SHFILEINFO(); @@ -335,7 +364,7 @@ public static (byte[]? icon, bool isIconCached) GetIcon( // Cannot access file, use file attributes var useFileAttibutes = iconData is null; - var ret = Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags); + var ret = Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags); if (ret == IntPtr.Zero) return (iconData, isIconCached); @@ -396,7 +425,22 @@ public static (byte[]? icon, bool isIconCached) GetIcon( } finally { + if (getIconOnly) + { + iconOnlyCacheEntry = new IconCacheEntry(); + if (iconData is not null) + iconOnlyCacheEntry.Icon = iconData; + iconOnlyEntry[thumbnailSize] = iconOnlyCacheEntry; + } + else + { + thumbnailCacheEntry = new IconCacheEntry(); + if (iconData is not null) + thumbnailCacheEntry.Icon = iconData; + + thumbnailEntry[thumbnailSize] = thumbnailCacheEntry; + } } }