Skip to content

Commit 5ff7f98

Browse files
yaira20x5bfahishitetsu
authored
Fix: Fixed issue where thumbnails would sometimes fail to load in OneDrive (#14552)
Co-authored-by: 0x5bfa <62196528+0x5bfa@users.noreply.github.com> Co-authored-by: hishitetsu <66369541+hishitetsu@users.noreply.github.com>
1 parent 55165e9 commit 5ff7f98

File tree

3 files changed

+64
-36
lines changed

3 files changed

+64
-36
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -941,11 +941,8 @@ private async Task<BitmapImage> GetShieldIcon()
941941
}
942942

943943
// ThumbnailSize is set to 96 so that unless we override it, mode is in turn set to SingleItem
944-
private async Task LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize = 96, IStorageItem? matchingStorageItem = null)
944+
private async Task<bool> LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize = 96, IStorageItem? matchingStorageItem = null)
945945
{
946-
var wasIconLoaded = false;
947-
948-
949946
if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive)
950947
{
951948
var getIconOnly = UserSettingsService.FoldersSettingsService.ShowThumbnails == false;
@@ -972,6 +969,8 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
972969
item.ShieldIcon = await GetShieldIcon();
973970
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
974971
}
972+
973+
return iconInfo.isIconCached;
975974
}
976975
else
977976
{
@@ -993,6 +992,8 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () =>
993992
item.ShieldIcon = await GetShieldIcon();
994993
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
995994
}
995+
996+
return iconInfo.isIconCached;
996997
}
997998
}
998999

@@ -1044,12 +1045,12 @@ await Task.Run(async () =>
10441045
if (matchingStorageFile is not null)
10451046
{
10461047
cts.Token.ThrowIfCancellationRequested();
1047-
await LoadItemThumbnailAsync(item, thumbnailSize, matchingStorageFile);
10481048

10491049
var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFile);
10501050
var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFile);
10511051
var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath);
10521052
var itemType = (item.ItemType == "Folder".GetLocalizedResource()) ? item.ItemType : matchingStorageFile.DisplayType;
1053+
10531054
cts.Token.ThrowIfCancellationRequested();
10541055

10551056
await dispatcherQueue.EnqueueOrInvokeAsync(() =>
@@ -1064,6 +1065,14 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() =>
10641065

10651066
SetFileTag(item);
10661067
wasSyncStatusLoaded = true;
1068+
1069+
var cancellationTokenSource = new CancellationTokenSource(3000);
1070+
// Loop until cached thumbnail is loaded or timeout is reached
1071+
while (!await LoadItemThumbnailAsync(item, thumbnailSize, matchingStorageFile))
1072+
{
1073+
cancellationTokenSource.Token.ThrowIfCancellationRequested();
1074+
await Task.Delay(100);
1075+
}
10671076
}
10681077
}
10691078

src/Files.App/Utils/Shell/Win32API.cs

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,31 @@ private class IconAndOverlayCacheEntry
228228

229229
private static readonly object _lock = new object();
230230

231-
public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path, int thumbnailSize, bool isFolder, bool getIconOnly, bool getOverlay = true, bool onlyGetOverlay = false)
231+
/// <summary>
232+
/// Returns an icon when a thumbnail isn't available or if getIconOnly is true.
233+
/// <br/>
234+
/// Returns an icon overlay when getOverlay is true.
235+
/// <br/>
236+
/// Returns a boolean indicating if the icon/thumbnail is cached.
237+
/// </summary>
238+
/// <param name="path"></param>
239+
/// <param name="thumbnailSize"></param>
240+
/// <param name="isFolder"></param>
241+
/// <param name="getIconOnly"></param>
242+
/// <param name="getOverlay"></param>
243+
/// <param name="onlyGetOverlay"></param>
244+
/// <returns></returns>
245+
public static (byte[]? icon, byte[]? overlay, bool isIconCached) GetFileIconAndOverlay(
246+
string path,
247+
int thumbnailSize,
248+
bool isFolder,
249+
bool getIconOnly,
250+
bool getOverlay = true,
251+
bool onlyGetOverlay = false)
232252
{
233253
byte[]? iconData = null, overlayData = null;
254+
bool isIconCached = false;
255+
234256
var entry = _iconAndOverlayCache.GetOrAdd(path, _ => new());
235257

236258
if (entry.TryGetValue(thumbnailSize, out var cacheEntry))
@@ -242,34 +264,36 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path,
242264
(!getOverlay && iconData is not null) ||
243265
(overlayData is not null && iconData is not null))
244266
{
245-
return (iconData, overlayData);
267+
return (iconData, overlayData, true);
246268
}
247269
}
248270

