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 thumbnails would sometimes fail to load in OneDrive #14552

Merged
merged 5 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 11 additions & 5 deletions src/Files.App/Data/Models/ItemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -941,11 +941,8 @@ private async Task<BitmapImage> GetShieldIcon()
}

// ThumbnailSize is set to 96 so that unless we override it, mode is in turn set to SingleItem
private async Task LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize = 96, IStorageItem? matchingStorageItem = null)
private async Task<bool> LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize = 96, IStorageItem? matchingStorageItem = null)
{
var wasIconLoaded = false;


if (item.IsLibrary || item.PrimaryItemAttribute == StorageItemTypes.File || item.IsArchive)
{
var getIconOnly = UserSettingsService.FoldersSettingsService.ShowThumbnails == false;
Expand All @@ -972,6 +969,8 @@ private async Task LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize =
item.ShieldIcon = await GetShieldIcon();
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
}

return iconInfo.isIconCached;
}
else
{
Expand All @@ -993,6 +992,8 @@ private async Task LoadItemThumbnailAsync(ListedItem item, uint thumbnailSize =
item.ShieldIcon = await GetShieldIcon();
}, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
}

return iconInfo.isIconCached;
}
}

Expand Down Expand Up @@ -1044,12 +1045,12 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item, uint thumbnai
if (matchingStorageFile is not null)
{
cts.Token.ThrowIfCancellationRequested();
await LoadItemThumbnailAsync(item, thumbnailSize, matchingStorageFile);

var syncStatus = await CheckCloudDriveSyncStatusAsync(matchingStorageFile);
var fileFRN = await FileTagsHelper.GetFileFRN(matchingStorageFile);
var fileTag = FileTagsHelper.ReadFileTag(item.ItemPath);
var itemType = (item.ItemType == "Folder".GetLocalizedResource()) ? item.ItemType : matchingStorageFile.DisplayType;

cts.Token.ThrowIfCancellationRequested();

await dispatcherQueue.EnqueueOrInvokeAsync(() =>
Expand All @@ -1064,6 +1065,11 @@ public async Task LoadExtendedItemPropertiesAsync(ListedItem item, uint thumbnai

SetFileTag(item);
wasSyncStatusLoaded = true;

var cancellationTokenSource = new CancellationTokenSource(3000);
// Loop until cached thumbnail is loaded or timeout is reached
while (!await LoadItemThumbnailAsync(item, thumbnailSize, matchingStorageFile))
cancellationTokenSource.Token.ThrowIfCancellationRequested();
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
70 changes: 47 additions & 23 deletions src/Files.App/Utils/Shell/Win32API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,31 @@ private class IconAndOverlayCacheEntry

private static readonly object _lock = new object();

public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path, int thumbnailSize, bool isFolder, bool getIconOnly, bool getOverlay = true, bool onlyGetOverlay = false)
/// <summary>
/// Returns an icon when a thumbnail isn't available or if getIconOnly is true
/// Returns an icon overlay when getOverlay is true
/// Returns a boolean indicating if the icon/thumbnail is cached
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="path"></param>
/// <param name="thumbnailSize"></param>
/// <param name="isFolder"></param>
/// <param name="getIconOnly"></param>
/// <param name="getOverlay"></param>
/// <param name="onlyGetOverlay"></param>
/// <returns></returns>
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
public static (byte[]? icon, byte[]? overlay, bool isIconCached) GetFileIconAndOverlay
(
string path,
int thumbnailSize,
bool isFolder,
bool getIconOnly,
bool getOverlay = true,
bool onlyGetOverlay = false
)
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
{
byte[]? iconData = null, overlayData = null;
bool isIconCached = false;

var entry = _iconAndOverlayCache.GetOrAdd(path, _ => new());

if (entry.TryGetValue(thumbnailSize, out var cacheEntry))
Expand All @@ -242,34 +264,36 @@ public static (byte[]? icon, byte[]? overlay) GetFileIconAndOverlay(string path,
(!getOverlay && iconData is not null) ||
(overlayData is not null && iconData is not null))
{
return (iconData, overlayData);
return (iconData, overlayData, true);
}
}

try
{
if (!onlyGetOverlay)
{
using var shellItem = SafetyExtensions.IgnoreExceptions(()
=> ShellFolderExtensions.GetShellItemFromPathOrPIDL(path));
// Attempt to get file icon/thumbnail using IShellItemImageFactory GetImage
using var shellItem = SafetyExtensions.IgnoreExceptions(()
=> ShellFolderExtensions.GetShellItemFromPathOrPIDL(path));

if (shellItem is not null && shellItem.IShellItem is Shell32.IShellItemImageFactory fctry)
{
var flags = Shell32.SIIGBF.SIIGBF_BIGGERSIZEOK;
if (shellItem is not null && shellItem.IShellItem is Shell32.IShellItemImageFactory shellFactory)
{
var flags = Shell32.SIIGBF.SIIGBF_BIGGERSIZEOK;

if (getIconOnly)
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;
if (getIconOnly)
flags |= Shell32.SIIGBF.SIIGBF_ICONONLY;
else
flags |= Shell32.SIIGBF.SIIGBF_THUMBNAILONLY;

var hres = fctry.GetImage(new SIZE(thumbnailSize, thumbnailSize), flags, out var hbitmap);
if (hres == HRESULT.S_OK)
{
using var image = GetBitmapFromHBitmap(hbitmap);
if (image is not null)
iconData = (byte[]?)new ImageConverter().ConvertTo(image, typeof(byte[]));
}
var hres = shellFactory.GetImage(new SIZE(thumbnailSize, thumbnailSize), flags, out var hbitmap);
if (hres == HRESULT.S_OK)
{
using var image = GetBitmapFromHBitmap(hbitmap);
if (image is not null)
iconData = (byte[]?)new ImageConverter().ConvertTo(image, typeof(byte[]));

//Marshal.ReleaseComObject(fctry);
isIconCached = true;
}

Marshal.ReleaseComObject(shellFactory);
}

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

User32.DestroyIcon(shfi.hIcon);

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

var imageList = (ComCtl32.IImageList)imageListOut;

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

return (iconData, overlayData);
return (iconData, overlayData, isIconCached);
}
else
{
return (iconData, null);
return (iconData, null, isIconCached);
}
}
finally
Expand Down
11 changes: 3 additions & 8 deletions src/Files.App/Utils/Storage/Helpers/FileThumbnailHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ namespace Files.App.Utils.Storage
{
public static class FileThumbnailHelper
{
public static Task<(byte[] IconData, byte[] OverlayData)> LoadIconAndOverlayAsync(string filePath, uint thumbnailSize, bool isFolder = false, bool getIconOnly = false)
=> Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly, true, false));

public static async Task<byte[]> LoadOverlayAsync(string filePath, uint thumbnailSize)
{
return (await Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, false, false, true, true))).overlay;
}
public static Task<(byte[] IconData, byte[] OverlayData, bool isIconCached)> LoadIconAndOverlayAsync(string filePath, uint thumbnailSize, bool isFolder = false, bool getIconOnly = false)
=> Win32API.StartSTATask(() => Win32API.GetFileIconAndOverlay(filePath, (int)thumbnailSize, isFolder, getIconOnly));

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

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