Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Fixed issue where loading thumbnails sometimes took very long time #14950

Merged
merged 3 commits into from Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 86 additions & 10 deletions src/Files.App/Data/Models/ItemViewModel.cs
Expand Up @@ -30,6 +30,7 @@ public sealed class ItemViewModel : ObservableObject, IDisposable
private readonly SemaphoreSlim enumFolderSemaphore;
private readonly SemaphoreSlim getFileOrFolderSemaphore;
private readonly SemaphoreSlim bulkOperationSemaphore;
private readonly SemaphoreSlim loadThumbnailSemaphore;
private readonly ConcurrentQueue<(uint Action, string FileName)> operationQueue;
private readonly ConcurrentQueue<uint> gitChangesQueue;
private readonly ConcurrentDictionary<string, bool> itemLoadQueue;
Expand Down Expand Up @@ -485,6 +486,7 @@ public ItemViewModel(LayoutPreferencesManager folderSettingsViewModel)
enumFolderSemaphore = new SemaphoreSlim(1, 1);
getFileOrFolderSemaphore = new SemaphoreSlim(50);
bulkOperationSemaphore = new SemaphoreSlim(1, 1);
loadThumbnailSemaphore = new SemaphoreSlim(1, 1);
dispatcherQueue = DispatcherQueue.GetForCurrentThread();

UserSettingsService.OnSettingChangedEvent += UserSettingsService_OnSettingChangedEvent;
Expand Down Expand Up @@ -914,18 +916,51 @@ private async Task<BitmapImage> GetShieldIcon()
return shieldIcon;
}

private async Task LoadThumbnailAsync(ListedItem item)
private async Task LoadThumbnailAsync(ListedItem item, CancellationToken cancellationToken)
{
// Cancel if thumbnails aren't enabled
var loadNonCachedThumbnail = false;
var thumbnailSize = folderSettings.GetRoundedIconSize();
var returnIconOnly = UserSettingsService.FoldersSettingsService.ShowThumbnails == false || thumbnailSize < 48;

// Get thumbnail
var result = await FileThumbnailHelper.GetIconAsync(
item.ItemPath,
thumbnailSize,
item.IsFolder,
returnIconOnly ? IconOptions.ReturnIconOnly : IconOptions.None);
byte[]? result = null;
if (item.IsFolder)
{
if (!returnIconOnly)
{
// Get cached thumbnail
result = await FileThumbnailHelper.GetIconAsync(
item.ItemPath,
thumbnailSize,
item.IsFolder,
IconOptions.ReturnThumbnailOnly | IconOptions.ReturnOnlyIfCached);

cancellationToken.ThrowIfCancellationRequested();
loadNonCachedThumbnail = true;
}

if (result is null)
{
// Get icon
result = await FileThumbnailHelper.GetIconAsync(
item.ItemPath,
thumbnailSize,
item.IsFolder,
IconOptions.ReturnIconOnly);

cancellationToken.ThrowIfCancellationRequested();
}
}
else
{
// Get icon or thumbnail
result = await FileThumbnailHelper.GetIconAsync(
item.ItemPath,
thumbnailSize,
item.IsFolder,
returnIconOnly ? IconOptions.ReturnIconOnly : IconOptions.None);

cancellationToken.ThrowIfCancellationRequested();
}

if (result is not null)
{
Expand All @@ -936,17 +971,57 @@ private async Task LoadThumbnailAsync(ListedItem item)
if (image is not null)
item.FileImage = image;
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);

cancellationToken.ThrowIfCancellationRequested();
}

// Get icon overlay
var iconOverlay = await FileThumbnailHelper.GetIconOverlayAsync(item.ItemPath, true);

cancellationToken.ThrowIfCancellationRequested();

if (iconOverlay is not null)
{
await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
item.IconOverlay = await iconOverlay.ToBitmapAsync();
item.ShieldIcon = await GetShieldIcon();
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);

cancellationToken.ThrowIfCancellationRequested();
}

if (loadNonCachedThumbnail)
{
// Get non-cached thumbnail asynchronously
_ = Task.Run(async () => {
await loadThumbnailSemaphore.WaitAsync(cancellationToken);
try
{
result = await FileThumbnailHelper.GetIconAsync(
item.ItemPath,
thumbnailSize,
item.IsFolder,
IconOptions.ReturnThumbnailOnly);
}
finally
{
loadThumbnailSemaphore.Release();
}

cancellationToken.ThrowIfCancellationRequested();

if (result is not null)
{
await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
{
// Assign FileImage property
var image = await result.ToBitmapAsync();
if (image is not null)
item.FileImage = image;
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
}
}, cancellationToken);
}
}

