Skip to content

Commit b07f313

Browse files
authored
Fix: Fixed issue where loading thumbnails sometimes took very long time (#14950)
1 parent 7883b19 commit b07f313

File tree

5 files changed

+107
-21
lines changed

5 files changed

+107
-21
lines changed

src/Files.App/Data/Models/ItemViewModel.cs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public sealed class ItemViewModel : ObservableObject, IDisposable
3030
private readonly SemaphoreSlim enumFolderSemaphore;
3131
private readonly SemaphoreSlim getFileOrFolderSemaphore;
3232
private readonly SemaphoreSlim bulkOperationSemaphore;
33+
private readonly SemaphoreSlim loadThumbnailSemaphore;
3334
private readonly ConcurrentQueue<(uint Action, string FileName)> operationQueue;
3435
private readonly ConcurrentQueue<uint> gitChangesQueue;
3536
private readonly ConcurrentDictionary<string, bool> itemLoadQueue;
@@ -485,6 +486,7 @@ public ItemViewModel(LayoutPreferencesManager folderSettingsViewModel)
485486
enumFolderSemaphore = new SemaphoreSlim(1, 1);
486487
getFileOrFolderSemaphore = new SemaphoreSlim(50);
487488
bulkOperationSemaphore = new SemaphoreSlim(1, 1);
489+
loadThumbnailSemaphore = new SemaphoreSlim(1, 1);
488490
dispatcherQueue = DispatcherQueue.GetForCurrentThread();
489491

490492
UserSettingsService.OnSettingChangedEvent += UserSettingsService_OnSettingChangedEvent;
@@ -914,18 +916,51 @@ private async Task<BitmapImage> GetShieldIcon()
914916
return shieldIcon;
915917
}
916918

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

923-
// Get thumbnail
924-
var result = await FileThumbnailHelper.GetIconAsync(
925-
item.ItemPath,
926-
thumbnailSize,
927-
item.IsFolder,
928-
returnIconOnly ? IconOptions.ReturnIconOnly : IconOptions.None);
925+
byte[]? result = null;
926+
if (item.IsFolder)
927+
{
928+
if (!returnIconOnly)
929+
{
930+
// Get cached thumbnail
931+
result = await FileThumbnailHelper.GetIconAsync(
932+
item.ItemPath,
933+
thumbnailSize,
934+
item.IsFolder,
935+
IconOptions.ReturnThumbnailOnly | IconOptions.ReturnOnlyIfCached);
936+
937+
cancellationToken.ThrowIfCancellationRequested();
938+
loadNonCachedThumbnail = true;
939+
}
940+
941+
if (result is null)
942+
{
943+
// Get icon
944+
result = await FileThumbnailHelper.GetIconAsync(
945+
item.ItemPath,
946+
thumbnailSize,
947+
item.IsFolder,
948+
IconOptions.ReturnIconOnly);
949+
950+
cancellationToken.ThrowIfCancellationRequested();
951+
}
952+
}
953+
else
954+
{
955+
// Get icon or thumbnail
956+
result = await FileThumbnailHelper.GetIconAsync(
957+
item.ItemPath,
958+
thumbnailSize,
959+
item.IsFolder,
960+
returnIconOnly ? IconOptions.ReturnIconOnly : IconOptions.None);
961+
962+
cancellationToken.ThrowIfCancellationRequested();
963+
}
929964

930965
if (result is not null)
931966
{
@@ -936,17 +971,57 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
936971
if (image is not null)
937972
item.FileImage = image;
938973
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
974+
975+
cancellationToken.ThrowIfCancellationRequested();
939976
}
940977

941978
// Get icon overlay
942979
var iconOverlay = await FileThumbnailHelper.GetIconOverlayAsync(item.ItemPath, true);
980+
981+
cancellationToken.ThrowIfCancellationRequested();
982+
943983
if (iconOverlay is not null)
944984
{
945985
await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
946986
{
947987
item.IconOverlay = await iconOverlay.ToBitmapAsync();
948988
item.ShieldIcon = await GetShieldIcon();
949989
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
990+
991+
cancellationToken.ThrowIfCancellationRequested();
992+
}
993+
994+
if (loadNonCachedThumbnail)
995+
{
996+
// Get non-cached thumbnail asynchronously
997+
_ = Task.Run(async () => {
998+
await loadThumbnailSemaphore.WaitAsync(cancellationToken);
999+
try
1000+
{
1001+
result = await FileThumbnailHelper.GetIconAsync(
1002+
item.ItemPath,
1003+
thumbnailSize,
1004+
item.IsFolder,
1005+
IconOptions.ReturnThumbnailOnly);
1006+
}
1007+
finally
1008+
{
1009+
loadThumbnailSemaphore.Release();
1010+
}
1011+
1012+
cancellationToken.ThrowIfCancellationRequested();
1013+
1014+
if (result is not null)
1015+
{
1016+
await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
1017+
{
1018+
// Assign FileImage property
1019+
var image = await result.ToBitmapAsync();
1020+
if (image is not null)
1021+
item.FileImage = image;
1022+
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
1023+
}
1024+
}, cancellationToken);
9501025
}
9511026
}
9521027

@@ -990,7 +1065,7 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item)
9901065
}
9911066