249271
try
250272
{
251-
if (!onlyGetOverlay)
252-
{
253-
using var shellItem = SafetyExtensions.IgnoreExceptions(()
254-
=> ShellFolderExtensions.GetShellItemFromPathOrPIDL(path));
273+
// Attempt to get file icon/thumbnail using IShellItemImageFactory GetImage
274+
using var shellItem = SafetyExtensions.IgnoreExceptions(()
275+
=> ShellFolderExtensions.GetShellItemFromPathOrPIDL(path));
255276

256-
if (shellItem is not null && shellItem.IShellItem is Shell32.IShellItemImageFactory fctry)
257-
{
258-
var flags = Shell32.SIIGBF.SIIGBF_BIGGERSIZEOK;
277+
if (shellItem is not null && shellItem.IShellItem is Shell32.IShellItemImageFactory shellFactory)
278+
{
279+
var flags = Shell32.SIIGBF.SIIGBF_BIGGERSIZEOK;
259280

260-
if (getIconOnly)
261-
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;
281+
if (getIconOnly)
282+
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;
283+
else
284+
flags |= Shell32.SIIGBF.SIIGBF_THUMBNAILONLY;
262285

263-
var hres = fctry.GetImage(new SIZE(thumbnailSize, thumbnailSize), flags, out var hbitmap);
264-
if (hres == HRESULT.S_OK)
265-
{
266-
using var image = GetBitmapFromHBitmap(hbitmap);
267-
if (image is not null)
268-
iconData = (byte[]?)new ImageConverter().ConvertTo(image, typeof(byte[]));
269-
}
286+
var hres = shellFactory.GetImage(new SIZE(thumbnailSize, thumbnailSize), flags, out var hbitmap);
287+
if (hres == HRESULT.S_OK)
288+
{
289+
using var image = GetBitmapFromHBitmap(hbitmap);
290+
if (image is not null)
291+
iconData = (byte[]?)new ImageConverter().ConvertTo(image, typeof(byte[]));
270292

271-
//Marshal.ReleaseComObject(fctry);
293+
isIconCached = true;
272294
}
295+
296+
Marshal.ReleaseComObject(shellFactory);
273297
}
274298

275299
if (getOverlay || (!onlyGetOverlay && iconData is null))
@@ -284,7 +308,7 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path,
284308
Shell32.SHGetFileInfo(pidl, 0, ref shfi, Shell32.SHFILEINFO.Size, Shell32.SHGFI.SHGFI_PIDL | flags) :
285309
Shell32.SHGetFileInfo(path, isFolder ? FileAttributes.Directory : 0, ref shfi, Shell32.SHFILEINFO.Size, flags | (useFileAttibutes ? Shell32.SHGFI.SHGFI_USEFILEATTRIBUTES : 0));
286310
if (ret == IntPtr.Zero)
287-
return (iconData, null);
311+
return (iconData, null, isIconCached);
288312

289313
User32.DestroyIcon(shfi.hIcon);
290314

@@ -299,7 +323,7 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path,
299323
lock (_lock)
300324
{
301325
if (!Shell32.SHGetImageList(imageListSize, typeof(ComCtl32.IImageList).GUID, out var imageListOut).Succeeded)
302-
return (iconData, null);
326+
return (iconData, null, isIconCached);
303327

304328
var imageList = (ComCtl32.IImageList)imageListOut;
305329

@@ -352,11 +376,11 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path,
352376
Marshal.ReleaseComObject(imageList);
353377
}
354378

355-
return (iconData, overlayData);
379+
return (iconData, overlayData, isIconCached);
356380
}
357381
else
358382
{
359-
return (iconData, null);
383+
return (iconData, null, isIconCached);
360384
}
361385
}
362386
finally

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@ namespace Files.App.Utils.Storage
99
{
1010
public static class FileThumbnailHelper
1111
{
12-
public static Task<(byte[] IconData, byte[] OverlayData)> LoadIconAndOverlayAsync(string filePath, uint thumbnailSize, bool isFolder = false, bool getIconOnly = false)
13-
=> Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly, true, false));
14-
15-
public static async Task<byte[]> LoadOverlayAsync(string filePath, uint thumbnailSize)
16-
{
17-
return (await Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, false, false, true, true))).overlay;
18-
}
12+
public static Task<(byte[] IconData, byte[] OverlayData, bool isIconCached)> LoadIconAndOverlayAsync(string filePath, uint thumbnailSize, bool isFolder = false, bool getIconOnly = false)
13+
=> Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly));
1914

2015
public static async Task<byte[]> LoadIconWithoutOverlayAsync(string filePath, uint thumbnailSize, bool isFolder, bool getIconOnly)
2116
{
22-
return (await Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly, false))).icon;
17+
return (await Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly))).icon;
2318
}
2419

2520
public static async Task<byte[]> LoadIconFromStorageItemAsync(IStorageItem item, uint thumbnailSize, ThumbnailMode thumbnailMode, ThumbnailOptions thumbnailOptions)

0 commit comments

Comments
 (0)