From 5f5d7237db4fe8b3f8f95c4c73adeb3d48f88e47 Mon Sep 17 00:00:00 2001 From: Dongle <29563098+dongle-the-gadget@users.noreply.github.com> Date: Sun, 2 Mar 2025 11:57:20 +0700 Subject: [PATCH 1/3] Report OneDrive size on 23H2. --- .../Utils/Storage/Helpers/DriveHelpers.cs | 62 +++++++++++++++++++ .../Properties/Items/DriveProperties.cs | 10 +++ 2 files changed, 72 insertions(+) diff --git a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs index 1a16a5351ecb..b78e614075ff 100644 --- a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs @@ -2,18 +2,25 @@ // Licensed under the MIT License. using DiscUtils.Udf; +using Files.App.Services.SizeProvider; using Microsoft.Management.Infrastructure; +using Microsoft.Win32; +using System.Runtime.InteropServices; using Windows.Devices.Enumeration; using Windows.Devices.Portable; using Windows.Storage; using Windows.Storage.FileProperties; +using Windows.Storage.Provider; using Windows.Win32; using Windows.Win32.Foundation; +using WinRT; namespace Files.App.Utils.Storage { public static class DriveHelpers { + private static readonly Guid IID_IStorageProviderStatusUISourceFactory = new Guid("12e46b74-4e5a-58d1-a62f-0376e8ee7dd8"); + public static async void EjectDeviceAsync(string path) { await ContextMenu.InvokeVerb("eject", path); @@ -168,5 +175,60 @@ public static async Task GetThumbnailAsync(StorageFolder f => (StorageItemThumbnail)await FilesystemTasks.Wrap(() => folder.GetThumbnailAsync(ThumbnailMode.SingleItem, 40, ThumbnailOptions.UseCurrentScale).AsTask() ); + + public static async Task<(bool Success, ulong Capacity, ulong Used)> GetSyncRootQuotaAsync(string path) + { + Windows.Storage.StorageFolder folder = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(path); + StorageProviderSyncRootInfo? syncRootInfo = null; + + try + { + syncRootInfo = StorageProviderSyncRootManager.GetSyncRootInformationForFolder(folder); + } + catch + { + return (false, 0, 0); + } + + RegistryKey? key; + if ((key = Registry.LocalMachine.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SyncRootManager\\{syncRootInfo.Id}")) is null) + { + return (false, 0, 0); + } + + using (key) + { + if (key.GetValue("StorageProviderStatusUISourceFactory") is string statusUIclass) + { + StorageProviderStatusUI statusUI; + + unsafe + { + if (PInvoke.CoCreateInstance(Guid.Parse(statusUIclass), null, Windows.Win32.System.Com.CLSCTX.CLSCTX_LOCAL_SERVER, IID_IStorageProviderStatusUISourceFactory, out void* statusUISourceFactoryAbi) != 0) + { + return (false, 0, 0); + } + + // CsWinRT wrappers won't work. + // TODO: look to replace MarshalString with MarshalString.Pinnable? + + nint statusUISourceAbi = 0; + nint syncRootIdHstring = MarshalString.FromManaged(syncRootInfo.Id); + nint statusUIAbi = 0; + ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[MemberFunction])(*(IntPtr*)((nint)(*(IntPtr*)statusUISourceFactoryAbi) + (nint)6 * (nint)sizeof(delegate* unmanaged[Stdcall]))))((nint)statusUISourceFactoryAbi, syncRootIdHstring, &statusUISourceAbi)); + ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[MemberFunction])(*(IntPtr*)((nint)(*(IntPtr*)statusUISourceAbi) + (nint)6 * (nint)sizeof(delegate* unmanaged[Stdcall]))))(statusUISourceAbi, &statusUIAbi)); + statusUI = StorageProviderStatusUI.FromAbi(statusUIAbi); + Marshal.Release(statusUISourceAbi); + Marshal.Release((nint)statusUISourceFactoryAbi); + MarshalString.DisposeAbi(statusUISourceAbi); + } + return (true, statusUI.QuotaUI.QuotaTotalInBytes, statusUI.QuotaUI.QuotaUsedInBytes); + } + else + { + return (false, 0, 0); + } + } + } } } \ No newline at end of file diff --git a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs index 750ac63b8ed0..70f5fdbf31b1 100644 --- a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs @@ -45,6 +45,7 @@ public override void GetBaseProperties() public async override Task GetSpecialPropertiesAsync() { ViewModel.ItemAttributesVisibility = false; + var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(Drive.Path)); BaseStorageFolder diskRoot = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(Drive.Path, item)); @@ -77,6 +78,15 @@ public async override Task GetSpecialPropertiesAsync() return; } + var syncRootStatus = await DriveHelpers.GetSyncRootQuotaAsync(Drive.Path); + if (syncRootStatus.Success) + { + ViewModel.DriveCapacityValue = syncRootStatus.Capacity; + ViewModel.DriveUsedSpaceValue = syncRootStatus.Used; + ViewModel.DriveFreeSpaceValue = syncRootStatus.Capacity - syncRootStatus.Used; + return; + } + try { string freeSpace = "System.FreeSpace"; From cf2b38c441e202c6bd34a149589e76c9786a1c4c Mon Sep 17 00:00:00 2001 From: Dongle <29563098+dongle-the-gadget@users.noreply.github.com> Date: Mon, 3 Mar 2025 00:03:47 +0700 Subject: [PATCH 2/3] Use ComPtr in sync root quota. --- global.json | 5 - src/Files.App.CsWin32/Windows.Win32.ComPtr.cs | 7 +- .../Utils/Storage/Helpers/DriveHelpers.cs | 62 ------- .../Utils/Storage/Helpers/SyncRootHelpers.cs | 151 ++++++++++++++++++ .../Properties/Items/DriveProperties.cs | 2 +- 5 files changed, 155 insertions(+), 72 deletions(-) delete mode 100644 global.json create mode 100644 src/Files.App/Utils/Storage/Helpers/SyncRootHelpers.cs diff --git a/global.json b/global.json deleted file mode 100644 index 62a1920fd79d..000000000000 --- a/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "8.0.303" - } -} diff --git a/src/Files.App.CsWin32/Windows.Win32.ComPtr.cs b/src/Files.App.CsWin32/Windows.Win32.ComPtr.cs index cbfeaa39e5d5..620d23ae2c0a 100644 --- a/src/Files.App.CsWin32/Windows.Win32.ComPtr.cs +++ b/src/Files.App.CsWin32/Windows.Win32.ComPtr.cs @@ -12,7 +12,7 @@ namespace Windows.Win32 /// /// Contains a COM pointer and a set of methods to work with the pointer safely. /// - public unsafe struct ComPtr : IDisposable where T : unmanaged + public unsafe struct ComPtr : IDisposable where T : unmanaged, IComIID { private T* _ptr; @@ -40,11 +40,10 @@ public ComPtr(T* ptr) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ComPtr As() where U : unmanaged + public readonly ComPtr As() where U : unmanaged, IComIID { ComPtr ptr = default; - Guid iid = typeof(U).GUID; - ((IUnknown*)_ptr)->QueryInterface(&iid, (void**)ptr.GetAddressOf()); + ((IUnknown*)_ptr)->QueryInterface((Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in U.Guid)), (void**)ptr.GetAddressOf()); return ptr; } diff --git a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs index b78e614075ff..1a16a5351ecb 100644 --- a/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/DriveHelpers.cs @@ -2,25 +2,18 @@ // Licensed under the MIT License. using DiscUtils.Udf; -using Files.App.Services.SizeProvider; using Microsoft.Management.Infrastructure; -using Microsoft.Win32; -using System.Runtime.InteropServices; using Windows.Devices.Enumeration; using Windows.Devices.Portable; using Windows.Storage; using Windows.Storage.FileProperties; -using Windows.Storage.Provider; using Windows.Win32; using Windows.Win32.Foundation; -using WinRT; namespace Files.App.Utils.Storage { public static class DriveHelpers { - private static readonly Guid IID_IStorageProviderStatusUISourceFactory = new Guid("12e46b74-4e5a-58d1-a62f-0376e8ee7dd8"); - public static async void EjectDeviceAsync(string path) { await ContextMenu.InvokeVerb("eject", path); @@ -175,60 +168,5 @@ public static async Task GetThumbnailAsync(StorageFolder f => (StorageItemThumbnail)await FilesystemTasks.Wrap(() => folder.GetThumbnailAsync(ThumbnailMode.SingleItem, 40, ThumbnailOptions.UseCurrentScale).AsTask() ); - - public static async Task<(bool Success, ulong Capacity, ulong Used)> GetSyncRootQuotaAsync(string path) - { - Windows.Storage.StorageFolder folder = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(path); - StorageProviderSyncRootInfo? syncRootInfo = null; - - try - { - syncRootInfo = StorageProviderSyncRootManager.GetSyncRootInformationForFolder(folder); - } - catch - { - return (false, 0, 0); - } - - RegistryKey? key; - if ((key = Registry.LocalMachine.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SyncRootManager\\{syncRootInfo.Id}")) is null) - { - return (false, 0, 0); - } - - using (key) - { - if (key.GetValue("StorageProviderStatusUISourceFactory") is string statusUIclass) - { - StorageProviderStatusUI statusUI; - - unsafe - { - if (PInvoke.CoCreateInstance(Guid.Parse(statusUIclass), null, Windows.Win32.System.Com.CLSCTX.CLSCTX_LOCAL_SERVER, IID_IStorageProviderStatusUISourceFactory, out void* statusUISourceFactoryAbi) != 0) - { - return (false, 0, 0); - } - - // CsWinRT wrappers won't work. - // TODO: look to replace MarshalString with MarshalString.Pinnable? - - nint statusUISourceAbi = 0; - nint syncRootIdHstring = MarshalString.FromManaged(syncRootInfo.Id); - nint statusUIAbi = 0; - ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[MemberFunction])(*(IntPtr*)((nint)(*(IntPtr*)statusUISourceFactoryAbi) + (nint)6 * (nint)sizeof(delegate* unmanaged[Stdcall]))))((nint)statusUISourceFactoryAbi, syncRootIdHstring, &statusUISourceAbi)); - ExceptionHelpers.ThrowExceptionForHR(((delegate* unmanaged[MemberFunction])(*(IntPtr*)((nint)(*(IntPtr*)statusUISourceAbi) + (nint)6 * (nint)sizeof(delegate* unmanaged[Stdcall]))))(statusUISourceAbi, &statusUIAbi)); - statusUI = StorageProviderStatusUI.FromAbi(statusUIAbi); - Marshal.Release(statusUISourceAbi); - Marshal.Release((nint)statusUISourceFactoryAbi); - MarshalString.DisposeAbi(statusUISourceAbi); - } - return (true, statusUI.QuotaUI.QuotaTotalInBytes, statusUI.QuotaUI.QuotaUsedInBytes); - } - else - { - return (false, 0, 0); - } - } - } } } \ No newline at end of file diff --git a/src/Files.App/Utils/Storage/Helpers/SyncRootHelpers.cs b/src/Files.App/Utils/Storage/Helpers/SyncRootHelpers.cs new file mode 100644 index 000000000000..16b17c2b5ca3 --- /dev/null +++ b/src/Files.App/Utils/Storage/Helpers/SyncRootHelpers.cs @@ -0,0 +1,151 @@ +using Microsoft.Win32; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Windows.Storage.Provider; +using Windows.Win32; +using Windows.Win32.Foundation; +using WinRT; + +namespace Files.App.Utils.Storage +{ + internal static class SyncRootHelpers + { + private unsafe struct IStorageProviderStatusUISourceFactory : IComIID + { + private void** vtbl; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HRESULT GetStatusUISource(nint syncRootId, IStorageProviderStatusUISource** result) + { + return ((delegate* unmanaged[Stdcall])vtbl[6])((IStorageProviderStatusUISourceFactory*)Unsafe.AsPointer(ref this), syncRootId, result); + } + + public static ref readonly Guid Guid + { + get + { + // 12e46b74-4e5a-58d1-a62f-0376e8ee7dd8 + ReadOnlySpan data = new byte[] + { + 0x74, 0x6b, 0xe4, 0x12, + 0x5a, 0x4e, + 0xd1, 0x58, + 0xa6, 0x2f, + 0x03, 0x76, 0xe8, 0xee, 0x7d, 0xd8 + }; + Debug.Assert(data.Length == sizeof(Guid)); + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + } + + private unsafe struct IStorageProviderStatusUISource : IComIID + { + private void** vtbl; + + public HRESULT GetStatusUI(IStorageProviderStatusUI** result) + { + return ((delegate* unmanaged[Stdcall])vtbl[6])((IStorageProviderStatusUISource*)Unsafe.AsPointer(ref this), result); + } + + public static ref readonly Guid Guid + { + get + { + // a306c249-3d66-5e70-9007-e43df96051ff + ReadOnlySpan data = new byte[] + { + 0x49, 0xc2, 0x06, 0xa3, + 0x66, 0x3d, + 0x70, 0x5e, + 0x90, 0x07, + 0xe4, 0x3d, 0xf9, 0x60, 0x51, 0xff + }; + Debug.Assert(data.Length == sizeof(Guid)); + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + } + + private unsafe struct IStorageProviderStatusUI : IComIID + { + public static ref readonly Guid Guid + { + get + { + // d6b6a758-198d-5b80-977f-5ff73da33118 + ReadOnlySpan data = new byte[] + { + 0x58, 0xa7, 0xb6, 0xd6, + 0x8d, 0x19, + 0x80, 0x5b, + 0x97, 0x7f, + 0x5f, 0xf7, 0x3d, 0xa3, 0x31, 0x18 + }; + Debug.Assert(data.Length == sizeof(Guid)); + return ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + } + } + } + + private static unsafe (bool Success, ulong Capacity, ulong Used) GetSyncRootQuotaFromSyncRootId(string syncRootId) + { + RegistryKey? key; + if ((key = Registry.LocalMachine.OpenSubKey($"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\SyncRootManager\\{syncRootId}")) is null) + { + return (false, 0, 0); + } + + using (key) + { + if (key.GetValue("StorageProviderStatusUISourceFactory") is string statusUIclass) + { + StorageProviderStatusUI statusUI; + using (ComPtr sourceFactoryNative = default) + { + Guid statusUIclassGuid = Guid.Parse(statusUIclass); + if (PInvoke.CoCreateInstance(&statusUIclassGuid, null, Windows.Win32.System.Com.CLSCTX.CLSCTX_LOCAL_SERVER, (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IStorageProviderStatusUISourceFactory.Guid)), (void**)sourceFactoryNative.GetAddressOf()) != 0) + { + return (false, 0, 0); + } + + MarshalString.Pinnable syncRootIdHstring = new(syncRootId); + fixed (char* ptr = syncRootIdHstring) + using (ComPtr sourceNative = default) + { + ExceptionHelpers.ThrowExceptionForHR(sourceFactoryNative.Get()->GetStatusUISource(syncRootIdHstring.GetAbi(), sourceNative.GetAddressOf())); + + using (ComPtr statusNative = default) + { + ExceptionHelpers.ThrowExceptionForHR(sourceNative.Get()->GetStatusUI(statusNative.GetAddressOf())); + statusUI = StorageProviderStatusUI.FromAbi((nint)statusNative.Get()); + } + } + } + return (true, statusUI.QuotaUI.QuotaTotalInBytes, statusUI.QuotaUI.QuotaUsedInBytes); + } + else + { + return (false, 0, 0); + } + } + } + + public static async Task<(bool Success, ulong Capacity, ulong Used)> GetSyncRootQuotaAsync(string path) + { + Windows.Storage.StorageFolder folder = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(path); + StorageProviderSyncRootInfo? syncRootInfo = null; + + try + { + syncRootInfo = StorageProviderSyncRootManager.GetSyncRootInformationForFolder(folder); + } + catch + { + return (false, 0, 0); + } + + return GetSyncRootQuotaFromSyncRootId(syncRootInfo.Id); + } + } +} diff --git a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs index 70f5fdbf31b1..c07138a6ce33 100644 --- a/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/DriveProperties.cs @@ -78,7 +78,7 @@ public async override Task GetSpecialPropertiesAsync() return; } - var syncRootStatus = await DriveHelpers.GetSyncRootQuotaAsync(Drive.Path); + var syncRootStatus = await SyncRootHelpers.GetSyncRootQuotaAsync(Drive.Path); if (syncRootStatus.Success) { ViewModel.DriveCapacityValue = syncRootStatus.Capacity; From 5205298683d5f0d7d952e01815ddc9a3b207664e Mon Sep 17 00:00:00 2001 From: Dongle <29563098+dongle-the-gadget@users.noreply.github.com> Date: Mon, 3 Mar 2025 00:05:50 +0700 Subject: [PATCH 3/3] Revert global.json delete (mistake) --- global.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 000000000000..6e063330b0d1 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "8.0.303" + } +} \ No newline at end of file