Skip to content

Commit

Permalink
Fix: Fixed issue where loading thumbnails sometimes took very long ti…
Browse files Browse the repository at this point in the history
…me (#14950)
  • Loading branch information
hishitetsu committed Mar 13, 2024
1 parent 7883b19 commit b07f313
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 21 deletions.
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,
}
}

0 comments on commit b07f313

Please sign in to comment.