Skip to content

Code Quality: Improved WindowsStorage implementations #17175

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/Files.App.CsWin32/ManualGuid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public static Guid* IID_IStorageProviderStatusUISourceFactory

[GuidRVAGen.Guid("00021500-0000-0000-C000-000000000046")]
public static partial Guid* IID_IQueryInfo { get; }

[GuidRVAGen.Guid("BCC18B79-BA16-442F-80C4-8A59C30C463B")]
public static partial Guid* IID_IShellItemImageFactory { get; }

[GuidRVAGen.Guid("000214E8-0000-0000-C000-000000000046")]
public static partial Guid* IID_IShellExtInit { get; }

[GuidRVAGen.Guid("000214F4-0000-0000-C000-000000000046")]
public static partial Guid* IID_IContextMenu2 { get; }
}

public static unsafe partial class CLSID
Expand All @@ -59,6 +68,9 @@ public static unsafe partial class CLSID

[GuidRVAGen.Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
public static partial Guid* CLSID_ApplicationActivationManager { get; }

[GuidRVAGen.Guid("D969A300-E7FF-11d0-A93B-00A0C90F2719")]
public static partial Guid* CLSID_NewMenu { get; }
}

public static unsafe partial class BHID
Expand Down
10 changes: 10 additions & 0 deletions src/Files.App.CsWin32/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,13 @@ QITIPF_FLAGS
GetKeyboardState
MapVirtualKey
GetKeyboardLayout
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
RegOpenKeyEx
RegEnumKeyEx
RegGetValue
IShellExtInit
IContextMenu2
GetSubMenu
GetMenuItemCount
GetMenuItemInfo
50 changes: 24 additions & 26 deletions src/Files.App.Storage/Storables/HomeFolder/HomeFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Files.App.Storage.Storables
{
public partial class HomeFolder : IHomeFolder
public unsafe partial class HomeFolder : IHomeFolder
{
public string Id => "Home"; // Will be "files://Home" in the future.

Expand Down Expand Up @@ -48,38 +48,36 @@ public IAsyncEnumerable<IStorableChild> GetQuickAccessFolderAsync(CancellationTo
/// <inheritdoc/>
public IAsyncEnumerable<IStorableChild> GetLogicalDrivesAsync(CancellationToken cancellationToken = default)
{
return GetLogicalDrives().ToAsyncEnumerable();
var availableDrives = PInvoke.GetLogicalDrives();
if (availableDrives is 0)
return Enumerable.Empty<IStorableChild>().ToAsyncEnumerable();

IEnumerable<IStorableChild> GetLogicalDrives()
{
var availableDrives = PInvoke.GetLogicalDrives();
if (availableDrives is 0)
yield break;

int count = BitOperations.PopCount(availableDrives);
var driveLetters = new char[count];
int count = BitOperations.PopCount(availableDrives);
var driveLetters = new char[count];

count = 0;
char driveLetter = 'A';
while (availableDrives is not 0)
{
if ((availableDrives & 1) is not 0)
driveLetters[count++] = driveLetter;
count = 0;
char driveLetter = 'A';
while (availableDrives is not 0)
{
if ((availableDrives & 1) is not 0)
driveLetters[count++] = driveLetter;

availableDrives >>= 1;
driveLetter++;
}
availableDrives >>= 1;
driveLetter++;
}

foreach (char letter in driveLetters)
{
cancellationToken.ThrowIfCancellationRequested();
List<IStorableChild> items = [];
foreach (char letter in driveLetters)
{
cancellationToken.ThrowIfCancellationRequested();

if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
throw new InvalidOperationException();
if (WindowsStorable.TryParse($"{letter}:\\") is not IWindowsStorable driveRoot)
throw new InvalidOperationException();

yield return new WindowsFolder(driveRoot.ThisPtr);
}
items.Add(new WindowsFolder(driveRoot.ThisPtr));
}

return items.ToAsyncEnumerable();
}

/// <inheritdoc/>
Expand Down
18 changes: 18 additions & 0 deletions src/Files.App.Storage/Storables/WindowsStorage/ContextMenuType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Storage
{
public enum ContextMenuType
{
Normal = 0x00000000,

Disabled = 0x00000003,

Checked = 0x00000008,

Highlighted = 0x00000080,

Default = 0x00001000,
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Windows.Win32;
using Windows.Win32.UI.Shell;

namespace Files.App.Storage
{
public interface IWindowsStorable : IDisposable
public unsafe interface IWindowsStorable : IDisposable
{
ComPtr<IShellItem> ThisPtr { get; }
IShellItem* ThisPtr { get; }
}
}
19 changes: 19 additions & 0 deletions src/Files.App.Storage/Storables/WindowsStorage/ShellNewItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

namespace Files.App.Storage
{
/// <summary>
/// Represents a ShellNew item with its type, file extension, description, and icon.
/// </summary>
public partial class ShellNewItem
{
public ContextMenuType Type { get; set; } = ContextMenuType.Normal;

public uint Id { get; set; }

public byte[]? Icon { get; set; }

public string? Name { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public unsafe WindowsBulkOperations(HWND ownerHWnd = default, FILEOPERATION_FLAG
public unsafe HRESULT QueueCopyOperation(WindowsStorable targetItem, WindowsFolder destinationFolder, string? copyName)
{
fixed (char* pszCopyName = copyName)
return _pFileOperation.Get()->CopyItem(targetItem.ThisPtr.Get(), destinationFolder.ThisPtr.Get(), pszCopyName, _pProgressSink.Get());
return _pFileOperation.Get()->CopyItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszCopyName, _pProgressSink.Get());
}

/// <summary>
Expand All @@ -111,7 +111,7 @@ public unsafe HRESULT QueueCopyOperation(WindowsStorable targetItem, WindowsFold
/// <returns>If this method succeeds, it returns <see cref="HRESULT.S_OK"/>. Otherwise, it returns an <see cref="HRESULT"/> error code.</returns>
public unsafe HRESULT QueueDeleteOperation(WindowsStorable targetItem)
{
return _pFileOperation.Get()->DeleteItem(targetItem.ThisPtr.Get(), _pProgressSink.Get());
return _pFileOperation.Get()->DeleteItem(targetItem.ThisPtr, _pProgressSink.Get());
}

/// <summary>
Expand All @@ -124,7 +124,7 @@ public unsafe HRESULT QueueDeleteOperation(WindowsStorable targetItem)
public unsafe HRESULT QueueMoveOperation(WindowsStorable targetItem, WindowsFolder destinationFolder, string? newName)
{
fixed (char* pszNewName = newName)
return _pFileOperation.Get()->MoveItem(targetItem.ThisPtr.Get(), destinationFolder.ThisPtr.Get(), pszNewName, null);
return _pFileOperation.Get()->MoveItem(targetItem.ThisPtr, destinationFolder.ThisPtr, pszNewName, null);
}

/// <summary>
Expand All @@ -138,7 +138,7 @@ public unsafe HRESULT QueueMoveOperation(WindowsStorable targetItem, WindowsFold
public unsafe HRESULT QueueCreateOperation(WindowsFolder destinationFolder, FILE_FLAGS_AND_ATTRIBUTES fileAttributes, string name, string? templateName)
{
fixed (char* pszName = name, pszTemplateName = templateName)
return _pFileOperation.Get()->NewItem(destinationFolder.ThisPtr.Get(), (uint)fileAttributes, pszName, pszTemplateName, _pProgressSink.Get());
return _pFileOperation.Get()->NewItem(destinationFolder.ThisPtr, (uint)fileAttributes, pszName, pszTemplateName, _pProgressSink.Get());
}

/// <summary>
Expand All @@ -150,7 +150,7 @@ public unsafe HRESULT QueueCreateOperation(WindowsFolder destinationFolder, FILE
public unsafe HRESULT QueueRenameOperation(WindowsStorable targetItem, string newName)
{
fixed (char* pszNewName = newName)
return _pFileOperation.Get()->RenameItem(targetItem.ThisPtr.Get(), pszNewName, _pProgressSink.Get());
return _pFileOperation.Get()->RenameItem(targetItem.ThisPtr, pszNewName, _pProgressSink.Get());
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Files.App.Storage/Storables/WindowsStorage/WindowsFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
namespace Files.App.Storage
{
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
public sealed class WindowsFile : WindowsStorable, IChildFile
public unsafe class WindowsFile : WindowsStorable, IChildFile
{
public WindowsFile(ComPtr<IShellItem> nativeObject)
public WindowsFile(IShellItem* ptr)
{
ThisPtr = nativeObject;
ThisPtr = ptr;
}

public Task<Stream> OpenStreamAsync(FileAccess accessMode, CancellationToken cancellationToken = default)
Expand Down
81 changes: 40 additions & 41 deletions src/Files.App.Storage/Storables/WindowsStorage/WindowsFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,40 @@
namespace Files.App.Storage
{
[DebuggerDisplay("{" + nameof(ToString) + "()}")]
public sealed class WindowsFolder : WindowsStorable, IChildFolder
public unsafe class WindowsFolder : WindowsStorable, IChildFolder
{
public WindowsFolder(ComPtr<IShellItem> nativeObject)
internal IContextMenu* NewMenuPtr
{
ThisPtr = nativeObject;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}

internal IContextMenu* ContextMenuPtr
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}

public unsafe WindowsFolder(IShellItem* nativeObject)
public WindowsFolder(IShellItem* ptr)
{
ComPtr<IShellItem> ptr = default;
ptr.Attach(nativeObject);
ThisPtr = ptr;
}

public unsafe WindowsFolder(Guid folderId)
public WindowsFolder(Guid folderId)
{
ComPtr<IShellItem> pItem = default;
IShellItem* pItem = default;

HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, IID.IID_IShellItem, (void**)pItem.GetAddressOf());
HRESULT hr = PInvoke.SHGetKnownFolderItem(&folderId, KNOWN_FOLDER_FLAG.KF_FLAG_DEFAULT, HANDLE.Null, IID.IID_IShellItem, (void**)&pItem);
if (hr.Failed)
{
fixed (char* pszShellPath = $"Shell:::{folderId:B}")
hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, IID.IID_IShellItem, (void**)pItem.GetAddressOf());
hr = PInvoke.SHCreateItemFromParsingName(pszShellPath, null, IID.IID_IShellItem, (void**)&pItem);

// Invalid FOLDERID; this should never happen.
hr.ThrowOnFailure();
Expand All @@ -43,47 +54,35 @@ public unsafe WindowsFolder(Guid folderId)

public IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type = StorableType.All, CancellationToken cancellationToken = default)
{
return GetItems().ToAsyncEnumerable();
ComPtr<IEnumShellItems> pEnumShellItems = default;
HRESULT hr = ThisPtr->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf());
if (hr.ThrowIfFailedOnDebug().Failed)
return Enumerable.Empty<IStorableChild>().ToAsyncEnumerable();

unsafe IEnumerable<IStorableChild> GetItems()
List<IStorableChild> items = [];
IShellItem* pShellItem = default;
while (pEnumShellItems.Get()->Next(1, &pShellItem).Succeeded && pShellItem is not null)
{
ComPtr<IEnumShellItems> pEnumShellItems = default;
GetEnumerator();
cancellationToken.ThrowIfCancellationRequested();

ComPtr<IShellItem> pShellItem = default;
while (GetNext() && !pShellItem.IsNull)
{
cancellationToken.ThrowIfCancellationRequested();
var isFolder = pShellItem.HasShellAttributes(SFGAO_FLAGS.SFGAO_FOLDER);
var isFolder = pShellItem->GetAttributes(SFGAO_FLAGS.SFGAO_FOLDER, out var returnedAttributes).Succeeded &&
returnedAttributes == SFGAO_FLAGS.SFGAO_FOLDER;

if (type is StorableType.File && !isFolder)
{
yield return new WindowsFile(pShellItem);
}
else if (type is StorableType.Folder && isFolder)
{
yield return new WindowsFolder(pShellItem);
}
else
{
continue;
}
if (type is StorableType.File && !isFolder)
{
items.Add(new WindowsFile(pShellItem));
}

yield break;

unsafe void GetEnumerator()
else if (type is StorableType.Folder && isFolder)
{
HRESULT hr = ThisPtr.Get()->BindToHandler(null, BHID.BHID_EnumItems, IID.IID_IEnumShellItems, (void**)pEnumShellItems.GetAddressOf());
hr.ThrowIfFailedOnDebug();
items.Add(new WindowsFolder(pShellItem));
}

unsafe bool GetNext()
else
{
HRESULT hr = pEnumShellItems.Get()->Next(1, pShellItem.GetAddressOf());
return hr.ThrowIfFailedOnDebug() == HRESULT.S_OK;
continue;
}
}

return items.ToAsyncEnumerable();
}
}
}
Loading
Loading