Expand Down Expand Up @@ -990,7 +1065,7 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item)
}

cts.Token.ThrowIfCancellationRequested();
await LoadThumbnailAsync(item);
await LoadThumbnailAsync(item, cts.Token);

cts.Token.ThrowIfCancellationRequested();
if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive)
Expand Down Expand Up @@ -1104,7 +1179,8 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item)
{
_ = Task.Run(async () => {
await Task.Delay(500);
await LoadThumbnailAsync(item);
cts.Token.ThrowIfCancellationRequested();
await LoadThumbnailAsync(item, cts.Token);
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/Files.App.csproj
Expand Up @@ -80,7 +80,7 @@
<PackageReference Include="LiveChartsCore.SkiaSharpView.WinUI" Version="2.0.0-rc1.2" />
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="5.0.3" />
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="5.0.3" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.2" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
Expand Down
14 changes: 10 additions & 4 deletions src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
Expand Up @@ -281,13 +281,13 @@ public static string ExtractStringFromDLL(string file, int number)
/// <param name="path"></param>
/// <param name="size"></param>
/// <param name="isFolder"></param>
/// <param name="returnIconOnly"></param>
/// <param name="iconOptions"></param>
/// <returns></returns>
public static byte[]? GetIcon(
string path,
int size,
bool isFolder,
bool returnIconOnly)
IconOptions iconOptions)
{
byte[]? iconData = null;

Expand All @@ -301,9 +301,15 @@ public static string ExtractStringFromDLL(string file, int number)
{
var flags = Shell32.SIIGBF.SIIGBF_BIGGERSIZEOK;

if (returnIconOnly)
if (iconOptions.HasFlag(IconOptions.ReturnIconOnly))
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;

if (iconOptions.HasFlag(IconOptions.ReturnThumbnailOnly))
flags |= Shell32.SIIGBF.SIIGBF_THUMBNAILONLY;

if (iconOptions.HasFlag(IconOptions.ReturnOnlyIfCached))
flags |= Shell32.SIIGBF.SIIGBF_INCACHEONLY;

var hres = shellFactory.GetImage(new SIZE(size, size), flags, out var hbitmap);
if (hres == HRESULT.S_OK)
{
Expand All @@ -315,7 +321,7 @@ public static string ExtractStringFromDLL(string file, int number)
Marshal.ReleaseComObject(shellFactory);
}

if (iconData is not null)
if (iconData is not null || iconOptions.HasFlag(IconOptions.ReturnThumbnailOnly))
return iconData;
else
{
Expand Down
3 changes: 1 addition & 2 deletions src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs
Expand Up @@ -13,9 +13,8 @@ public static class FileThumbnailHelper
public static async Task<byte[]?> GetIconAsync(string path, uint requestedSize, bool isFolder, IconOptions iconOptions)
{
var size = iconOptions.HasFlag(IconOptions.UseCurrentScale) ? requestedSize * App.AppModel.AppWindowDPI : requestedSize;
var returnIconOnly = iconOptions.HasFlag(IconOptions.ReturnIconOnly);

return await Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, returnIconOnly));
return await Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, iconOptions));
}

/// <summary>
Expand Down
13 changes: 9 additions & 4 deletions src/Files.Core/Data/Enums/IconOptions.cs
Expand Up @@ -12,21 +12,26 @@ public enum IconOptions
/// <summary>
/// Default. No options.
/// </summary>
None,
None = 0,

/// <summary>
/// Increase requested size based on the displays DPI setting.
/// </summary>
UseCurrentScale,
UseCurrentScale = 1,

/// <summary>
/// Retrieve only the file icon, even a thumbnail is available. This has the best performance.
/// </summary>
ReturnIconOnly,
ReturnIconOnly = 2,

/// <summary>
/// Retrieve only the thumbnail.
/// </summary>
ReturnThumbnailOnly = 4,

/// <summary>
/// Retrieve a thumbnail only if it is cached or embedded in the file.
/// </summary>
ReturnOnlyIfCached,
ReturnOnlyIfCached = 8,
}
}