9921067
cts.Token.ThrowIfCancellationRequested();
993-
await LoadThumbnailAsync(item);
1068+
await LoadThumbnailAsync(item, cts.Token);
9941069

9951070
cts.Token.ThrowIfCancellationRequested();
9961071
if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive)
@@ -1104,7 +1179,8 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() =>
11041179
{
11051180
_ = Task.Run(async () => {
11061181
await Task.Delay(500);
1107-
await LoadThumbnailAsync(item);
1182+
cts.Token.ThrowIfCancellationRequested();
1183+
await LoadThumbnailAsync(item, cts.Token);
11081184
});
11091185
}
11101186
}

src/Files.App/Files.App.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
<PackageReference Include="LiveChartsCore.SkiaSharpView.WinUI" Version="2.0.0-rc1.2" />
8181
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="5.0.3" />
8282
<PackageReference Include="Microsoft.AppCenter.Crashes" Version="5.0.3" />
83-
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.2" />
83+
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.3" />
8484
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
8585
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
8686
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />

src/Files.App/Helpers/Win32/Win32Helper.Storage.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,13 @@ public static string ExtractStringFromDLL(string file, int number)
281281
/// <param name="path"></param>
282282
/// <param name="size"></param>
283283
/// <param name="isFolder"></param>
284-
/// <param name="returnIconOnly"></param>
284+
/// <param name="iconOptions"></param>
285285
/// <returns></returns>
286286
public static byte[]? GetIcon(
287287
string path,
288288
int size,
289289
bool isFolder,
290-
bool returnIconOnly)
290+
IconOptions iconOptions)
291291
{
292292
byte[]? iconData = null;
293293

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

304-
if (returnIconOnly)
304+
if (iconOptions.HasFlag(IconOptions.ReturnIconOnly))
305305
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;
306306

307+
if (iconOptions.HasFlag(IconOptions.ReturnThumbnailOnly))
308+
flags |= Shell32.SIIGBF.SIIGBF_THUMBNAILONLY;
309+
310+
if (iconOptions.HasFlag(IconOptions.ReturnOnlyIfCached))
311+
flags |= Shell32.SIIGBF.SIIGBF_INCACHEONLY;
312+
307313
var hres = shellFactory.GetImage(new SIZE(size, size), flags, out var hbitmap);
308314
if (hres == HRESULT.S_OK)
309315
{
@@ -315,7 +321,7 @@ public static string ExtractStringFromDLL(string file, int number)
315321
Marshal.ReleaseComObject(shellFactory);
316322
}
317323

318-
if (iconData is not null)
324+
if (iconData is not null || iconOptions.HasFlag(IconOptions.ReturnThumbnailOnly))
319325
return iconData;
320326
else
321327
{

src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ public static class FileThumbnailHelper
1313
public static async Task<byte[]?> GetIconAsync(string path, uint requestedSize, bool isFolder, IconOptions iconOptions)
1414
{
1515
var size = iconOptions.HasFlag(IconOptions.UseCurrentScale) ? requestedSize * App.AppModel.AppWindowDPI : requestedSize;
16-
var returnIconOnly = iconOptions.HasFlag(IconOptions.ReturnIconOnly);
1716

18-
return await Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, returnIconOnly));
17+
return await Win32Helper.StartSTATask(() => Win32Helper.GetIcon(path, (int)size, isFolder, iconOptions));
1918
}
2019

2120
/// <summary>

src/Files.Core/Data/Enums/IconOptions.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,26 @@ public enum IconOptions
1212
/// <summary>
1313
/// Default. No options.
1414
/// </summary>
15-
None,
15+
None = 0,
1616

1717
/// <summary>
1818
/// Increase requested size based on the displays DPI setting.
1919
/// </summary>
20-
UseCurrentScale,
20+
UseCurrentScale = 1,
2121

2222
/// <summary>
2323
/// Retrieve only the file icon, even a thumbnail is available. This has the best performance.
2424
/// </summary>
25-
ReturnIconOnly,
25+
ReturnIconOnly = 2,
26+
27+
/// <summary>
28+
/// Retrieve only the thumbnail.
29+
/// </summary>
30+
ReturnThumbnailOnly = 4,
2631

2732
/// <summary>
2833
/// Retrieve a thumbnail only if it is cached or embedded in the file.
2934
/// </summary>
30-
ReturnOnlyIfCached,
35+
ReturnOnlyIfCached = 8,
3136
}
3237
}

0 commit comments

Comments
 (0)