Add Explorer Workbench with hierarchical, off-UI Storage analysis.
Storage queries run in the background with cancellation and covering indexes so switching views no longer freezes the UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
159
src/Explorer.App/ShellIconConverter.cs
Normal file
159
src/Explorer.App/ShellIconConverter.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Explorer.Domain;
|
||||
using Explorer.Presentation;
|
||||
|
||||
namespace Explorer.App;
|
||||
|
||||
public sealed class ShellIconConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var large = parameter is string p && p.Equals("large", StringComparison.OrdinalIgnoreCase);
|
||||
return value switch
|
||||
{
|
||||
FolderItemViewModel item => ShellIconCache.Get(item.FullPath, item.IsDirectory, large, item.MayHydrateOnRead),
|
||||
FileSystemItem fs => ShellIconCache.Get(fs.FullPath, fs.IsDirectory, large, AttributeFlags.MayHydrateOnRead(fs.Attributes)),
|
||||
string path => ShellIconCache.Get(path, Directory.Exists(path), large),
|
||||
_ => ShellIconCache.Get(null, true, large)
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal static class ShellIconCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, ImageSource> Cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static ImageSource Get(string? path, bool isDirectory, bool large = false, bool avoidHydration = false)
|
||||
{
|
||||
var key = (large ? "L:" : "S:") + (avoidHydration ? "A:" : "") + CacheKey(path, isDirectory);
|
||||
return Cache.GetOrAdd(key, _ => Load(path, isDirectory, large, avoidHydration));
|
||||
}
|
||||
|
||||
private static string CacheKey(string? path, bool isDirectory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || path == "This PC")
|
||||
{
|
||||
return isDirectory ? "dir:generic" : "file:generic";
|
||||
}
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
return PathRules.IsDriveRoot(path) || (path.Length <= 3 && path.Contains(':', StringComparison.Ordinal))
|
||||
? "drive:" + path.TrimEnd('\\').ToUpperInvariant()
|
||||
: "dir:generic";
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(path);
|
||||
if (ext.Equals(".exe", StringComparison.OrdinalIgnoreCase)
|
||||
|| ext.Equals(".lnk", StringComparison.OrdinalIgnoreCase)
|
||||
|| ext.Equals(".ico", StringComparison.OrdinalIgnoreCase)
|
||||
|| ext.Equals(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "path:" + path;
|
||||
}
|
||||
|
||||
return "ext:" + (string.IsNullOrEmpty(ext) ? ".file" : ext.ToLowerInvariant());
|
||||
}
|
||||
|
||||
private static ImageSource Load(string? path, bool isDirectory, bool large, bool avoidHydration = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new ShFileInfo();
|
||||
uint flags = ShgfiIcon | (large ? ShgfiLargeIcon : ShgfiSmallIcon);
|
||||
uint attrs = isDirectory ? FileAttributeDirectory : FileAttributeNormal;
|
||||
string psz;
|
||||
if (string.IsNullOrWhiteSpace(path) || path == "This PC")
|
||||
{
|
||||
psz = isDirectory ? "folder" : "file";
|
||||
flags |= ShgfiUseFileAttributes;
|
||||
}
|
||||
else if (isDirectory && PathRules.IsDriveRoot(path))
|
||||
{
|
||||
psz = PathRules.EnsureDirectoryTrailingSlashIfRoot(path);
|
||||
}
|
||||
else if (isDirectory)
|
||||
{
|
||||
psz = path;
|
||||
flags |= ShgfiUseFileAttributes;
|
||||
}
|
||||
else
|
||||
{
|
||||
psz = path;
|
||||
var ext = Path.GetExtension(path);
|
||||
if (avoidHydration
|
||||
|| !ext.Equals(".exe", StringComparison.OrdinalIgnoreCase)
|
||||
&& !ext.Equals(".lnk", StringComparison.OrdinalIgnoreCase)
|
||||
&& !ext.Equals(".ico", StringComparison.OrdinalIgnoreCase)
|
||||
&& File.Exists(path) == false)
|
||||
{
|
||||
flags |= ShgfiUseFileAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
SHGetFileInfo(psz, attrs, ref info, (uint)Marshal.SizeOf<ShFileInfo>(), flags);
|
||||
if (info.hIcon == 0)
|
||||
{
|
||||
return Fallback();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var source = Imaging.CreateBitmapSourceFromHIcon(info.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
source.Freeze();
|
||||
return source;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DestroyIcon(info.hIcon);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Fallback();
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageSource Fallback()
|
||||
{
|
||||
var bmp = BitmapSource.Create(16, 16, 96, 96, PixelFormats.Bgra32, null, new byte[16 * 16 * 4], 16 * 4);
|
||||
bmp.Freeze();
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private const uint ShgfiIcon = 0x100;
|
||||
private const uint ShgfiLargeIcon = 0x0;
|
||||
private const uint ShgfiSmallIcon = 0x1;
|
||||
private const uint ShgfiUseFileAttributes = 0x10;
|
||||
private const uint FileAttributeDirectory = 0x10;
|
||||
private const uint FileAttributeNormal = 0x80;
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern nint SHGetFileInfo(string pszPath, uint dwFileAttributes, ref ShFileInfo psfi, uint cbFileInfo, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool DestroyIcon(nint hIcon);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct ShFileInfo
|
||||
{
|
||||
public nint hIcon;
|
||||
public int iIcon;
|
||||
public uint dwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
|
||||
public string szTypeName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user