Skip to content

Commit

Permalink
Redo icon cache
Browse files Browse the repository at this point in the history
  • Loading branch information
yaira2 committed Mar 1, 2024
1 parent 1eae1b6 commit 68bd772
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 124 deletions.
114 changes: 12 additions & 102 deletions src/Files.App/Data/Models/ItemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -900,41 +900,6 @@ public void UpdateGroupOptions()
FilesAndFolders.GetExtendedGroupHeaderInfo = groupInfoSelector.Item2;
}

public Dictionary<string, BitmapImage> DefaultIcons = new();

private uint currentDefaultIconSize = 0;

public async Task GetDefaultItemIconsAsync(uint size)
{
if (currentDefaultIconSize == size)
return;

DefaultIcons.Clear();

// Get icon for folders
using StorageItemThumbnail icon = await FilesystemTasks.Wrap(() => StorageItemIconHelpers.GetIconForItemType(size, IconPersistenceOptions.Persist));
if (icon is not null)
{
var img = new BitmapImage();
await img.SetSourceAsync(icon);
DefaultIcons.Add(string.Empty, img);
}

// Get icon for exe files
var executableIcon = await FileThumbnailHelper.GetIconAsync(
@"ms-appx:///Assets/FilesOpenDialog/Files.App.Launcher.exe",
size,
false,
true,
IconOptions.None);

var bitmapImage = await executableIcon.IconData.ToBitmapAsync();
if (bitmapImage is not null)
DefaultIcons.Add(".exe", bitmapImage);

currentDefaultIconSize = size;
}

private bool isLoadingItems = false;
public bool IsLoadingItems
{
Expand All @@ -949,44 +914,28 @@ private async Task<BitmapImage> GetShieldIcon()
return shieldIcon;
}

/// <summary>
/// Loads basic icon using ReturnIconOnly flag
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
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;

// Add file icon to the DefaultIcons list
if
(
!item.IsFolder &&
!DefaultIcons.ContainsKey(item.FileExtension.ToLowerInvariant()) &&
!string.IsNullOrEmpty(item.FileExtension) &&
!item.IsShortcut &&
!item.IsExecutable
)
{
DefaultIcons.TryAdd(item.FileExtension.ToLowerInvariant(), image);
}
}
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
}

Expand All @@ -1002,39 +951,6 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
}
}

/// <summary>
/// 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)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
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);
}
}

/// <summary>
/// Tries getting a cached thumbnail from the system. This is usually only needed for cloud locations, otherwise LoadThumbnailAsync does the job.
/// </summary>
Expand Down Expand Up @@ -1129,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)
{
Expand Down Expand Up @@ -1240,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)
Expand Down Expand Up @@ -1518,8 +1432,6 @@ private async Task RapidAddItemsToCollectionAsync(string? path, LibraryItem? lib
break;
}

await GetDefaultItemIconsAsync(folderSettings.GetRoundedIconSize());

if (IsLoadingCancelled)
{
IsLoadingCancelled = false;
Expand Down Expand Up @@ -1738,7 +1650,7 @@ await Task.Run(async () =>
filesAndFolders.AddRange(intermediateList);
await OrderFilesAndFoldersAsync();
await ApplyFilesAndFoldersChangesAsync();
}, defaultIconPairs: DefaultIcons);
});

filesAndFolders.AddRange(fileList);

Expand Down Expand Up @@ -1786,8 +1698,7 @@ await Task.Run(async () =>

await OrderFilesAndFoldersAsync();
await ApplyFilesAndFoldersChangesAsync();
},
defaultIconPairs: DefaultIcons);
});

filesAndFolders.AddRange(finalList);

Expand Down Expand Up @@ -2526,7 +2437,6 @@ public void Dispose()
fileTagsSettingsService.OnSettingImportedEvent -= FileTagsSettingsService_OnSettingUpdated;
fileTagsSettingsService.OnTagsUpdated -= FileTagsSettingsService_OnSettingUpdated;
folderSizeProvider.SizeChanged -= FolderSizeProvider_SizeChanged;
DefaultIcons.Clear();
}
}

Expand Down
48 changes: 46 additions & 2 deletions src/Files.App/Utils/Shell/Win32API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ConcurrentDictionary<int, IconCacheEntry>> _iconOnlyCache = new();

private static readonly ConcurrentDictionary<string, ConcurrentDictionary<int, IconCacheEntry>> _thumbnailCache = new();

private static readonly object _iconLock = new object();

/// <summary>
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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);

Expand Down Expand Up @@ -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;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ public static async Task<List<ListedItem>> ListEntries(
NativeFindStorageItemHelper.WIN32_FIND_DATA findData,
CancellationToken cancellationToken,
int countLimit,
Func<List<ListedItem>, Task> intermediateAction,
Dictionary<string, BitmapImage> defaultIconPairs = null
Func<List<ListedItem>, Task> intermediateAction
)
{
var sampler = new IntervalSampler(500);
Expand All @@ -54,18 +53,6 @@ public static async Task<List<ListedItem>> ListEntries(
var file = await GetFile(findData, path, isGitRepo, cancellationToken);
if (file is not null)
{
if (defaultIconPairs is not null)
{
if (!string.IsNullOrEmpty(file.FileExtension))
{
var lowercaseExtension = file.FileExtension.ToLowerInvariant();
if (defaultIconPairs.ContainsKey(lowercaseExtension))
{
file.FileImage = defaultIconPairs[lowercaseExtension];
}
}
}

tempList.Add(file);
++count;

Expand All @@ -82,12 +69,6 @@ public static async Task<List<ListedItem>> ListEntries(
var folder = await GetFolder(findData, path, isGitRepo, cancellationToken);
if (folder is not null)
{
if (defaultIconPairs?.ContainsKey(string.Empty) ?? false)
{
// Set folder icon (found by empty extension string)
folder.FileImage = defaultIconPairs[string.Empty];
}

tempList.Add(folder);
++count;

Expand Down

0 comments on commit 68bd772

Please sign in to comment.