From 31c31c843abbc80f6319c8a33c726466afb1ec70 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:40:26 +0900 Subject: [PATCH 01/14] Init --- src/Files.App/Constants.cs | 5 +++++ .../Data/Models/PinnedFoldersManager.cs | 4 ++-- src/Files.App/Services/QuickAccessService.cs | 22 +++++++++---------- .../Utils/Global/QuickAccessManager.cs | 2 -- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Files.App/Constants.cs b/src/Files.App/Constants.cs index f6498a4e2138..c907e055030a 100644 --- a/src/Files.App/Constants.cs +++ b/src/Files.App/Constants.cs @@ -210,6 +210,11 @@ public static class Actions public const int MaxSelectedItems = 5; } + public static class CLID + { + public static readonly string QuickAccess = "::{679f85cb-0220-4080-b29b-5540cc05aab6}"; + } + public static class UserEnvironmentPaths { public static readonly string DesktopPath = Windows.Storage.UserDataPaths.GetDefault().Desktop; diff --git a/src/Files.App/Data/Models/PinnedFoldersManager.cs b/src/Files.App/Data/Models/PinnedFoldersManager.cs index 58208198a78e..9cf66247e4a2 100644 --- a/src/Files.App/Data/Models/PinnedFoldersManager.cs +++ b/src/Files.App/Data/Models/PinnedFoldersManager.cs @@ -9,7 +9,7 @@ namespace Files.App.Data.Models { public sealed class PinnedFoldersManager { - private IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); + private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public EventHandler? DataChanged; @@ -160,7 +160,7 @@ private void AddLocationItemToSidebar(LocationItem locationItem) /// public async Task AddAllItemsToSidebarAsync() { - if (userSettingsService.GeneralSettingsService.ShowPinnedSection) + if (UserSettingsService.GeneralSettingsService.ShowPinnedSection) foreach (string path in PinnedFolders) await AddItemToSidebarAsync(path); } diff --git a/src/Files.App/Services/QuickAccessService.cs b/src/Files.App/Services/QuickAccessService.cs index 5d85503740e2..3cdd0d0b153e 100644 --- a/src/Files.App/Services/QuickAccessService.cs +++ b/src/Files.App/Services/QuickAccessService.cs @@ -1,25 +1,22 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Utils.Shell; -using Files.App.UserControls.Widgets; - namespace Files.App.Services { internal sealed class QuickAccessService : IQuickAccessService { - private readonly static string guid = "::{679f85cb-0220-4080-b29b-5540cc05aab6}"; - public async Task> GetPinnedFoldersAsync() { - var result = (await Win32Helper.GetShellFolderAsync(guid, false, true, 0, int.MaxValue, "System.Home.IsPinned")).Enumerate + var result = (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")).Enumerate .Where(link => link.IsFolder); return result; } - public Task PinToSidebarAsync(string folderPath) => PinToSidebarAsync(new[] { folderPath }); + public Task PinToSidebarAsync(string folderPath) + => PinToSidebarAsync([ folderPath ]); - public Task PinToSidebarAsync(string[] folderPaths) => PinToSidebarAsync(folderPaths, true); + public Task PinToSidebarAsync(string[] folderPaths) + => PinToSidebarAsync(folderPaths, true); private async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget) { @@ -27,19 +24,22 @@ private async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAcc await ContextMenu.InvokeVerb("pintohome", [folderPath]); await App.QuickAccessManager.Model.LoadAsync(); + if (doUpdateQuickAccessWidget) App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); } - public Task UnpinFromSidebarAsync(string folderPath) => UnpinFromSidebarAsync(new[] { folderPath }); + public Task UnpinFromSidebarAsync(string folderPath) + => UnpinFromSidebarAsync([ folderPath ]); - public Task UnpinFromSidebarAsync(string[] folderPaths) => UnpinFromSidebarAsync(folderPaths, true); + public Task UnpinFromSidebarAsync(string[] folderPaths) + => UnpinFromSidebarAsync(folderPaths, true); private async Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget) { Type? shellAppType = Type.GetTypeFromProgID("Shell.Application"); object? shell = Activator.CreateInstance(shellAppType); - dynamic? f2 = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, [$"shell:{guid}"]); + dynamic? f2 = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, [$"shell:{Constants.CLID.QuickAccess}"]); if (folderPaths.Length == 0) folderPaths = (await GetPinnedFoldersAsync()) diff --git a/src/Files.App/Utils/Global/QuickAccessManager.cs b/src/Files.App/Utils/Global/QuickAccessManager.cs index 622e88e32de0..aaa5b55ba869 100644 --- a/src/Files.App/Utils/Global/QuickAccessManager.cs +++ b/src/Files.App/Utils/Global/QuickAccessManager.cs @@ -1,8 +1,6 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Services; -using Files.App.UserControls.Widgets; using System.IO; namespace Files.App.Utils From 01ae62564039043474e3c43cfdea1d03e2ea053e Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:46:05 +0900 Subject: [PATCH 02/14] Update --- .../Sidebar/PinFolderToSidebarAction.cs | 2 +- .../Sidebar/UnpinFolderToSidebarAction.cs | 2 +- .../Data/Contracts/IQuickAccessService.cs | 18 ++------------- .../Data/Models/PinnedFoldersManager.cs | 2 -- src/Files.App/Services/QuickAccessService.cs | 23 ++++++------------- .../Widgets/QuickAccessWidget.xaml.cs | 4 ++-- .../Utils/Global/QuickAccessManager.cs | 7 +++--- .../UserControls/SidebarViewModel.cs | 4 ++-- .../Widgets/BaseWidgetViewModel.cs | 4 ++-- 9 files changed, 20 insertions(+), 46 deletions(-) diff --git a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs index 835701c1a9bf..ae68ffdb898c 100644 --- a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs @@ -41,7 +41,7 @@ public async Task ExecuteAsync() } else if (context.Folder is not null) { - await service.PinToSidebarAsync(context.Folder.ItemPath); + await service.PinToSidebarAsync([context.Folder.ItemPath]); } } diff --git a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs index ae15537323e8..335de0cf9c1b 100644 --- a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs @@ -38,7 +38,7 @@ public async Task ExecuteAsync() } else if (context.Folder is not null) { - await service.UnpinFromSidebarAsync(context.Folder.ItemPath); + await service.UnpinFromSidebarAsync([context.Folder.ItemPath]); } } diff --git a/src/Files.App/Data/Contracts/IQuickAccessService.cs b/src/Files.App/Data/Contracts/IQuickAccessService.cs index 1608146c6e30..254db4c3142f 100644 --- a/src/Files.App/Data/Contracts/IQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IQuickAccessService.cs @@ -11,33 +11,19 @@ public interface IQuickAccessService /// Task> GetPinnedFoldersAsync(); - /// - /// Pins a folder to the quick access list - /// - /// The folder to pin - /// - Task PinToSidebarAsync(string folderPath); - /// /// Pins folders to the quick access list /// /// The array of folders to pin /// - Task PinToSidebarAsync(string[] folderPaths); - - /// - /// Unpins a folder from the quick access list - /// - /// The folder to unpin - /// - Task UnpinFromSidebarAsync(string folderPath); + Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true); /// /// Unpins folders from the quick access list /// /// The array of folders to unpin /// - Task UnpinFromSidebarAsync(string[] folderPaths); + Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true); /// /// Checks if a folder is pinned to the quick access list diff --git a/src/Files.App/Data/Models/PinnedFoldersManager.cs b/src/Files.App/Data/Models/PinnedFoldersManager.cs index 9cf66247e4a2..d3c3c641a87d 100644 --- a/src/Files.App/Data/Models/PinnedFoldersManager.cs +++ b/src/Files.App/Data/Models/PinnedFoldersManager.cs @@ -19,8 +19,6 @@ public sealed class PinnedFoldersManager public List PinnedFolders { get; set; } = []; public readonly List _PinnedFolderItems = []; - - [JsonIgnore] public IReadOnlyList PinnedFolderItems { get diff --git a/src/Files.App/Services/QuickAccessService.cs b/src/Files.App/Services/QuickAccessService.cs index 3cdd0d0b153e..3bab1e269a01 100644 --- a/src/Files.App/Services/QuickAccessService.cs +++ b/src/Files.App/Services/QuickAccessService.cs @@ -7,18 +7,15 @@ internal sealed class QuickAccessService : IQuickAccessService { public async Task> GetPinnedFoldersAsync() { - var result = (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")).Enumerate - .Where(link => link.IsFolder); + var result = + (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")) + .Enumerate + .Where(link => link.IsFolder); + return result; } - public Task PinToSidebarAsync(string folderPath) - => PinToSidebarAsync([ folderPath ]); - - public Task PinToSidebarAsync(string[] folderPaths) - => PinToSidebarAsync(folderPaths, true); - - private async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget) + public async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true) { foreach (string folderPath in folderPaths) await ContextMenu.InvokeVerb("pintohome", [folderPath]); @@ -29,13 +26,7 @@ private async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAcc App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); } - public Task UnpinFromSidebarAsync(string folderPath) - => UnpinFromSidebarAsync([ folderPath ]); - - public Task UnpinFromSidebarAsync(string[] folderPaths) - => UnpinFromSidebarAsync(folderPaths, true); - - private async Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget) + public async Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true) { Type? shellAppType = Type.GetTypeFromProgID("Shell.Application"); object? shell = Activator.CreateInstance(shellAppType); diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index e3f9b4736895..603835c28b05 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -304,7 +304,7 @@ private void OpenProperties(WidgetFolderCardItem item) public override async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinToSidebarAsync(item.Path); + await QuickAccessService.PinToSidebarAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); @@ -323,7 +323,7 @@ public override async Task PinToSidebarAsync(WidgetCardItem item) public override async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFromSidebarAsync(item.Path); + await QuickAccessService.UnpinFromSidebarAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); } diff --git a/src/Files.App/Utils/Global/QuickAccessManager.cs b/src/Files.App/Utils/Global/QuickAccessManager.cs index aaa5b55ba869..b37e52aaabc3 100644 --- a/src/Files.App/Utils/Global/QuickAccessManager.cs +++ b/src/Files.App/Utils/Global/QuickAccessManager.cs @@ -7,19 +7,18 @@ namespace Files.App.Utils { public sealed class QuickAccessManager { + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public FileSystemWatcher? PinnedItemsWatcher; public event FileSystemEventHandler? PinnedItemsModified; public EventHandler? UpdateQuickAccessWidget; - public IQuickAccessService QuickAccessService; + public PinnedFoldersManager Model = new(); - public PinnedFoldersManager Model; public QuickAccessManager() { - QuickAccessService = Ioc.Default.GetRequiredService(); - Model = new(); Initialize(); } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 53e42b94ced2..3af1b7317e9c 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -848,7 +848,7 @@ private void PinItem() private void UnpinItem() { if (rightClickedItem.Section == SectionType.Pinned || rightClickedItem is DriveItem) - _ = QuickAccessService.UnpinFromSidebarAsync(rightClickedItem.Path); + _ = QuickAccessService.UnpinFromSidebarAsync([rightClickedItem.Path]); } private void HideSection() @@ -1265,7 +1265,7 @@ private async Task HandleLocationItemDroppedAsync(LocationItem locationItem, Ite foreach (var item in storageItems) { if (item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.PinnedFolders.Contains(item.Path)) - await QuickAccessService.PinToSidebarAsync(item.Path); + await QuickAccessService.PinToSidebarAsync([item.Path]); } } else diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index fd59058b217b..5b550fcd1d20 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -103,12 +103,12 @@ public async Task OpenInNewWindowAsync(WidgetCardItem item) public virtual async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinToSidebarAsync(item.Path); + await QuickAccessService.PinToSidebarAsync([item.Path]); } public virtual async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFromSidebarAsync(item.Path); + await QuickAccessService.UnpinFromSidebarAsync([item.Path]); } protected void OnRightClickedItemChanged(WidgetCardItem? item, CommandBarFlyout? flyout) From 46aee8178b127d182614c61ef6b2d8b2ca114b2f Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 05:29:24 +0900 Subject: [PATCH 03/14] Remove others --- .../Sidebar/PinFolderToSidebarAction.cs | 15 +- .../Sidebar/UnpinFolderToSidebarAction.cs | 4 +- src/Files.App/App.xaml.cs | 2 - .../Data/Contexts/SideBar/SideBarContext.cs | 4 +- .../Data/Contracts/IQuickAccessService.cs | 56 +++- src/Files.App/Data/Items/DriveItem.cs | 4 +- src/Files.App/Data/Items/ListedItem.cs | 13 +- src/Files.App/Data/Items/LocationItem.cs | 4 +- .../Data/Models/PinnedFoldersManager.cs | 204 ------------- .../Helpers/Application/AppLifecycleHelper.cs | 4 +- src/Files.App/Services/QuickAccessService.cs | 287 ++++++++++++++++-- .../Services/WindowsJumpListService.cs | 8 +- .../Widgets/FileTagsWidget.xaml.cs | 2 +- .../Widgets/QuickAccessWidget.xaml.cs | 11 +- .../Widgets/RecentFilesWidget.xaml.cs | 2 +- .../Utils/Global/QuickAccessManager.cs | 51 ---- .../ReorderSidebarItemsDialogViewModel.cs | 10 +- .../UserControls/SidebarViewModel.cs | 20 +- .../Widgets/BaseWidgetViewModel.cs | 2 +- .../Views/Layouts/ColumnsLayoutPage.xaml.cs | 6 +- 20 files changed, 372 insertions(+), 337 deletions(-) delete mode 100644 src/Files.App/Data/Models/PinnedFoldersManager.cs delete mode 100644 src/Files.App/Utils/Global/QuickAccessManager.cs diff --git a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs index ae68ffdb898c..63ff414b1868 100644 --- a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs @@ -7,8 +7,8 @@ namespace Files.App.Actions { internal sealed class PinFolderToSidebarAction : ObservableObject, IAction { - private readonly IContentPageContext context; - private readonly IQuickAccessService service; + private IContentPageContext context { get; } = Ioc.Default.GetRequiredService(); + private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public string Label => "PinFolderToSidebar".GetLocalizedResource(); @@ -24,11 +24,8 @@ public bool IsExecutable public PinFolderToSidebarAction() { - context = Ioc.Default.GetRequiredService(); - service = Ioc.Default.GetRequiredService(); - context.PropertyChanged += Context_PropertyChanged; - App.QuickAccessManager.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; + QuickAccessService.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; } public async Task ExecuteAsync() @@ -37,17 +34,17 @@ public async Task ExecuteAsync() { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await service.PinToSidebarAsync(items); + await QuickAccessService.PinToSidebarAsync(items); } else if (context.Folder is not null) { - await service.PinToSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.PinToSidebarAsync([context.Folder.ItemPath]); } } private bool GetIsExecutable() { - string[] pinnedFolders = [.. App.QuickAccessManager.Model.PinnedFolders]; + string[] pinnedFolders = [.. QuickAccessService.PinnedFolders]; return context.HasSelection ? context.SelectedItems.All(IsPinnable) diff --git a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs index 335de0cf9c1b..0aba4e80ce06 100644 --- a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs @@ -26,7 +26,7 @@ public UnpinFolderFromSidebarAction() service = Ioc.Default.GetRequiredService(); context.PropertyChanged += Context_PropertyChanged; - App.QuickAccessManager.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; + QuickAccessService.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; } public async Task ExecuteAsync() @@ -44,7 +44,7 @@ public async Task ExecuteAsync() private bool GetIsExecutable() { - string[] pinnedFolders = [.. App.QuickAccessManager.Model.PinnedFolders]; + string[] pinnedFolders = [.. QuickAccessService.PinnedFolders]; return context.HasSelection ? context.SelectedItems.All(IsPinned) diff --git a/src/Files.App/App.xaml.cs b/src/Files.App/App.xaml.cs index 5895b10b0fb3..b216a7b686f1 100644 --- a/src/Files.App/App.xaml.cs +++ b/src/Files.App/App.xaml.cs @@ -37,7 +37,6 @@ public static CommandBarFlyout? LastOpenedFlyout } // TODO: Replace with DI - public static QuickAccessManager QuickAccessManager { get; private set; } = null!; public static StorageHistoryWrapper HistoryWrapper { get; private set; } = null!; public static FileTagsManager FileTagsManager { get; private set; } = null!; public static RecentItems RecentItemsManager { get; private set; } = null!; @@ -112,7 +111,6 @@ async Task ActivateAsync() } // TODO: Replace with DI - QuickAccessManager = Ioc.Default.GetRequiredService(); HistoryWrapper = Ioc.Default.GetRequiredService(); FileTagsManager = Ioc.Default.GetRequiredService(); RecentItemsManager = Ioc.Default.GetRequiredService(); diff --git a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs index b8412d9f8e3a..2b03f12a7711 100644 --- a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs +++ b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs @@ -6,11 +6,11 @@ namespace Files.App.Data.Contexts /// internal sealed class SidebarContext : ObservableObject, ISidebarContext { - private readonly PinnedFoldersManager favoriteModel = App.QuickAccessManager.Model; + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private int PinnedFolderItemIndex => IsItemRightClicked - ? favoriteModel.IndexOfItem(_RightClickedItem!) + ? QuickAccessService.IndexOfItem(_RightClickedItem!) : -1; private INavigationControlItem? _RightClickedItem = null; diff --git a/src/Files.App/Data/Contracts/IQuickAccessService.cs b/src/Files.App/Data/Contracts/IQuickAccessService.cs index 254db4c3142f..d4cb25a273ee 100644 --- a/src/Files.App/Data/Contracts/IQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IQuickAccessService.cs @@ -1,10 +1,22 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using System.Collections.Specialized; + namespace Files.App.Data.Contracts { public interface IQuickAccessService { + event EventHandler? DataChanged; + + event SystemIO.FileSystemEventHandler? PinnedItemsModified; + + event EventHandler? UpdateQuickAccessWidget; + + IReadOnlyList PinnedFolderItems { get; } + + Task InitializeAsync(); + /// /// Gets the list of quick access items /// @@ -16,27 +28,63 @@ public interface IQuickAccessService /// /// The array of folders to pin /// - Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true); + Task PinToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Unpins folders from the quick access list /// /// The array of folders to unpin /// - Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true); + Task UnpinFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Checks if a folder is pinned to the quick access list /// /// The path of the folder /// true if the item is pinned - bool IsItemPinned(string folderPath); + bool IsPinnedToSidebar(string folderPath); /// /// Saves a state of pinned folder items in the sidebar /// /// The array of items to save /// - Task SaveAsync(string[] items); + Task NotifyPinnedItemsChangesAsync(string[] items); + + /// + /// Updates items with the pinned items from the explorer sidebar + /// + Task UpdateItemsWithExplorerAsync(); + + /// + /// Returns the index of the location item in the navigation sidebar + /// + /// The location item + /// Index of the item + int IndexOfItem(INavigationControlItem locationItem); + + /// + /// CreateLocationItemFromPathAsync + /// + /// + /// + Task CreateLocationItemFromPathAsync(string path); + + /// + /// Adds the item (from a path) to the navigation sidebar + /// + /// The path which to save + /// Task + Task AddItemToSidebarAsync(string path); + + /// + /// Adds all items to the navigation sidebar + /// + Task AddAllItemsToSidebarAsync(); + + /// + /// Removes stale items in the navigation sidebar + /// + void RemoveStaleSidebarItems(); } } diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index 1e12528b3832..335eee80965e 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -13,6 +13,8 @@ namespace Files.App.Data.Items { public sealed class DriveItem : ObservableObject, INavigationControlItem, ILocatableFolder { + private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private BitmapImage icon; public BitmapImage Icon { @@ -48,7 +50,7 @@ public bool IsNetwork => Type == DriveType.Network; public bool IsPinned - => App.QuickAccessManager.Model.PinnedFolders.Contains(path); + => QuickAccessService.PinnedFolders.Contains(path); public string MaxSpaceText => MaxSpace.ToSizeString(); diff --git a/src/Files.App/Data/Items/ListedItem.cs b/src/Files.App/Data/Items/ListedItem.cs index d7deb502397c..8e47a0beae21 100644 --- a/src/Files.App/Data/Items/ListedItem.cs +++ b/src/Files.App/Data/Items/ListedItem.cs @@ -16,13 +16,12 @@ namespace Files.App.Utils { public class ListedItem : ObservableObject, IGroupableItem { - protected static IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + protected IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + protected IStartMenuService StartMenuService { get; } = Ioc.Default.GetRequiredService(); + protected IFileTagsSettingsService fileTagsSettingsService { get; } = Ioc.Default.GetRequiredService(); + protected IDateTimeFormatter dateTimeFormatter { get; } = Ioc.Default.GetRequiredService(); + protected IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); - protected static IStartMenuService StartMenuService { get; } = Ioc.Default.GetRequiredService(); - - protected static readonly IFileTagsSettingsService fileTagsSettingsService = Ioc.Default.GetRequiredService(); - - protected static readonly IDateTimeFormatter dateTimeFormatter = Ioc.Default.GetRequiredService(); public bool IsHiddenItem { get; set; } = false; @@ -368,7 +367,7 @@ public override string ToString() public bool IsGitItem => this is GitItem; public virtual bool IsExecutable => FileExtensionHelpers.IsExecutableFile(ItemPath); public virtual bool IsScriptFile => FileExtensionHelpers.IsScriptFile(ItemPath); - public bool IsPinned => App.QuickAccessManager.Model.PinnedFolders.Contains(itemPath); + public bool IsPinned => QuickAccessService.PinnedFolders.Contains(itemPath); public bool IsDriveRoot => ItemPath == PathNormalization.GetPathRoot(ItemPath); public bool IsElevationRequired { get; set; } diff --git a/src/Files.App/Data/Items/LocationItem.cs b/src/Files.App/Data/Items/LocationItem.cs index 6994d3f18a36..8d64a2f20551 100644 --- a/src/Files.App/Data/Items/LocationItem.cs +++ b/src/Files.App/Data/Items/LocationItem.cs @@ -10,6 +10,8 @@ namespace Files.App.Data.Items { public class LocationItem : ObservableObject, INavigationControlItem { + private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public BitmapImage icon; public BitmapImage Icon { @@ -79,7 +81,7 @@ public bool IsExpanded public bool IsInvalid { get; set; } = false; - public bool IsPinned => App.QuickAccessManager.Model.PinnedFolders.Contains(path); + public bool IsPinned => QuickAccessService.PinnedFolders.Contains(path); public SectionType Section { get; set; } diff --git a/src/Files.App/Data/Models/PinnedFoldersManager.cs b/src/Files.App/Data/Models/PinnedFoldersManager.cs deleted file mode 100644 index d3c3c641a87d..000000000000 --- a/src/Files.App/Data/Models/PinnedFoldersManager.cs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System.Collections.Specialized; -using System.IO; -using System.Text.Json.Serialization; - -namespace Files.App.Data.Models -{ - public sealed class PinnedFoldersManager - { - private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); - - public EventHandler? DataChanged; - - private readonly SemaphoreSlim addSyncSemaphore = new(1, 1); - - public List PinnedFolders { get; set; } = []; - - public readonly List _PinnedFolderItems = []; - public IReadOnlyList PinnedFolderItems - { - get - { - lock (_PinnedFolderItems) - return _PinnedFolderItems.ToList().AsReadOnly(); - } - } - - /// - /// Updates items with the pinned items from the explorer sidebar - /// - public async Task UpdateItemsWithExplorerAsync() - { - await addSyncSemaphore.WaitAsync(); - - try - { - PinnedFolders = (await QuickAccessService.GetPinnedFoldersAsync()) - .Where(link => (bool?)link.Properties["System.Home.IsPinned"] ?? false) - .Select(link => link.FilePath).ToList(); - RemoveStaleSidebarItems(); - await AddAllItemsToSidebarAsync(); - } - finally - { - addSyncSemaphore.Release(); - } - } - - /// - /// Returns the index of the location item in the navigation sidebar - /// - /// The location item - /// Index of the item - public int IndexOfItem(INavigationControlItem locationItem) - { - lock (_PinnedFolderItems) - { - return _PinnedFolderItems.FindIndex(x => x.Path == locationItem.Path); - } - } - - public async Task CreateLocationItemFromPathAsync(string path) - { - var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(path)); - var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, item)); - LocationItem locationItem; - - if (string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase)) - locationItem = LocationItem.Create(); - else - { - locationItem = LocationItem.Create(); - - if (path.Equals(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase)) - locationItem.Text = "ThisPC".GetLocalizedResource(); - else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) - locationItem.Text = "Network".GetLocalizedResource(); - } - - locationItem.Path = path; - locationItem.Section = SectionType.Pinned; - locationItem.MenuOptions = new ContextMenuOptions - { - IsLocationItem = true, - ShowProperties = true, - ShowUnpinItem = true, - ShowShellItems = true, - ShowEmptyRecycleBin = string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) - }; - locationItem.IsDefaultLocation = false; - locationItem.Text = res.Result?.DisplayName ?? Path.GetFileName(path.TrimEnd('\\')); - - if (res) - { - locationItem.IsInvalid = false; - if (res && res.Result is not null) - { - var result = await FileThumbnailHelper.GetIconAsync( - res.Result.Path, - Constants.ShellIconSizes.Small, - true, - IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); - - locationItem.IconData = result; - - var bitmapImage = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal); - if (bitmapImage is not null) - locationItem.Icon = bitmapImage; - } - } - else - { - locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); - locationItem.IsInvalid = true; - Debug.WriteLine($"Pinned item was invalid {res.ErrorCode}, item: {path}"); - } - - return locationItem; - } - - /// - /// Adds the item (from a path) to the navigation sidebar - /// - /// The path which to save - /// Task - public async Task AddItemToSidebarAsync(string path) - { - var locationItem = await CreateLocationItemFromPathAsync(path); - - AddLocationItemToSidebar(locationItem); - } - - /// - /// Adds the location item to the navigation sidebar - /// - /// The location item which to save - private void AddLocationItemToSidebar(LocationItem locationItem) - { - int insertIndex = -1; - lock (_PinnedFolderItems) - { - if (_PinnedFolderItems.Any(x => x.Path == locationItem.Path)) - return; - - var lastItem = _PinnedFolderItems.LastOrDefault(x => x.ItemType is NavigationControlItemType.Location); - insertIndex = lastItem is not null ? _PinnedFolderItems.IndexOf(lastItem) + 1 : 0; - _PinnedFolderItems.Insert(insertIndex, locationItem); - } - - DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, locationItem, insertIndex)); - } - - /// - /// Adds all items to the navigation sidebar - /// - public async Task AddAllItemsToSidebarAsync() - { - if (UserSettingsService.GeneralSettingsService.ShowPinnedSection) - foreach (string path in PinnedFolders) - await AddItemToSidebarAsync(path); - } - - /// - /// Removes stale items in the navigation sidebar - /// - public void RemoveStaleSidebarItems() - { - // Remove unpinned items from PinnedFolderItems - foreach (var childItem in PinnedFolderItems) - { - if (childItem is LocationItem item && !item.IsDefaultLocation && !PinnedFolders.Contains(item.Path)) - { - lock (_PinnedFolderItems) - { - _PinnedFolderItems.Remove(item); - } - DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); - } - } - - // Remove unpinned items from sidebar - DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - public async void LoadAsync(object? sender, FileSystemEventArgs e) - { - App.QuickAccessManager.PinnedItemsWatcher.EnableRaisingEvents = false; - await LoadAsync(); - App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(null, new ModifyQuickAccessEventArgs((await QuickAccessService.GetPinnedFoldersAsync()).ToArray(), true) - { - Reset = true - }); - App.QuickAccessManager.PinnedItemsWatcher.EnableRaisingEvents = true; - } - - public async Task LoadAsync() - { - await UpdateItemsWithExplorerAsync(); - } - } -} diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index dab4c5ef124c..ffea5428eee7 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -67,6 +67,7 @@ public static async Task InitializeAppComponentsAsync() var addItemService = Ioc.Default.GetRequiredService(); var generalSettingsService = userSettingsService.GeneralSettingsService; var jumpListService = Ioc.Default.GetRequiredService(); + var quickAccessService = Ioc.Default.GetRequiredService(); // Start off a list of tasks we need to run before we can continue startup await Task.WhenAll( @@ -74,7 +75,7 @@ await Task.WhenAll( App.LibraryManager.UpdateLibrariesAsync(), OptionalTaskAsync(WSLDistroManager.UpdateDrivesAsync(), generalSettingsService.ShowWslSection), OptionalTaskAsync(App.FileTagsManager.UpdateFileTagsAsync(), generalSettingsService.ShowFileTagsSection), - App.QuickAccessManager.InitializeAsync() + quickAccessService.InitializeAsync() ); await Task.WhenAll( @@ -200,7 +201,6 @@ public static IHost ConfigureHost() .AddSingleton() .AddTransient() // Utilities - .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Files.App/Services/QuickAccessService.cs b/src/Files.App/Services/QuickAccessService.cs index 3bab1e269a01..47ca250c8bac 100644 --- a/src/Files.App/Services/QuickAccessService.cs +++ b/src/Files.App/Services/QuickAccessService.cs @@ -1,51 +1,120 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using System.Collections.Specialized; + namespace Files.App.Services { internal sealed class QuickAccessService : IQuickAccessService { - public async Task> GetPinnedFoldersAsync() + // Dependency injections + + private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + + // Fields + + private readonly SemaphoreSlim _addSyncSemaphore = new(1, 1); + + private readonly SystemIO.FileSystemWatcher _quickAccessFolderWatcher; + + // Properties + + /// + public List PinnedFolders { get; set; } = []; + + private readonly List _PinnedFolderItems = []; + /// + public IReadOnlyList PinnedFolderItems { - var result = - (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")) - .Enumerate - .Where(link => link.IsFolder); + get + { + lock (_PinnedFolderItems) + return _PinnedFolderItems.ToList().AsReadOnly(); + } + } + + // Events + + /// + public event EventHandler? DataChanged; - return result; + /// + public event SystemIO.FileSystemEventHandler? PinnedItemsModified; + + /// + public event EventHandler? UpdateQuickAccessWidget; + + public QuickAccessService() + { + _quickAccessFolderWatcher = new() + { + Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), + Filter = "f01b4d95cf55d32a.automaticDestinations-ms", + NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName, + EnableRaisingEvents = true + }; + + _quickAccessFolderWatcher.Changed += PinnedItemsWatcher_Changed; + } + + /// + public async Task InitializeAsync() + { + PinnedItemsModified += LoadAsync; + + //if (!Model.PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) + // await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); + + await UpdateItemsWithExplorerAsync(); + } + + /// + public async Task> GetPinnedFoldersAsync() + { + return (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")) + .Enumerate + .Where(link => link.IsFolder); } - public async Task PinToSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true) + /// + public async Task PinToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { foreach (string folderPath in folderPaths) await ContextMenu.InvokeVerb("pintohome", [folderPath]); - await App.QuickAccessManager.Model.LoadAsync(); + await UpdateItemsWithExplorerAsync(); - if (doUpdateQuickAccessWidget) - App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); + if (invokeQuickAccessChangedEvent) + UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); } - public async Task UnpinFromSidebarAsync(string[] folderPaths, bool doUpdateQuickAccessWidget = true) + /// + public async Task UnpinFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { - Type? shellAppType = Type.GetTypeFromProgID("Shell.Application"); - object? shell = Activator.CreateInstance(shellAppType); + var shellAppType = Type.GetTypeFromProgID("Shell.Application")!; + + var shell = Activator.CreateInstance(shellAppType); + dynamic? f2 = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, [$"shell:{Constants.CLID.QuickAccess}"]); + if (f2 is null) + return; + if (folderPaths.Length == 0) folderPaths = (await GetPinnedFoldersAsync()) .Where(link => (bool?)link.Properties["System.Home.IsPinned"] ?? false) .Select(link => link.FilePath).ToArray(); - foreach (dynamic? fi in f2.Items()) + foreach (var fi in f2.Items()) { if (ShellStorageFolder.IsShellPath((string)fi.Path)) { var folder = await ShellStorageFolder.FromPathAsync((string)fi.Path); var path = folder?.Path; + // Fix for the Linux header if (path is not null && - (folderPaths.Contains(path) || (path.StartsWith(@"\\SHELL\") && folderPaths.Any(x => x.StartsWith(@"\\SHELL\"))))) // Fix for the Linux header + (folderPaths.Contains(path) || (path.StartsWith(@"\\SHELL\") && folderPaths.Any(x => x.StartsWith(@"\\SHELL\"))))) { await SafetyExtensions.IgnoreExceptions(async () => { @@ -64,33 +133,201 @@ await SafetyExtensions.IgnoreExceptions(async () => } } - await App.QuickAccessManager.Model.LoadAsync(); - if (doUpdateQuickAccessWidget) - App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, false)); + await UpdateItemsWithExplorerAsync(); + + if (invokeQuickAccessChangedEvent) + UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, false)); } - public bool IsItemPinned(string folderPath) + /// + public bool IsPinnedToSidebar(string folderPath) { - return App.QuickAccessManager.Model.PinnedFolders.Contains(folderPath); + return PinnedFolders.Contains(folderPath); } - public async Task SaveAsync(string[] items) + /// + public async Task NotifyPinnedItemsChangesAsync(string[] items) { - if (Equals(items, App.QuickAccessManager.Model.PinnedFolders.ToArray())) + if (Equals(items, PinnedFolders.ToArray())) return; - App.QuickAccessManager.PinnedItemsWatcher.EnableRaisingEvents = false; + _quickAccessFolderWatcher.EnableRaisingEvents = false; // Unpin every item that is below this index and then pin them all in order await UnpinFromSidebarAsync([], false); await PinToSidebarAsync(items, false); - App.QuickAccessManager.PinnedItemsWatcher.EnableRaisingEvents = true; + _quickAccessFolderWatcher.EnableRaisingEvents = true; - App.QuickAccessManager.UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(items, true) + UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(items, true) { Reorder = true }); } + + /// + public async Task UpdateItemsWithExplorerAsync() + { + await _addSyncSemaphore.WaitAsync(); + + try + { + PinnedFolders = (await GetPinnedFoldersAsync()) + .Where(link => (bool?)link.Properties["System.Home.IsPinned"] ?? false) + .Select(link => link.FilePath).ToList(); + + RemoveStaleSidebarItems(); + + await AddAllItemsToSidebarAsync(); + } + finally + { + _addSyncSemaphore.Release(); + } + } + + /// + public int IndexOfItem(INavigationControlItem locationItem) + { + lock (_PinnedFolderItems) + return _PinnedFolderItems.FindIndex(x => x.Path == locationItem.Path); + } + + /// + public async Task CreateLocationItemFromPathAsync(string path) + { + var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(path)); + var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, item)); + LocationItem locationItem; + + if (string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase)) + { + locationItem = LocationItem.Create(); + } + else + { + locationItem = LocationItem.Create(); + + if (path.Equals(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase)) + locationItem.Text = "ThisPC".GetLocalizedResource(); + else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) + locationItem.Text = "Network".GetLocalizedResource(); + } + + locationItem.Path = path; + locationItem.Section = SectionType.Pinned; + locationItem.MenuOptions = new ContextMenuOptions + { + IsLocationItem = true, + ShowProperties = true, + ShowUnpinItem = true, + ShowShellItems = true, + ShowEmptyRecycleBin = string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) + }; + locationItem.IsDefaultLocation = false; + locationItem.Text = res.Result?.DisplayName ?? SystemIO.Path.GetFileName(path.TrimEnd('\\')); + + if (res) + { + locationItem.IsInvalid = false; + + if (res && res.Result is not null) + { + var result = await FileThumbnailHelper.GetIconAsync( + res.Result.Path, + Constants.ShellIconSizes.Small, + true, + IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); + + locationItem.IconData = result; + + var bitmapImage = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal); + if (bitmapImage is not null) + locationItem.Icon = bitmapImage; + } + } + else + { + locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); + locationItem.IsInvalid = true; + + Debug.WriteLine($"Pinned item was invalid {res.ErrorCode}, item: {path}"); + } + + return locationItem; + } + + /// + public async Task AddItemToSidebarAsync(string path) + { + var locationItem = await CreateLocationItemFromPathAsync(path); + + AddLocationItemToSidebar(locationItem); + } + + /// + public async Task AddAllItemsToSidebarAsync() + { + if (UserSettingsService.GeneralSettingsService.ShowPinnedSection) + { + foreach (string path in PinnedFolders) + await AddItemToSidebarAsync(path); + } + } + + /// + public void RemoveStaleSidebarItems() + { + // Remove unpinned items from PinnedFolderItems + foreach (var childItem in PinnedFolderItems) + { + if (childItem is LocationItem item && !item.IsDefaultLocation && !PinnedFolders.Contains(item.Path)) + { + lock (_PinnedFolderItems) + _PinnedFolderItems.Remove(item); + + DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Remove, item)); + } + } + + // Remove unpinned items from sidebar + DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Reset)); + } + + /// + public async void LoadAsync(object? sender, SystemIO.FileSystemEventArgs e) + { + _quickAccessFolderWatcher.EnableRaisingEvents = false; + + await UpdateItemsWithExplorerAsync(); + + UpdateQuickAccessWidget?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); + + _quickAccessFolderWatcher.EnableRaisingEvents = true; + } + + private void AddLocationItemToSidebar(LocationItem locationItem) + { + int insertIndex = -1; + + lock (_PinnedFolderItems) + { + if (_PinnedFolderItems.Any(x => x.Path == locationItem.Path)) + return; + + var lastItem = _PinnedFolderItems.LastOrDefault(x => x.ItemType is NavigationControlItemType.Location); + + insertIndex = lastItem is not null ? _PinnedFolderItems.IndexOf(lastItem) + 1 : 0; + + _PinnedFolderItems.Insert(insertIndex, locationItem); + } + + DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, locationItem, insertIndex)); + } + + private void PinnedItemsWatcher_Changed(object sender, SystemIO.FileSystemEventArgs e) + { + PinnedItemsModified?.Invoke(this, e); + } } } diff --git a/src/Files.App/Services/WindowsJumpListService.cs b/src/Files.App/Services/WindowsJumpListService.cs index 4efa69643498..7251b9974f33 100644 --- a/src/Files.App/Services/WindowsJumpListService.cs +++ b/src/Files.App/Services/WindowsJumpListService.cs @@ -10,6 +10,8 @@ namespace Files.App.Services { public sealed class WindowsJumpListService : IWindowsJumpListService { + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private const string JumpListRecentGroupHeader = "ms-resource:///Resources/JumpListRecentGroupHeader"; private const string JumpListPinnedGroupHeader = "ms-resource:///Resources/JumpListPinnedGroupHeader"; @@ -17,8 +19,8 @@ public async Task InitializeAsync() { try { - App.QuickAccessManager.UpdateQuickAccessWidget -= UpdateQuickAccessWidget_Invoked; - App.QuickAccessManager.UpdateQuickAccessWidget += UpdateQuickAccessWidget_Invoked; + QuickAccessService.UpdateQuickAccessWidget -= UpdateQuickAccessWidget_Invoked; + QuickAccessService.UpdateQuickAccessWidget += UpdateQuickAccessWidget_Invoked; await RefreshPinnedFoldersAsync(); } @@ -88,7 +90,7 @@ public async Task RefreshPinnedFoldersAsync() var itemsToRemove = instance.Items.Where(x => string.Equals(x.GroupName, JumpListPinnedGroupHeader, StringComparison.OrdinalIgnoreCase)).ToList(); itemsToRemove.ForEach(x => instance.Items.Remove(x)); - App.QuickAccessManager.Model.PinnedFolders.ForEach(x => AddFolder(x, JumpListPinnedGroupHeader, instance)); + QuickAccessService.PinnedFolders.ForEach(x => AddFolder(x, JumpListPinnedGroupHeader, instance)); await instance.SaveAsync(); } } diff --git a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs index f7c719d73663..8c68c63db2aa 100644 --- a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs @@ -121,7 +121,7 @@ private void AdaptiveGridView_RightTapped(object sender, RightTappedRoutedEventA OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), item.IsFolder); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path), item.IsFolder); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index 603835c28b05..52cec75e2ac6 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -22,6 +22,7 @@ public sealed partial class QuickAccessWidget : BaseWidgetViewModel, IWidgetView public IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); + private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public static ObservableCollection ItemsAdded = []; @@ -169,7 +170,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in itemsToAdd) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await App.QuickAccessManager.Model.CreateLocationItemFromPathAsync(itemToAdd); + var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); var isPinned = (bool?)e.Items.Where(x => x.FilePath == itemToAdd).FirstOrDefault()?.Properties["System.Home.IsPinned"] ?? false; if (interimItemsAdded.Any(x => x.Path == itemToAdd)) @@ -193,7 +194,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in e.Paths) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await App.QuickAccessManager.Model.CreateLocationItemFromPathAsync(itemToAdd); + var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); if (interimItemsAdded.Any(x => x.Path == itemToAdd)) continue; @@ -211,7 +212,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in e.Paths) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await App.QuickAccessManager.Model.CreateLocationItemFromPathAsync(itemToAdd); + var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); if (interimItemsAdded.Any(x => x.Path == itemToAdd)) continue; @@ -237,13 +238,13 @@ private async void QuickAccessWidget_Loaded(object sender, RoutedEventArgs e) Reset = true }); - App.QuickAccessManager.UpdateQuickAccessWidget += ModifyItemAsync; + QuickAccessService.UpdateQuickAccessWidget += ModifyItemAsync; } private void QuickAccessWidget_Unloaded(object sender, RoutedEventArgs e) { Unloaded -= QuickAccessWidget_Unloaded; - App.QuickAccessManager.UpdateQuickAccessWidget -= ModifyItemAsync; + QuickAccessService.UpdateQuickAccessWidget -= ModifyItemAsync; } private static async void ItemsAdded_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) diff --git a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs index ffb4bdcbde5e..83040030af7f 100644 --- a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -129,7 +129,7 @@ private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e) OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path)); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path)); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout diff --git a/src/Files.App/Utils/Global/QuickAccessManager.cs b/src/Files.App/Utils/Global/QuickAccessManager.cs deleted file mode 100644 index b37e52aaabc3..000000000000 --- a/src/Files.App/Utils/Global/QuickAccessManager.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System.IO; - -namespace Files.App.Utils -{ - public sealed class QuickAccessManager - { - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); - - public FileSystemWatcher? PinnedItemsWatcher; - - public event FileSystemEventHandler? PinnedItemsModified; - - public EventHandler? UpdateQuickAccessWidget; - - public PinnedFoldersManager Model = new(); - - public QuickAccessManager() - { - Initialize(); - } - - public void Initialize() - { - PinnedItemsWatcher = new() - { - Path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), - Filter = "f01b4d95cf55d32a.automaticDestinations-ms", - NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName, - EnableRaisingEvents = true - }; - - PinnedItemsWatcher.Changed += PinnedItemsWatcher_Changed; - } - - private void PinnedItemsWatcher_Changed(object sender, FileSystemEventArgs e) - => PinnedItemsModified?.Invoke(this, e); - - public async Task InitializeAsync() - { - PinnedItemsModified += Model.LoadAsync; - - //if (!Model.PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) - // await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); - - await Model.LoadAsync(); - } - } -} diff --git a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs index d8df0b682af3..215f044e7532 100644 --- a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs @@ -12,19 +12,21 @@ public sealed class ReorderSidebarItemsDialogViewModel : ObservableObject public string HeaderText = "ReorderSidebarItemsDialogText".GetLocalizedResource(); public ICommand PrimaryButtonCommand { get; private set; } - public ObservableCollection SidebarPinnedFolderItems = new(App.QuickAccessManager.Model._PinnedFolderItems - .Where(x => x is LocationItem loc && loc.Section is SectionType.Pinned && !loc.IsHeader) - .Cast()); + public ObservableCollection SidebarPinnedFolderItems; public ReorderSidebarItemsDialogViewModel() { //App.Logger.LogWarning(string.Join(", ", SidebarPinnedFolderItems.Select(x => x.Path))); PrimaryButtonCommand = new RelayCommand(SaveChanges); + + SidebarPinnedFolderItems = new(quickAccessService.PinnedFolderItems + .Where(x => x is LocationItem loc && loc.Section is SectionType.Pinned && !loc.IsHeader) + .Cast()) } public void SaveChanges() { - quickAccessService.SaveAsync(SidebarPinnedFolderItems.Select(x => x.Path).ToArray()); + quickAccessService.NotifyPinnedItemsChangesAsync(SidebarPinnedFolderItems.Select(x => x.Path).ToArray()); } } } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 3af1b7317e9c..4a72835deae3 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -28,6 +28,7 @@ public sealed class SidebarViewModel : ObservableObject, IDisposable, ISidebarVi private ICommandManager Commands { get; } = Ioc.Default.GetRequiredService(); private readonly DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); private readonly IFileTagsService fileTagsService; + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private readonly NetworkDrivesViewModel networkDrivesViewModel = Ioc.Default.GetRequiredService(); @@ -48,8 +49,6 @@ public IFilesystemHelpers FilesystemHelpers public object SidebarItems => sidebarItems; public BulkConcurrentObservableCollection sidebarItems { get; init; } - public PinnedFoldersManager SidebarPinnedModel => App.QuickAccessManager.Model; - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private SidebarDisplayMode sidebarDisplayMode; public SidebarDisplayMode SidebarDisplayMode @@ -246,7 +245,7 @@ public SidebarViewModel() Manager_DataChanged(SectionType.WSL, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); Manager_DataChanged(SectionType.FileTag, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - App.QuickAccessManager.Model.DataChanged += Manager_DataChanged; + QuickAccessService.DataChanged += Manager_DataChanged; App.LibraryManager.DataChanged += Manager_DataChanged; drivesViewModel.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged += Manager_DataChanged; @@ -280,7 +279,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () => var section = await GetOrCreateSectionAsync(sectionType); Func> getElements = () => sectionType switch { - SectionType.Pinned => App.QuickAccessManager.Model.PinnedFolderItems, + SectionType.Pinned => QuickAccessService.PinnedFolderItems, SectionType.CloudDrives => CloudDrivesManager.Drives, SectionType.Drives => drivesViewModel.Drives.Cast().ToList().AsReadOnly(), SectionType.Network => networkDrivesViewModel.Drives.Cast().ToList().AsReadOnly(), @@ -582,7 +581,7 @@ public async Task UpdateSectionVisibilityAsync(SectionType sectionType, bool sho SectionType.WSL when generalSettingsService.ShowWslSection => WSLDistroManager.UpdateDrivesAsync, SectionType.FileTag when generalSettingsService.ShowFileTagsSection => App.FileTagsManager.UpdateFileTagsAsync, SectionType.Library => App.LibraryManager.UpdateLibrariesAsync, - SectionType.Pinned => App.QuickAccessManager.Model.AddAllItemsToSidebarAsync, + SectionType.Pinned => QuickAccessService.AddAllItemsToSidebarAsync, _ => () => Task.CompletedTask }; @@ -641,7 +640,7 @@ public void Dispose() { UserSettingsService.OnSettingChangedEvent -= UserSettingsService_OnSettingChangedEvent; - App.QuickAccessManager.Model.DataChanged -= Manager_DataChanged; + QuickAccessService.DataChanged -= Manager_DataChanged; App.LibraryManager.DataChanged -= Manager_DataChanged; drivesViewModel.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged -= Manager_DataChanged; @@ -937,9 +936,8 @@ private List GetLocationItemMenuItems(INavigatio { var options = item.MenuOptions; - var pinnedFolderModel = App.QuickAccessManager.Model; - var pinnedFolderIndex = pinnedFolderModel.IndexOfItem(item); - var pinnedFolderCount = pinnedFolderModel.PinnedFolders.Count; + var pinnedFolderIndex = QuickAccessService.IndexOfItem(item); + var pinnedFolderCount = QuickAccessService.PinnedFolders.Count; var isPinnedItem = item.Section is SectionType.Pinned && pinnedFolderIndex is not -1; var showMoveItemUp = isPinnedItem && pinnedFolderIndex > 0; @@ -1099,7 +1097,7 @@ private async Task HandleLocationItemDragOverAsync(LocationItem locationItem, It if (isPathNull && hasStorageItems && SectionType.Pinned.Equals(locationItem.Section)) { - var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.PinnedFolders.Contains(item.Path)); + var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolders.Contains(item.Path)); if (!haveFoldersToPin) { @@ -1264,7 +1262,7 @@ private async Task HandleLocationItemDroppedAsync(LocationItem locationItem, Ite var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); foreach (var item in storageItems) { - if (item.ItemType == FilesystemItemType.Directory && !SidebarPinnedModel.PinnedFolders.Contains(item.Path)) + if (item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolders.Contains(item.Path)) await QuickAccessService.PinToSidebarAsync([item.Path]); } } diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 5b550fcd1d20..3b1625c90ddd 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -69,7 +69,7 @@ public void Button_RightTapped(object sender, RightTappedRoutedEventArgs e) OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path)); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path)); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout diff --git a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs index 8b11c25f4f21..9095f130213f 100644 --- a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs +++ b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs @@ -17,6 +17,10 @@ namespace Files.App.Views.Layouts /// public sealed partial class ColumnsLayoutPage : BaseLayoutPage { + // Dependency injections + + public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + // Properties protected override ItemsControl ItemsControl => ColumnHost; @@ -96,7 +100,7 @@ protected override void OnNavigatedTo(NavigationEventArgs eventArgs) if (!string.IsNullOrEmpty(pathRoot)) { - var rootPathList = App.QuickAccessManager.Model.PinnedFolders.Select(NormalizePath) + var rootPathList = QuickAccessService.PinnedFolders.Select(NormalizePath) .Concat(CloudDrivesManager.Drives.Select(x => NormalizePath(x.Path))).ToList() .Concat(App.LibraryManager.Libraries.Select(x => NormalizePath(x.Path))).ToList(); rootPathList.Add(NormalizePath(pathRoot)); From 2cb8c53129472e6c8f46520f7a2bbcda6bb460c3 Mon Sep 17 00:00:00 2001 From: 0x5bfa <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 07:50:03 +0900 Subject: [PATCH 04/14] Update ReorderSidebarItemsDialogViewModel.cs --- .../Dialogs/ReorderSidebarItemsDialogViewModel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs index 215f044e7532..8ce00ce1bbd4 100644 --- a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs @@ -19,9 +19,10 @@ public ReorderSidebarItemsDialogViewModel() //App.Logger.LogWarning(string.Join(", ", SidebarPinnedFolderItems.Select(x => x.Path))); PrimaryButtonCommand = new RelayCommand(SaveChanges); - SidebarPinnedFolderItems = new(quickAccessService.PinnedFolderItems - .Where(x => x is LocationItem loc && loc.Section is SectionType.Pinned && !loc.IsHeader) - .Cast()) + SidebarPinnedFolderItems = + new(quickAccessService.PinnedFolderItems + .Where(x => x is LocationItem loc && loc.Section is SectionType.Pinned && !loc.IsHeader) + .Cast()); } public void SaveChanges() From c57dbc358ceeeb0fc96e89cd438f9bd2c65ef4f4 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:11:11 +0900 Subject: [PATCH 05/14] Fix --- .../Actions/Sidebar/PinFolderToSidebarAction.cs | 2 +- .../Actions/Sidebar/UnpinFolderToSidebarAction.cs | 15 ++++++--------- .../Data/Contexts/SideBar/SideBarContext.cs | 2 +- ...ssService.cs => IWindowsQuickAccessService.cs} | 4 +++- src/Files.App/Data/Items/DriveItem.cs | 2 +- src/Files.App/Data/Items/ListedItem.cs | 2 +- src/Files.App/Data/Items/LocationItem.cs | 2 +- .../Helpers/Application/AppLifecycleHelper.cs | 4 ++-- src/Files.App/Services/WindowsJumpListService.cs | 2 +- ...essService.cs => WindowsQuickAccessService.cs} | 4 ++-- .../Widgets/QuickAccessWidget.xaml.cs | 2 +- .../Dialogs/ReorderSidebarItemsDialogViewModel.cs | 2 +- .../ViewModels/UserControls/SidebarViewModel.cs | 2 +- .../UserControls/Widgets/BaseWidgetViewModel.cs | 2 +- .../Views/Layouts/ColumnsLayoutPage.xaml.cs | 2 +- 15 files changed, 24 insertions(+), 25 deletions(-) rename src/Files.App/Data/Contracts/{IQuickAccessService.cs => IWindowsQuickAccessService.cs} (97%) rename src/Files.App/Services/{QuickAccessService.cs => WindowsQuickAccessService.cs} (98%) diff --git a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs index 63ff414b1868..78d5855d485d 100644 --- a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs @@ -8,7 +8,7 @@ namespace Files.App.Actions internal sealed class PinFolderToSidebarAction : ObservableObject, IAction { private IContentPageContext context { get; } = Ioc.Default.GetRequiredService(); - private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public string Label => "PinFolderToSidebar".GetLocalizedResource(); diff --git a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs index 0aba4e80ce06..b25e4a9895c4 100644 --- a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs @@ -5,8 +5,8 @@ namespace Files.App.Actions { internal sealed class UnpinFolderFromSidebarAction : ObservableObject, IAction { - private readonly IContentPageContext context; - private readonly IQuickAccessService service; + private IContentPageContext context { get; } = Ioc.Default.GetRequiredService(); + private IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public string Label => "UnpinFolderFromSidebar".GetLocalizedResource(); @@ -22,11 +22,8 @@ public bool IsExecutable public UnpinFolderFromSidebarAction() { - context = Ioc.Default.GetRequiredService(); - service = Ioc.Default.GetRequiredService(); - context.PropertyChanged += Context_PropertyChanged; - QuickAccessService.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; + QuickAccessService.UpdateQuickAccessWidget += QuickAccessService_DataChanged; } public async Task ExecuteAsync() @@ -34,11 +31,11 @@ public async Task ExecuteAsync() if (context.HasSelection) { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await service.UnpinFromSidebarAsync(items); + await QuickAccessService.UnpinFromSidebarAsync(items); } else if (context.Folder is not null) { - await service.UnpinFromSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.UnpinFromSidebarAsync([context.Folder.ItemPath]); } } @@ -67,7 +64,7 @@ private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) } } - private void QuickAccessManager_DataChanged(object? sender, ModifyQuickAccessEventArgs e) + private void QuickAccessService_DataChanged(object? sender, ModifyQuickAccessEventArgs e) { OnPropertyChanged(nameof(IsExecutable)); } diff --git a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs index 2b03f12a7711..51758ad43c4a 100644 --- a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs +++ b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs @@ -6,7 +6,7 @@ namespace Files.App.Data.Contexts /// internal sealed class SidebarContext : ObservableObject, ISidebarContext { - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private int PinnedFolderItemIndex => IsItemRightClicked diff --git a/src/Files.App/Data/Contracts/IQuickAccessService.cs b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs similarity index 97% rename from src/Files.App/Data/Contracts/IQuickAccessService.cs rename to src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs index d4cb25a273ee..ba70e9e9a273 100644 --- a/src/Files.App/Data/Contracts/IQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs @@ -5,7 +5,7 @@ namespace Files.App.Data.Contracts { - public interface IQuickAccessService + public interface IWindowsQuickAccessService { event EventHandler? DataChanged; @@ -13,6 +13,8 @@ public interface IQuickAccessService event EventHandler? UpdateQuickAccessWidget; + List PinnedFolders { get; } + IReadOnlyList PinnedFolderItems { get; } Task InitializeAsync(); diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index 335eee80965e..b4118393fb82 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -13,7 +13,7 @@ namespace Files.App.Data.Items { public sealed class DriveItem : ObservableObject, INavigationControlItem, ILocatableFolder { - private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private BitmapImage icon; public BitmapImage Icon diff --git a/src/Files.App/Data/Items/ListedItem.cs b/src/Files.App/Data/Items/ListedItem.cs index 8e47a0beae21..bc2ce738a4af 100644 --- a/src/Files.App/Data/Items/ListedItem.cs +++ b/src/Files.App/Data/Items/ListedItem.cs @@ -20,7 +20,7 @@ public class ListedItem : ObservableObject, IGroupableItem protected IStartMenuService StartMenuService { get; } = Ioc.Default.GetRequiredService(); protected IFileTagsSettingsService fileTagsSettingsService { get; } = Ioc.Default.GetRequiredService(); protected IDateTimeFormatter dateTimeFormatter { get; } = Ioc.Default.GetRequiredService(); - protected IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + protected IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public bool IsHiddenItem { get; set; } = false; diff --git a/src/Files.App/Data/Items/LocationItem.cs b/src/Files.App/Data/Items/LocationItem.cs index 8d64a2f20551..760cdd24fc25 100644 --- a/src/Files.App/Data/Items/LocationItem.cs +++ b/src/Files.App/Data/Items/LocationItem.cs @@ -10,7 +10,7 @@ namespace Files.App.Data.Items { public class LocationItem : ObservableObject, INavigationControlItem { - private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public BitmapImage icon; public BitmapImage Icon diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index ffea5428eee7..b2c624c60ab7 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -67,7 +67,7 @@ public static async Task InitializeAppComponentsAsync() var addItemService = Ioc.Default.GetRequiredService(); var generalSettingsService = userSettingsService.GeneralSettingsService; var jumpListService = Ioc.Default.GetRequiredService(); - var quickAccessService = Ioc.Default.GetRequiredService(); + var quickAccessService = Ioc.Default.GetRequiredService(); // Start off a list of tasks we need to run before we can continue startup await Task.WhenAll( @@ -183,7 +183,7 @@ public static IHost ConfigureHost() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/Files.App/Services/WindowsJumpListService.cs b/src/Files.App/Services/WindowsJumpListService.cs index 7251b9974f33..277d7aee74df 100644 --- a/src/Files.App/Services/WindowsJumpListService.cs +++ b/src/Files.App/Services/WindowsJumpListService.cs @@ -10,7 +10,7 @@ namespace Files.App.Services { public sealed class WindowsJumpListService : IWindowsJumpListService { - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private const string JumpListRecentGroupHeader = "ms-resource:///Resources/JumpListRecentGroupHeader"; private const string JumpListPinnedGroupHeader = "ms-resource:///Resources/JumpListPinnedGroupHeader"; diff --git a/src/Files.App/Services/QuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs similarity index 98% rename from src/Files.App/Services/QuickAccessService.cs rename to src/Files.App/Services/WindowsQuickAccessService.cs index 47ca250c8bac..3bcfd986d46d 100644 --- a/src/Files.App/Services/QuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -5,7 +5,7 @@ namespace Files.App.Services { - internal sealed class QuickAccessService : IQuickAccessService + internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService { // Dependency injections @@ -44,7 +44,7 @@ public IReadOnlyList PinnedFolderItems /// public event EventHandler? UpdateQuickAccessWidget; - public QuickAccessService() + public WindowsQuickAccessService() { _quickAccessFolderWatcher = new() { diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index 52cec75e2ac6..8763da4a5fde 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -22,7 +22,7 @@ public sealed partial class QuickAccessWidget : BaseWidgetViewModel, IWidgetView public IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); - private IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + private IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public static ObservableCollection ItemsAdded = []; diff --git a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs index 8ce00ce1bbd4..3863cb05a124 100644 --- a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs @@ -7,7 +7,7 @@ namespace Files.App.ViewModels.Dialogs { public sealed class ReorderSidebarItemsDialogViewModel : ObservableObject { - private readonly IQuickAccessService quickAccessService = Ioc.Default.GetRequiredService(); + private readonly IWindowsQuickAccessService quickAccessService = Ioc.Default.GetRequiredService(); public string HeaderText = "ReorderSidebarItemsDialogText".GetLocalizedResource(); public ICommand PrimaryButtonCommand { get; private set; } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 4a72835deae3..f074d1b52a90 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -28,7 +28,7 @@ public sealed class SidebarViewModel : ObservableObject, IDisposable, ISidebarVi private ICommandManager Commands { get; } = Ioc.Default.GetRequiredService(); private readonly DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); private readonly IFileTagsService fileTagsService; - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); private readonly NetworkDrivesViewModel networkDrivesViewModel = Ioc.Default.GetRequiredService(); diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 3b1625c90ddd..8c463b2955e6 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -18,7 +18,7 @@ public abstract class BaseWidgetViewModel : UserControl // Dependency injections public IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); public IStorageService StorageService { get; } = Ioc.Default.GetRequiredService(); // Fields diff --git a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs index 9095f130213f..8a586c2b7b5f 100644 --- a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs +++ b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs @@ -19,7 +19,7 @@ public sealed partial class ColumnsLayoutPage : BaseLayoutPage { // Dependency injections - public IQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); + public IWindowsQuickAccessService QuickAccessService { get; } = Ioc.Default.GetRequiredService(); // Properties From 432c1790a92931eb3e885c13325b2572f3325a8b Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:43:25 +0900 Subject: [PATCH 06/14] Init --- src/Files.App/Data/Models/ItemViewModel.cs | 23 +- src/Files.App/Data/Models/RemovableDevice.cs | 8 +- .../Extensions/Win32FindDataExtensions.cs | 2 +- .../Helpers/Environment/ElevationHelpers.cs | 5 +- .../Interop/NativeFileOperationsHelper.cs | 519 ------------------ .../Helpers/Interop/NativeWinApiHelper.cs | 259 --------- .../Helpers/Layout/AdaptiveLayoutHelpers.cs | 2 +- .../Layout/LayoutPreferencesManager.cs | 6 +- .../Helpers/NativeFindStorageItemHelper.cs | 131 ----- .../Helpers/NaturalStringComparer.cs | 45 +- .../Helpers/Navigation/NavigationHelpers.cs | 24 +- .../Helpers/Win32/Win32Helper.Process.cs | 49 ++ .../Helpers/Win32/Win32Helper.Storage.cs | 290 +++++++++- .../Helpers/Win32/Win32PInvoke.Consts.cs | 36 ++ .../Helpers/Win32/Win32PInvoke.Enums.cs | 167 ++++++ .../Helpers/Win32/Win32PInvoke.Methods.cs | 198 ++++++- .../Helpers/Win32/Win32PInvoke.Structs.cs | 157 ++++++ .../Services/SideloadUpdateService.cs | 5 +- .../SizeProvider/CachedSizeProvider.cs | 2 +- .../Utils/FileTags/FileTagsHelper.cs | 16 +- .../DefaultSettingsSerializer.cs | 7 +- src/Files.App/Utils/Shell/ItemStreamHelper.cs | 10 +- src/Files.App/Utils/Shell/PreviewHandler.cs | 11 +- .../Enumerators/Win32StorageEnumerator.cs | 28 +- .../Utils/Storage/Helpers/FolderHelpers.cs | 2 +- .../Utils/Storage/Helpers/StorageHelpers.cs | 4 +- .../Storage/Operations/FileSizeCalculator.cs | 4 +- .../Storage/Operations/FilesystemHelpers.cs | 18 +- .../Operations/FilesystemOperations.cs | 8 +- .../Utils/Storage/Search/FolderSearch.cs | 4 +- .../Storage/StorageItems/NativeStorageFile.cs | 12 +- .../Storage/StorageItems/SystemStorageFile.cs | 2 +- .../StorageItems/VirtualStorageItem.cs | 4 +- .../Storage/StorageItems/ZipStorageFile.cs | 10 +- .../Storage/StorageItems/ZipStorageFolder.cs | 8 +- .../Properties/Items/BaseProperties.cs | 4 +- .../Properties/Items/CombinedProperties.cs | 14 +- .../Properties/Items/FileProperties.cs | 16 +- .../Properties/Items/FolderProperties.cs | 8 +- .../Properties/Items/LibraryProperties.cs | 12 +- 40 files changed, 1013 insertions(+), 1117 deletions(-) delete mode 100644 src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs delete mode 100644 src/Files.App/Helpers/Interop/NativeWinApiHelper.cs delete mode 100644 src/Files.App/Helpers/NativeFindStorageItemHelper.cs diff --git a/src/Files.App/Data/Models/ItemViewModel.cs b/src/Files.App/Data/Models/ItemViewModel.cs index 9e8464e922c4..ebb69ff08a11 100644 --- a/src/Files.App/Data/Models/ItemViewModel.cs +++ b/src/Files.App/Data/Models/ItemViewModel.cs @@ -20,7 +20,7 @@ using Windows.Storage.FileProperties; using Windows.Storage.Search; using static Files.App.Helpers.Win32PInvoke; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; using FileAttributes = System.IO.FileAttributes; @@ -39,6 +39,7 @@ public sealed class ItemViewModel : ObservableObject, IDisposable private readonly AsyncManualResetEvent gitChangedEvent; private readonly DispatcherQueue dispatcherQueue; private readonly JsonElement defaultJson = JsonSerializer.SerializeToElement("{}"); + private readonly StorageCacheController fileListCache = StorageCacheController.GetInstance(); private readonly string folderTypeTextLocalized = "Folder".GetLocalizedResource(); private Task? aProcessQueueAction; @@ -49,10 +50,8 @@ public sealed class ItemViewModel : ObservableObject, IDisposable private readonly IWindowsJumpListService jumpListService = Ioc.Default.GetRequiredService(); private readonly IDialogService dialogService = Ioc.Default.GetRequiredService(); private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - private readonly INetworkDrivesService NetworkDrivesService = Ioc.Default.GetRequiredService(); private readonly IFileTagsSettingsService fileTagsSettingsService = Ioc.Default.GetRequiredService(); private readonly ISizeProvider folderSizeProvider = Ioc.Default.GetRequiredService(); - private readonly IStorageCacheService fileListCache = Ioc.Default.GetRequiredService(); // Only used for Binding and ApplyFilesAndFoldersChangesAsync, don't manipulate on this! public BulkConcurrentObservableCollection FilesAndFolders { get; } @@ -680,7 +679,7 @@ void ClearDisplay() DirectoryInfoUpdated?.Invoke(this, EventArgs.Empty); } - if (NativeWinApiHelper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) + if (Win32Helper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) ClearDisplay(); else await dispatcherQueue.EnqueueOrInvokeAsync(ClearDisplay); @@ -768,7 +767,7 @@ void OrderEntries() folderSettings.SortDirectoriesAlongsideFiles, folderSettings.SortFilesFirst)); } - if (NativeWinApiHelper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) + if (Win32Helper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) return Task.Run(OrderEntries); OrderEntries(); @@ -1123,7 +1122,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => { item.ItemNameRaw = matchingStorageFolder.DisplayName; }); - await fileListCache.AddDisplayName(item.ItemPath, matchingStorageFolder.DisplayName); + await fileListCache.SaveFileDisplayNameToCache(item.ItemPath, matchingStorageFolder.DisplayName); if (folderSettings.DirectorySortOption == SortOption.Name && !isLoadingItems) { await OrderFilesAndFoldersAsync(); @@ -1507,7 +1506,7 @@ private async Task EnumerateItemsFromStandardFolderAsync(string path, Cance if (isNetwork) { - var auth = await NetworkDrivesService.AuthenticateNetworkShare(path); + var auth = await NetworkDrivesAPI.AuthenticateNetworkShare(path); if (!auth) return -1; } @@ -1865,8 +1864,8 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStatus) { Debug.WriteLine($"WatchForDirectoryChanges: {path}"); - var hWatchDir = NativeFileOperationsHelper.CreateFileFromApp(path, 1, 1 | 2 | 4, - IntPtr.Zero, 3, (uint)NativeFileOperationsHelper.File_Attributes.BackupSemantics | (uint)NativeFileOperationsHelper.File_Attributes.Overlapped, IntPtr.Zero); + var hWatchDir = Win32Helper.CreateFileFromApp(path, 1, 1 | 2 | 4, + IntPtr.Zero, 3, (uint)Win32Helper.File_Attributes.BackupSemantics | (uint)Win32Helper.File_Attributes.Overlapped, IntPtr.Zero); if (hWatchDir.ToInt64() == -1) return; @@ -1974,13 +1973,13 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat private void WatchForGitChanges() { - var hWatchDir = NativeFileOperationsHelper.CreateFileFromApp( + var hWatchDir = Win32Helper.CreateFileFromApp( GitDirectory!, 1, 1 | 2 | 4, IntPtr.Zero, 3, - (uint)NativeFileOperationsHelper.File_Attributes.BackupSemantics | (uint)NativeFileOperationsHelper.File_Attributes.Overlapped, + (uint)Win32Helper.File_Attributes.BackupSemantics | (uint)Win32Helper.File_Attributes.Overlapped, IntPtr.Zero); if (hWatchDir.ToInt64() == -1) @@ -2226,7 +2225,7 @@ private async Task AddFileOrFolderAsync(ListedItem? item) if (UserSettingsService.FoldersSettingsService.AreAlternateStreamsVisible) { // New file added, enumerate ADS - foreach (var ads in NativeFileOperationsHelper.GetAlternateStreams(item.ItemPath)) + foreach (var ads in Win32Helper.GetAlternateStreams(item.ItemPath)) { var adsItem = Win32StorageEnumerator.GetAlternateStream(ads, item); filesAndFolders.Add(adsItem); diff --git a/src/Files.App/Data/Models/RemovableDevice.cs b/src/Files.App/Data/Models/RemovableDevice.cs index cd24e822e5ab..0923b17615b5 100644 --- a/src/Files.App/Data/Models/RemovableDevice.cs +++ b/src/Files.App/Data/Models/RemovableDevice.cs @@ -52,7 +52,7 @@ private async Task LockVolumeAsync() for (int i = 0; i < 5; i++) { - if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero)) + if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nint.Zero, 0, out _, 0, out _, nint.Zero)) { Debug.WriteLine("Lock successful!"); result = true; @@ -72,18 +72,18 @@ private async Task LockVolumeAsync() private bool DismountVolume() { - return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero); + return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, out _, 0, out _, nint.Zero); } private bool PreventRemovalOfVolume(bool prevent) { byte[] buf = [prevent ? (byte)1 : (byte)0]; - return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, nint.Zero, 0, out _, nint.Zero); + return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, out _, 0, out _, nint.Zero); } private bool AutoEjectVolume() { - return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero); + return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nint.Zero, 0, out _, 0, out _, nint.Zero); } private bool CloseVolume() diff --git a/src/Files.App/Extensions/Win32FindDataExtensions.cs b/src/Files.App/Extensions/Win32FindDataExtensions.cs index 7a81be8460c8..ccbb038692b9 100644 --- a/src/Files.App/Extensions/Win32FindDataExtensions.cs +++ b/src/Files.App/Extensions/Win32FindDataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; namespace Files.App.Extensions { diff --git a/src/Files.App/Helpers/Environment/ElevationHelpers.cs b/src/Files.App/Helpers/Environment/ElevationHelpers.cs index ff1bf0ee1a65..6902d8d4427a 100644 --- a/src/Files.App/Helpers/Environment/ElevationHelpers.cs +++ b/src/Files.App/Helpers/Environment/ElevationHelpers.cs @@ -8,15 +8,12 @@ namespace Files.App.Helpers { public static class ElevationHelpers { - [DllImport("shell32.dll", EntryPoint = "#865", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] private static extern bool _IsElevationRequired([MarshalAs(UnmanagedType.LPWStr)] string pszPath); - public static bool IsElevationRequired(string filePath) { if (string.IsNullOrEmpty(filePath)) return false; - return _IsElevationRequired(filePath); + return Win32PInvoke._IsElevationRequired(filePath); } public static bool IsAppRunAsAdmin() diff --git a/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs b/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs deleted file mode 100644 index ea093e24f98c..000000000000 --- a/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using Microsoft.Win32.SafeHandles; -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using System.Text; -using System.Threading; -using Vanara.PInvoke; -using static Files.App.Helpers.NativeFindStorageItemHelper; - -namespace Files.App.Helpers -{ - public sealed class NativeFileOperationsHelper - { - public enum File_Attributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Directory = 0x00000010, - Archive = 0x00000020, - Device = 0x00000040, - Normal = 0x00000080, - Temporary = 0x00000100, - SparseFile = 0x00000200, - ReparsePoint = 0x00000400, - Compressed = 0x00000800, - Offline = 0x00001000, - NotContentIndexed = 0x00002000, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - FirstPipeInstance = 0x00080000 - } - - public const uint GENERIC_READ = 0x80000000; - public const uint GENERIC_WRITE = 0x40000000; - public const uint FILE_APPEND_DATA = 0x0004; - public const uint FILE_WRITE_ATTRIBUTES = 0x100; - - public const uint FILE_SHARE_READ = 0x00000001; - public const uint FILE_SHARE_WRITE = 0x00000002; - public const uint FILE_SHARE_DELETE = 0x00000004; - - public const uint FILE_BEGIN = 0; - public const uint FILE_END = 2; - - public const uint CREATE_ALWAYS = 2; - public const uint CREATE_NEW = 1; - public const uint OPEN_ALWAYS = 4; - public const uint OPEN_EXISTING = 3; - public const uint TRUNCATE_EXISTING = 5; - - [DllImport("api-ms-win-core-handle-l1-1-0.dll")] - public static extern bool CloseHandle(IntPtr hObject); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern IntPtr CreateFileFromApp( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr SecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile - ); - - public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite = true) - { - return new SafeFileHandle(CreateFileFromApp(filePath, - GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? CREATE_ALWAYS : OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); - } - - public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false, uint flags = 0) - { - return new SafeFileHandle(CreateFileFromApp(filePath, - GENERIC_READ | (readWrite ? GENERIC_WRITE : 0), FILE_SHARE_READ | (readWrite ? 0 : FILE_SHARE_WRITE), IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics | flags, IntPtr.Zero), true); - } - - private const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; - private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; - public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; - public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct REPARSE_DATA_BUFFER - { - public uint ReparseTag; - public short ReparseDataLength; - public short Reserved; - public short SubsNameOffset; - public short SubsNameLength; - public short PrintNameOffset; - public short PrintNameLength; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_REPARSE_DATA_BUFFER_SIZE)] - public char[] PathBuffer; - } - - [DllImport("api-ms-win-core-io-l1-1-0.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeviceIoControl( - IntPtr hDevice, - uint dwIoControlCode, - IntPtr lpInBuffer, - uint nInBufferSize, - //IntPtr lpOutBuffer, - out REPARSE_DATA_BUFFER outBuffer, - uint nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetFileAttributesExFromApp( - string lpFileName, - GET_FILEEX_INFO_LEVELS fInfoLevelId, - out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetFileAttributesFromApp( - string lpFileName, - FileAttributes dwFileAttributes); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", ExactSpelling = true, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern uint SetFilePointer( - IntPtr hFile, - long lDistanceToMove, - IntPtr lpDistanceToMoveHigh, - uint dwMoveMethod - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public unsafe static extern bool ReadFile( - IntPtr hFile, - byte* lpBuffer, - int nBufferLength, - int* lpBytesReturned, - IntPtr lpOverlapped - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public unsafe static extern bool WriteFile( - IntPtr hFile, - byte* lpBuffer, - int nBufferLength, - int* lpBytesWritten, - IntPtr lpOverlapped - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern bool WriteFileEx( - IntPtr hFile, - byte[] lpBuffer, - uint nNumberOfBytesToWrite, - [In] ref NativeOverlapped lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); - - public delegate void LPOVERLAPPED_COMPLETION_ROUTINE(uint dwErrorCode, uint dwNumberOfBytesTransfered, ref NativeOverlapped lpOverlapped); - - public enum GET_FILEEX_INFO_LEVELS - { - GetFileExInfoStandard, - } - - [StructLayout(LayoutKind.Sequential)] - public struct WIN32_FILE_ATTRIBUTE_DATA - { - public FileAttributes dwFileAttributes; - public FILETIME ftCreationTime; - public FILETIME ftLastAccessTime; - public FILETIME ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - } - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool GetFileTime([In] IntPtr hFile, out FILETIME lpCreationTime, out FILETIME lpLastAccessTime, out FILETIME lpLastWriteTime); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool SetFileTime([In] IntPtr hFile, in FILETIME lpCreationTime, in FILETIME lpLastAccessTime, in FILETIME lpLastWriteTime); - - private enum FILE_INFO_BY_HANDLE_CLASS - { - FileBasicInfo = 0, - FileStandardInfo = 1, - FileNameInfo = 2, - FileRenameInfo = 3, - FileDispositionInfo = 4, - FileAllocationInfo = 5, - FileEndOfFileInfo = 6, - FileStreamInfo = 7, - FileCompressionInfo = 8, - FileAttributeTagInfo = 9, - FileIdBothDirectoryInfo = 10,// 0x0A - FileIdBothDirectoryRestartInfo = 11, // 0xB - FileIoPriorityHintInfo = 12, // 0xC - FileRemoteProtocolInfo = 13, // 0xD - FileFullDirectoryInfo = 14, // 0xE - FileFullDirectoryRestartInfo = 15, // 0xF - FileStorageInfo = 16, // 0x10 - FileAlignmentInfo = 17, // 0x11 - FileIdInfo = 18, // 0x12 - FileIdExtdDirectoryInfo = 19, // 0x13 - FileIdExtdDirectoryRestartInfo = 20, // 0x14 - MaximumFileInfoByHandlesClass - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private struct FILE_ID_BOTH_DIR_INFO - { - public uint NextEntryOffset; - public uint FileIndex; - public long CreationTime; - public long LastAccessTime; - public long LastWriteTime; - public long ChangeTime; - public long EndOfFile; - public long AllocationSize; - public uint FileAttributes; - public uint FileNameLength; - public uint EaSize; - public char ShortNameLength; - [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 12)] - public string ShortName; - public long FileId; - [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)] - public string FileName; - } - - [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - private static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, out FILE_ID_BOTH_DIR_INFO dirInfo, uint dwBufferSize); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] - private struct FILE_STREAM_INFO - { - public uint NextEntryOffset; - public uint StreamNameLength; - public long StreamSize; - public long StreamAllocationSize; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] - public string StreamName; - } - - [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - private static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, IntPtr dirInfo, uint dwBufferSize); - - private enum StreamInfoLevels { FindStreamInfoStandard = 0 } - - [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] - private static extern IntPtr FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); - - [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool FindNextStreamW(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private sealed class WIN32_FIND_STREAM_DATA { - public long StreamSize; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] - public string cStreamName; - } - - public static bool GetFileDateModified(string filePath, out FILETIME dateModified) - { - using var hFile = new SafeFileHandle(CreateFileFromApp(filePath, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); - return GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified); - } - - public static bool SetFileDateModified(string filePath, FILETIME dateModified) - { - using var hFile = new SafeFileHandle(CreateFileFromApp(filePath, FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); - return SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified); - } - - public static bool HasFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (GetFileAttributesExFromApp( - lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return (lpFileInfo.dwFileAttributes & dwAttrs) == dwAttrs; - } - return false; - } - - public static bool SetFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (!GetFileAttributesExFromApp( - lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return false; - } - return SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes | dwAttrs); - } - - public static bool UnsetFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (!GetFileAttributesExFromApp( - lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return false; - } - return SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes & ~dwAttrs); - } - - public static string ReadStringFromFile(string filePath) - { - IntPtr hFile = CreateFileFromApp(filePath, - GENERIC_READ, - FILE_SHARE_READ, - IntPtr.Zero, - OPEN_EXISTING, - (uint)File_Attributes.BackupSemantics, - IntPtr.Zero); - - if (hFile.ToInt64() == -1) - { - return null; - } - - const int BUFFER_LENGTH = 4096; - byte[] buffer = new byte[BUFFER_LENGTH]; - int dwBytesRead; - string szRead = string.Empty; - - unsafe - { - using (MemoryStream ms = new MemoryStream()) - using (StreamReader reader = new StreamReader(ms, true)) - { - while (true) - { - fixed (byte* pBuffer = buffer) - { - if (ReadFile(hFile, pBuffer, BUFFER_LENGTH - 1, &dwBytesRead, IntPtr.Zero) && dwBytesRead > 0) - { - ms.Write(buffer, 0, dwBytesRead); - } - else - { - break; - } - } - } - ms.Position = 0; - szRead = reader.ReadToEnd(); - } - } - - CloseHandle(hFile); - - return szRead; - } - - public static bool WriteStringToFile(string filePath, string str, File_Attributes flags = 0) - { - IntPtr hStream = CreateFileFromApp(filePath, - GENERIC_WRITE, 0, IntPtr.Zero, CREATE_ALWAYS, (uint)(File_Attributes.BackupSemantics | flags), IntPtr.Zero); - if (hStream.ToInt64() == -1) - { - return false; - } - byte[] buff = Encoding.UTF8.GetBytes(str); - int dwBytesWritten; - unsafe - { - fixed (byte* pBuff = buff) - { - WriteFile(hStream, pBuff, buff.Length, &dwBytesWritten, IntPtr.Zero); - } - } - CloseHandle(hStream); - return true; - } - - public static bool WriteBufferToFileWithProgress(string filePath, byte[] buffer, LPOVERLAPPED_COMPLETION_ROUTINE callback) - { - using var hFile = CreateFileForWrite(filePath); - - if (hFile.IsInvalid) - { - return false; - } - - NativeOverlapped nativeOverlapped = new NativeOverlapped(); - bool result = WriteFileEx(hFile.DangerousGetHandle(), buffer, (uint)buffer.LongLength, ref nativeOverlapped, callback); - - if (!result) - { - System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error()); - } - - return result; - } - - // https://www.pinvoke.net/default.aspx/kernel32/GetFileInformationByHandleEx.html - public static ulong? GetFolderFRN(string folderPath) - { - using var handle = OpenFileForRead(folderPath); - if (!handle.IsInvalid) - { - var fileStruct = new FILE_ID_BOTH_DIR_INFO(); - if (GetFileInformationByHandleEx(handle.DangerousGetHandle(), FILE_INFO_BY_HANDLE_CLASS.FileIdBothDirectoryInfo, out fileStruct, (uint)Marshal.SizeOf(fileStruct))) - { - return (ulong)fileStruct.FileId; - } - } - return null; - } - - public static ulong? GetFileFRN(string filePath) - { - using var handle = OpenFileForRead(filePath); - if (!handle.IsInvalid) - { - try - { - var fileID = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileIdInfo); - return BitConverter.ToUInt64(fileID.FileId.Identifier, 0); - } - catch { } - } - return null; - } - - public static long? GetFileSizeOnDisk(string filePath) - { - using var handle = OpenFileForRead(filePath); - if (!handle.IsInvalid) - { - try - { - var fileAllocationInfo = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo); - return fileAllocationInfo.AllocationSize; - } - catch { } - } - return null; - } - - // https://github.com/rad1oactive/BetterExplorer/blob/master/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Shell/ReparsePoint.cs - public static string ParseSymLink(string path) - { - using var handle = OpenFileForRead(path, false, 0x00200000); - if (!handle.IsInvalid) - { - REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); - if (DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero)) - { - var subsString = new string(buffer.PathBuffer, ((buffer.SubsNameOffset / 2) + 2), buffer.SubsNameLength / 2); - var printString = new string(buffer.PathBuffer, ((buffer.PrintNameOffset / 2) + 2), buffer.PrintNameLength / 2); - var normalisedTarget = printString ?? subsString; - if (string.IsNullOrEmpty(normalisedTarget)) - { - normalisedTarget = subsString; - if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) - { - normalisedTarget = normalisedTarget.Substring(4); - } - } - if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) - { - // Target is relative, get the absolute path - normalisedTarget = normalisedTarget.TrimStart(Path.DirectorySeparatorChar); - path = path.TrimEnd(Path.DirectorySeparatorChar); - normalisedTarget = Path.GetFullPath(Path.Combine(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar)), normalisedTarget)); - } - return normalisedTarget; - } - } - return null; - } - - // https://stackoverflow.com/a/7988352 - public static IEnumerable<(string Name, long Size)> GetAlternateStreams(string path) - { - WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA(); - IntPtr hFile = FindFirstStreamW(path, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); - - if (hFile.ToInt64() != -1) - { - do - { - // The documentation for FindFirstStreamW says that it is always a ::$DATA - // stream type, but FindNextStreamW doesn't guarantee that for subsequent - // streams so we check to make sure - if (findStreamData.cStreamName.EndsWith(":$DATA") && findStreamData.cStreamName != "::$DATA") - { - yield return (findStreamData.cStreamName, findStreamData.StreamSize); - } - } - while (FindNextStreamW(hFile, findStreamData)); - - FindClose(hFile); - } - } - } -} diff --git a/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs b/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs deleted file mode 100644 index 5a625cb61ce2..000000000000 --- a/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using Files.App.Utils.Shell; -using Microsoft.Extensions.Logging; -using System.Runtime.InteropServices; -using System.Text; -using Windows.Foundation.Metadata; -using Windows.System; - -namespace Files.App.Helpers -{ - public sealed class NativeWinApiHelper - { - [DllImport("Shcore.dll", SetLastError = true)] - public static extern int GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY); - - [DllImport("api-ms-win-core-processthreads-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenProcessToken([In] IntPtr ProcessHandle, TokenAccess DesiredAccess, out IntPtr TokenHandle); - - [DllImport("api-ms-win-core-processthreads-l1-1-2.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr GetCurrentProcess(); - - [DllImport("api-ms-win-security-base-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetTokenInformation(IntPtr hObject, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr pTokenInfo, int tokenInfoLength, out int returnLength); - - [DllImport("api-ms-win-core-handle-l1-1-0.dll")] - public static extern bool CloseHandle(IntPtr hObject); - - [DllImport("api-ms-win-security-base-l1-1-0.dll", ExactSpelling = true, SetLastError = true)] - public static extern int GetLengthSid(IntPtr pSid); - - [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptUnprotectData( - in CRYPTOAPI_BLOB pDataIn, - StringBuilder szDataDescr, - in CRYPTOAPI_BLOB pOptionalEntropy, - IntPtr pvReserved, - IntPtr pPromptStruct, - CryptProtectFlags dwFlags, - out CRYPTOAPI_BLOB pDataOut); - - [StructLayout(LayoutKind.Sequential)] - public struct TOKEN_USER - { - public SID_AND_ATTRIBUTES User; - } - - [StructLayout(LayoutKind.Sequential)] - public struct SID_AND_ATTRIBUTES - { - public IntPtr Sid; - - public uint Attributes; - } - - [Flags] - public enum CryptProtectFlags - { - CRYPTPROTECT_UI_FORBIDDEN = 0x1, - - CRYPTPROTECT_LOCAL_MACHINE = 0x4, - - CRYPTPROTECT_CRED_SYNC = 0x8, - - CRYPTPROTECT_AUDIT = 0x10, - - CRYPTPROTECT_NO_RECOVERY = 0x20, - - CRYPTPROTECT_VERIFY_PROTECTION = 0x40, - - CRYPTPROTECT_CRED_REGENERATE = 0x80 - } - - [StructLayout(LayoutKind.Sequential)] - public struct CRYPTOAPI_BLOB - { - public uint cbData; - - public IntPtr pbData; - } - - public enum TOKEN_INFORMATION_CLASS - { - TokenUser = 1, - - TokenGroups, - - TokenPrivileges, - - TokenOwner, - - TokenPrimaryGroup, - - TokenDefaultDacl, - - TokenSource, - - TokenType, - - TokenImpersonationLevel, - - TokenStatistics, - - TokenRestrictedSids, - - TokenSessionId, - - TokenGroupsAndPrivileges, - - TokenSessionReference, - - TokenSandBoxInert, - - TokenAuditPolicy, - - TokenOrigin, - - TokenElevationType, - - TokenLinkedToken, - - TokenElevation, - - TokenHasRestrictions, - - TokenAccessInformation, - - TokenVirtualizationAllowed, - - TokenVirtualizationEnabled, - - TokenIntegrityLevel, - - TokenUIAccess, - - TokenMandatoryPolicy, - - TokenLogonSid, - - TokenIsAppContainer, - - TokenCapabilities, - - TokenAppContainerSid, - - TokenAppContainerNumber, - - TokenUserClaimAttributes, - - TokenDeviceClaimAttributes, - - TokenRestrictedUserClaimAttributes, - - TokenRestrictedDeviceClaimAttributes, - - TokenDeviceGroups, - - TokenRestrictedDeviceGroups, - - TokenSecurityAttributes, - - TokenIsRestricted - } - - [Serializable] - public enum TOKEN_TYPE - { - TokenPrimary = 1, - - TokenImpersonation = 2 - } - - [Flags] - public enum TokenAccess : uint - { - TOKEN_ASSIGN_PRIMARY = 0x0001, - - TOKEN_DUPLICATE = 0x0002, - - TOKEN_IMPERSONATE = 0x0004, - - TOKEN_QUERY = 0x0008, - - TOKEN_QUERY_SOURCE = 0x0010, - - TOKEN_ADJUST_PRIVILEGES = 0x0020, - - TOKEN_ADJUST_GROUPS = 0x0040, - - TOKEN_ADJUST_DEFAULT = 0x0080, - - TOKEN_ADJUST_SESSIONID = 0x0100, - - TOKEN_ALL_ACCESS_P = 0x000F00FF, - - TOKEN_ALL_ACCESS = 0x000F01FF, - - TOKEN_READ = 0x00020008, - - TOKEN_WRITE = 0x000200E0, - - TOKEN_EXECUTE = 0x00020000 - } - - [DllImport("api-ms-win-core-wow64-l1-1-1.dll", SetLastError = true)] - private static extern bool IsWow64Process2( - IntPtr process, - out ushort processMachine, - out ushort nativeMachine); - - // https://stackoverflow.com/questions/54456140/how-to-detect-were-running-under-the-arm64-version-of-windows-10-in-net - // https://learn.microsoft.com/windows/win32/sysinfo/image-file-machine-constants - private static bool? isRunningOnArm = null; - - public static bool IsRunningOnArm - { - get - { - if (isRunningOnArm is null) - { - isRunningOnArm = IsArmProcessor(); - App.Logger.LogInformation("Running on ARM: {0}", isRunningOnArm); - } - return isRunningOnArm ?? false; - } - } - - private static bool IsArmProcessor() - { - var handle = System.Diagnostics.Process.GetCurrentProcess().Handle; - if (!IsWow64Process2(handle, out _, out var nativeMachine)) - { - return false; - } - return (nativeMachine == 0xaa64 || - nativeMachine == 0x01c0 || - nativeMachine == 0x01c2 || - nativeMachine == 0x01c4); - } - - private static bool? isHasThreadAccessPropertyPresent = null; - - public static bool IsHasThreadAccessPropertyPresent - { - get - { - isHasThreadAccessPropertyPresent ??= ApiInformation.IsPropertyPresent(typeof(DispatcherQueue).FullName, "HasThreadAccess"); - return isHasThreadAccessPropertyPresent ?? false; - } - } - - public static Task GetFileAssociationAsync(string filePath) - => Win32Helper.GetFileAssociationAsync(filePath, true); - } -} \ No newline at end of file diff --git a/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs b/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs index 6c47ff8c63d0..27417b823517 100644 --- a/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs +++ b/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs @@ -49,7 +49,7 @@ private static Layouts GetPathLayout(string path) { var iniPath = IO.Path.Combine(path, "desktop.ini"); - var iniContents = NativeFileOperationsHelper.ReadStringFromFile(iniPath)?.Trim(); + var iniContents = Win32Helper.ReadStringFromFile(iniPath)?.Trim(); if (string.IsNullOrEmpty(iniContents)) return Layouts.None; diff --git a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs index a024363c1146..a2f19e1b41a3 100644 --- a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs +++ b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs @@ -396,7 +396,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte { if (!UserSettingsService.LayoutSettingsService.SyncFolderPreferencesAcrossDirectories) { - var folderFRN = NativeFileOperationsHelper.GetFolderFRN(path); + var folderFRN = Win32Helper.GetFolderFRN(path); var trimmedFolderPath = path.TrimPath(); if (trimmedFolderPath is not null) SetLayoutPreferencesToDatabase(trimmedFolderPath, folderFRN, preferencesItem); @@ -509,7 +509,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte { path = path.TrimPath() ?? string.Empty; - var folderFRN = NativeFileOperationsHelper.GetFolderFRN(path); + var folderFRN = Win32Helper.GetFolderFRN(path); return GetLayoutPreferencesFromDatabase(path, folderFRN) ?? GetLayoutPreferencesFromAds(path, folderFRN) @@ -521,7 +521,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte private static LayoutPreferencesItem? GetLayoutPreferencesFromAds(string path, ulong? frn) { - var str = NativeFileOperationsHelper.ReadStringFromFile($"{path}:files_layoutmode"); + var str = Win32Helper.ReadStringFromFile($"{path}:files_layoutmode"); var layoutPreferences = SafetyExtensions.IgnoreExceptions(() => string.IsNullOrEmpty(str) ? null : JsonSerializer.Deserialize(str)); diff --git a/src/Files.App/Helpers/NativeFindStorageItemHelper.cs b/src/Files.App/Helpers/NativeFindStorageItemHelper.cs deleted file mode 100644 index bc9f3bc9f0f7..000000000000 --- a/src/Files.App/Helpers/NativeFindStorageItemHelper.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; - -namespace Files.App.Helpers -{ - /// - /// Provides a bunch of Win32API for native find storage items. - /// - public sealed class NativeFindStorageItemHelper - { - [StructLayout(LayoutKind.Sequential)] - public struct SYSTEMTIME - { - [MarshalAs(UnmanagedType.U2)] public short Year; - [MarshalAs(UnmanagedType.U2)] public short Month; - [MarshalAs(UnmanagedType.U2)] public short DayOfWeek; - [MarshalAs(UnmanagedType.U2)] public short Day; - [MarshalAs(UnmanagedType.U2)] public short Hour; - [MarshalAs(UnmanagedType.U2)] public short Minute; - [MarshalAs(UnmanagedType.U2)] public short Second; - [MarshalAs(UnmanagedType.U2)] public short Milliseconds; - - public SYSTEMTIME(DateTime dt) - { - dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC - Year = (short)dt.Year; - Month = (short)dt.Month; - DayOfWeek = (short)dt.DayOfWeek; - Day = (short)dt.Day; - Hour = (short)dt.Hour; - Minute = (short)dt.Minute; - Second = (short)dt.Second; - Milliseconds = (short)dt.Millisecond; - } - - public DateTime ToDateTime() - { - return new(Year, Month, Day, Hour, Minute, Second, Milliseconds, DateTimeKind.Utc); - } - } - - public enum FINDEX_INFO_LEVELS - { - FindExInfoStandard = 0, - FindExInfoBasic = 1 - } - - public enum FINDEX_SEARCH_OPS - { - FindExSearchNameMatch = 0, - FindExSearchLimitToDirectories = 1, - FindExSearchLimitToDevices = 2 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct WIN32_FIND_DATA - { - public uint dwFileAttributes; - - public FILETIME ftCreationTime; - public FILETIME ftLastAccessTime; - public FILETIME ftLastWriteTime; - - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string cFileName; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr FindFirstFileExFromApp( - string lpFileName, - FINDEX_INFO_LEVELS fInfoLevelId, - out WIN32_FIND_DATA lpFindFileData, - FINDEX_SEARCH_OPS fSearchOp, - IntPtr lpSearchFilter, - int dwAdditionalFlags); - - public const int FIND_FIRST_EX_CASE_SENSITIVE = 1; - public const int FIND_FIRST_EX_LARGE_FETCH = 2; - - [DllImport("api-ms-win-core-file-l1-1-0.dll", CharSet = CharSet.Unicode)] - public static extern bool FindNextFile( - IntPtr hFindFile, - out WIN32_FIND_DATA lpFindFileData); - - [DllImport("api-ms-win-core-file-l1-1-0.dll")] - public static extern bool FindClose( - IntPtr hFindFile); - - [DllImport("api-ms-win-core-timezone-l1-1-0.dll", SetLastError = true)] - public static extern bool FileTimeToSystemTime( - ref FILETIME lpFileTime, - out SYSTEMTIME lpSystemTime); - - public static bool GetWin32FindDataForPath( - string targetPath, - out WIN32_FIND_DATA findData) - { - FINDEX_INFO_LEVELS findInfoLevel = FINDEX_INFO_LEVELS.FindExInfoBasic; - - int additionalFlags = FIND_FIRST_EX_LARGE_FETCH; - - IntPtr hFile = FindFirstFileExFromApp( - targetPath, - findInfoLevel, - out findData, - FINDEX_SEARCH_OPS.FindExSearchNameMatch, - IntPtr.Zero, - additionalFlags); - - if (hFile.ToInt64() != -1) - { - FindClose(hFile); - - return true; - } - - return false; - } - } -} diff --git a/src/Files.App/Helpers/NaturalStringComparer.cs b/src/Files.App/Helpers/NaturalStringComparer.cs index e786d92e7a36..2e22c266417c 100644 --- a/src/Files.App/Helpers/NaturalStringComparer.cs +++ b/src/Files.App/Helpers/NaturalStringComparer.cs @@ -1,48 +1,13 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - namespace Files.App.Helpers { - internal static class SafeNativeMethods - { - public const Int32 NORM_IGNORECASE = 0x00000001; - public const Int32 NORM_IGNORENONSPACE = 0x00000002; - public const Int32 NORM_IGNORESYMBOLS = 0x00000004; - public const Int32 LINGUISTIC_IGNORECASE = 0x00000010; - public const Int32 LINGUISTIC_IGNOREDIACRITIC = 0x00000020; - public const Int32 NORM_IGNOREKANATYPE = 0x00010000; - public const Int32 NORM_IGNOREWIDTH = 0x00020000; - public const Int32 NORM_LINGUISTIC_CASING = 0x08000000; - public const Int32 SORT_STRINGSORT = 0x00001000; - public const Int32 SORT_DIGITSASNUMBERS = 0x00000008; - - public const String LOCALE_NAME_USER_DEFAULT = null; - public const String LOCALE_NAME_INVARIANT = ""; - public const String LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale"; - - [DllImport("api-ms-win-core-string-l1-1-0.dll", CharSet = CharSet.Unicode)] - public static extern Int32 CompareStringEx( - String localeName, - Int32 flags, - String str1, - Int32 count1, - String str2, - Int32 count2, - IntPtr versionInformation, - IntPtr reserved, - Int32 param - ); - } - public sealed class NaturalStringComparer { public static IComparer GetForProcessor() { - return NativeWinApiHelper.IsRunningOnArm ? new StringComparerArm64() : new StringComparerDefault(); + return Win32Helper.IsRunningOnArm ? new StringComparerArm64() : new StringComparerDefault(); } private sealed class StringComparerArm64 : IComparer @@ -57,9 +22,9 @@ private sealed class StringComparerDefault : IComparer { public int Compare(object a, object b) { - return SafeNativeMethods.CompareStringEx( - SafeNativeMethods.LOCALE_NAME_USER_DEFAULT, - SafeNativeMethods.SORT_DIGITSASNUMBERS, // Add other flags if required. + return Win32PInvoke.CompareStringEx( + Win32PInvoke.LOCALE_NAME_USER_DEFAULT, + Win32PInvoke.SORT_DIGITSASNUMBERS, // Add other flags if required. a?.ToString(), a?.ToString().Length ?? 0, b?.ToString(), @@ -70,4 +35,4 @@ public int Compare(object a, object b) } } } -} \ No newline at end of file +} diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index e76430b91f90..cb701523f36b 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs @@ -15,7 +15,7 @@ public static class NavigationHelpers { private static MainPageViewModel MainPageViewModel { get; } = Ioc.Default.GetRequiredService(); private static DrivesViewModel DrivesViewModel { get; } = Ioc.Default.GetRequiredService(); - private static INetworkDrivesService NetworkDrivesService { get; } = Ioc.Default.GetRequiredService(); + private static NetworkDrivesViewModel NetworkDrivesViewModel { get; } = Ioc.Default.GetRequiredService(); public static Task OpenPathInNewTab(string? path, bool focusNewTab) { @@ -169,7 +169,7 @@ private static async Task UpdateTabInfoAsync(TabBarItem tabItem, object navigati } else if (PathNormalization.NormalizePath(PathNormalization.GetPathRoot(currentPath)) == normalizedCurrentPath) // If path is a drive's root { - var matchingDrive = NetworkDrivesService.Drives.Cast().FirstOrDefault(netDrive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(netDrive.Path), StringComparison.OrdinalIgnoreCase)); + var matchingDrive = NetworkDrivesViewModel.Drives.Cast().FirstOrDefault(netDrive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(netDrive.Path), StringComparison.OrdinalIgnoreCase)); matchingDrive ??= DrivesViewModel.Drives.Cast().FirstOrDefault(drive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(drive.Path), StringComparison.OrdinalIgnoreCase)); tabLocationHeader = matchingDrive is not null ? matchingDrive.Text : normalizedCurrentPath; } @@ -336,9 +336,9 @@ public static async Task OpenItemsWithExecutableAsync(IShellPage associatedInsta public static async Task OpenPath(string path, IShellPage associatedInstance, FilesystemItemType? itemType = null, bool openSilent = false, bool openViaApplicationPicker = false, IEnumerable? selectItems = null, string? args = default, bool forceOpenInNewTab = false) { string previousDir = associatedInstance.FilesystemViewModel.WorkingDirectory; - bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); - bool isDirectory = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Directory); - bool isReparsePoint = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.ReparsePoint); + bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isDirectory = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Directory); + bool isReparsePoint = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.ReparsePoint); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); bool isScreenSaver = FileExtensionHelpers.IsScreenSaverFile(path); bool isTag = path.StartsWith("tag:"); @@ -392,16 +392,16 @@ public static async Task OpenPath(string path, IShellPage associatedInstan else if (isReparsePoint) { if (!isDirectory && - NativeFindStorageItemHelper.GetWin32FindDataForPath(path, out var findData) && - findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK) + Win32Helper.GetWin32FindDataForPath(path, out var findData) && + findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK) { - shortcutInfo.TargetPath = NativeFileOperationsHelper.ParseSymLink(path); + shortcutInfo.TargetPath = Win32Helper.ParseSymLink(path); } itemType ??= isDirectory ? FilesystemItemType.Directory : FilesystemItemType.File; } else if (isHiddenItem) { - itemType = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Directory) ? FilesystemItemType.Directory : FilesystemItemType.File; + itemType = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Directory) ? FilesystemItemType.Directory : FilesystemItemType.File; } else { @@ -443,7 +443,7 @@ private static async Task OpenLibrary(string path, IShellPage IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService(); var opened = (FilesystemResult)false; - bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); if (isHiddenItem) { await OpenPath(forceOpenInNewTab, UserSettingsService.FoldersSettingsService.OpenFoldersInNewTab, path, associatedInstance); @@ -463,7 +463,7 @@ private static async Task OpenDirectory(string path, IShellPag IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService(); var opened = (FilesystemResult)false; - bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); if (isShortcut) @@ -507,7 +507,7 @@ private static async Task OpenDirectory(string path, IShellPag private static async Task OpenFile(string path, IShellPage associatedInstance, ShellLinkItem shortcutInfo, bool openViaApplicationPicker = false, string? args = default) { var opened = (FilesystemResult)false; - bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path) || !string.IsNullOrEmpty(shortcutInfo.TargetPath); if (isShortcut) diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs index 6165db4610c6..71763e791da8 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs @@ -1,6 +1,10 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using Microsoft.Extensions.Logging; +using Windows.Foundation.Metadata; +using Windows.System; + namespace Files.App.Helpers { /// @@ -108,5 +112,50 @@ public static List WhoIsLocking(string[] resources) return processes; } + + // https://stackoverflow.com/questions/54456140/how-to-detect-were-running-under-the-arm64-version-of-windows-10-in-net + // https://learn.microsoft.com/windows/win32/sysinfo/image-file-machine-constants + private static bool? isRunningOnArm = null; + public static bool IsRunningOnArm + { + get + { + if (isRunningOnArm is null) + { + isRunningOnArm = IsArmProcessor(); + App.Logger.LogInformation("Running on ARM: {0}", isRunningOnArm); + } + return isRunningOnArm ?? false; + } + } + + private static bool IsArmProcessor() + { + var handle = System.Diagnostics.Process.GetCurrentProcess().Handle; + if (!Win32PInvoke.IsWow64Process2(handle, out _, out var nativeMachine)) + { + return false; + } + return (nativeMachine == 0xaa64 || + nativeMachine == 0x01c0 || + nativeMachine == 0x01c2 || + nativeMachine == 0x01c4); + } + + private static bool? isHasThreadAccessPropertyPresent = null; + + public static bool IsHasThreadAccessPropertyPresent + { + get + { + isHasThreadAccessPropertyPresent ??= ApiInformation.IsPropertyPresent(typeof(DispatcherQueue).FullName, "HasThreadAccess"); + return isHasThreadAccessPropertyPresent ?? false; + } + } + + public static Task GetFileAssociationAsync(string filePath) + { + return Win32Helper.GetFileAssociationAsync(filePath, true); + } } } diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs index 138bae36e0f5..1a989574f050 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs @@ -2,11 +2,13 @@ // Licensed under the MIT License. See the LICENSE. using Microsoft.Extensions.Logging; +using Microsoft.Win32.SafeHandles; using System.Collections.Concurrent; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Windows.Forms; using Vanara.PInvoke; @@ -638,20 +640,6 @@ private static bool IsAlphaBitmap(BitmapData bmpData) return false; } - // There is usually no need to define Win32 COM interfaces/P-Invoke methods here. - // The Vanara library contains the definitions for all members of Shell32.dll, User32.dll and more - // The ones below are due to bugs in the current version of the library and can be removed once fixed - // Structure used by SHQueryRecycleBin. - [StructLayout(LayoutKind.Sequential, Pack = 0)] - public struct SHQUERYRBINFO - { - public int cbSize; - - public long i64Size; - - public long i64NumItems; - } - public static IEnumerable GetDesktopWindows() { HWND prevHwnd = HWND.NULL; @@ -818,10 +806,6 @@ public static void OpenFolderInExistingShellWindow(string folderPath) } } - // Get information from recycle bin. - [DllImport(Lib.Shell32, SetLastError = false, CharSet = CharSet.Unicode)] - public static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo); - public static async Task InstallInf(string filePath) { try @@ -893,5 +877,275 @@ private static Process CreatePowershellProcess(string command, bool runAsAdmin) return process; } + + public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite = true) + { + return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, + Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? Win32PInvoke.CREATE_ALWAYS : Win32PInvoke.OPEN_ALWAYS, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); + } + + public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false, uint flags = 0) + { + return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, + Win32PInvoke.GENERIC_READ | (readWrite ? Win32PInvoke.GENERIC_WRITE : 0), (uint)(Win32PInvoke.FILE_SHARE_READ | (readWrite ? 0 : Win32PInvoke.FILE_SHARE_WRITE)), IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics | flags, IntPtr.Zero), true); + } + + public static bool GetFileDateModified(string filePath, out FILETIME dateModified) + { + using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.GENERIC_READ, Win32PInvoke.FILE_SHARE_READ, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); + return Win32PInvoke.GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified); + } + + public static bool SetFileDateModified(string filePath, FILETIME dateModified) + { + using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); + return Win32PInvoke.SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified); + } + + public static bool HasFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (Win32PInvoke.GetFileAttributesExFromApp( + lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return (lpFileInfo.dwFileAttributes & dwAttrs) == dwAttrs; + } + return false; + } + + public static bool SetFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (!Win32PInvoke.GetFileAttributesExFromApp( + lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return false; + } + return Win32PInvoke.SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes | dwAttrs); + } + + public static bool UnsetFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (!Win32PInvoke.GetFileAttributesExFromApp( + lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return false; + } + return Win32PInvoke.SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes & ~dwAttrs); + } + + public static string ReadStringFromFile(string filePath) + { + IntPtr hFile = Win32PInvoke.CreateFileFromApp(filePath, + Win32PInvoke.GENERIC_READ, + Win32PInvoke.FILE_SHARE_READ, + IntPtr.Zero, + Win32PInvoke.OPEN_EXISTING, + (uint)Win32PInvoke.File_Attributes.BackupSemantics, + IntPtr.Zero); + + if (hFile.ToInt64() == -1) + { + return null; + } + + const int BUFFER_LENGTH = 4096; + byte[] buffer = new byte[BUFFER_LENGTH]; + int dwBytesRead; + string szRead = string.Empty; + + unsafe + { + using (MemoryStream ms = new MemoryStream()) + using (StreamReader reader = new StreamReader(ms, true)) + { + while (true) + { + fixed (byte* pBuffer = buffer) + { + if (Win32PInvoke.ReadFile(hFile, pBuffer, BUFFER_LENGTH - 1, &dwBytesRead, IntPtr.Zero) && dwBytesRead > 0) + { + ms.Write(buffer, 0, dwBytesRead); + } + else + { + break; + } + } + } + ms.Position = 0; + szRead = reader.ReadToEnd(); + } + } + + Win32PInvoke.CloseHandle(hFile); + + return szRead; + } + + public static bool WriteStringToFile(string filePath, string str, Win32PInvoke.File_Attributes flags = 0) + { + IntPtr hStream = Win32PInvoke.CreateFileFromApp(filePath, + Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, Win32PInvoke.CREATE_ALWAYS, (uint)(Win32PInvoke.File_Attributes.BackupSemantics | flags), IntPtr.Zero); + if (hStream.ToInt64() == -1) + { + return false; + } + byte[] buff = Encoding.UTF8.GetBytes(str); + int dwBytesWritten; + unsafe + { + fixed (byte* pBuff = buff) + { + Win32PInvoke.WriteFile(hStream, pBuff, buff.Length, &dwBytesWritten, IntPtr.Zero); + } + } + Win32PInvoke.CloseHandle(hStream); + return true; + } + + public static bool WriteBufferToFileWithProgress(string filePath, byte[] buffer, Win32PInvoke.LPOVERLAPPED_COMPLETION_ROUTINE callback) + { + using var hFile = CreateFileForWrite(filePath); + + if (hFile.IsInvalid) + { + return false; + } + + NativeOverlapped nativeOverlapped = new NativeOverlapped(); + bool result = Win32PInvoke.WriteFileEx(hFile.DangerousGetHandle(), buffer, (uint)buffer.LongLength, ref nativeOverlapped, callback); + + if (!result) + { + System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error()); + } + + return result; + } + + // https://www.pinvoke.net/default.aspx/kernel32/GetFileInformationByHandleEx.html + public static ulong? GetFolderFRN(string folderPath) + { + using var handle = OpenFileForRead(folderPath); + if (!handle.IsInvalid) + { + var fileStruct = new Win32PInvoke.FILE_ID_BOTH_DIR_INFO(); + if (Win32PInvoke.GetFileInformationByHandleEx(handle.DangerousGetHandle(), Win32PInvoke.FILE_INFO_BY_HANDLE_CLASS.FileIdBothDirectoryInfo, out fileStruct, (uint)Marshal.SizeOf(fileStruct))) + { + return (ulong)fileStruct.FileId; + } + } + return null; + } + + public static ulong? GetFileFRN(string filePath) + { + using var handle = OpenFileForRead(filePath); + if (!handle.IsInvalid) + { + try + { + var fileID = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileIdInfo); + return BitConverter.ToUInt64(fileID.FileId.Identifier, 0); + } + catch { } + } + return null; + } + + public static long? GetFileSizeOnDisk(string filePath) + { + using var handle = OpenFileForRead(filePath); + if (!handle.IsInvalid) + { + try + { + var fileAllocationInfo = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo); + return fileAllocationInfo.AllocationSize; + } + catch { } + } + return null; + } + + // https://github.com/rad1oactive/BetterExplorer/blob/master/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Shell/ReparsePoint.cs + public static string ParseSymLink(string path) + { + using var handle = OpenFileForRead(path, false, 0x00200000); + if (!handle.IsInvalid) + { + Win32PInvoke.REPARSE_DATA_BUFFER buffer = new Win32PInvoke.REPARSE_DATA_BUFFER(); + if (Win32PInvoke.DeviceIoControl(handle.DangerousGetHandle(), Win32PInvoke.FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out (nint)buffer, Win32PInvoke.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero)) + { + var subsString = new string(buffer.PathBuffer, ((buffer.SubsNameOffset / 2) + 2), buffer.SubsNameLength / 2); + var printString = new string(buffer.PathBuffer, ((buffer.PrintNameOffset / 2) + 2), buffer.PrintNameLength / 2); + var normalisedTarget = printString ?? subsString; + if (string.IsNullOrEmpty(normalisedTarget)) + { + normalisedTarget = subsString; + if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) + { + normalisedTarget = normalisedTarget.Substring(4); + } + } + if (buffer.ReparseTag == Win32PInvoke.IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) + { + // Target is relative, get the absolute path + normalisedTarget = normalisedTarget.TrimStart(Path.DirectorySeparatorChar); + path = path.TrimEnd(Path.DirectorySeparatorChar); + normalisedTarget = Path.GetFullPath(Path.Combine(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar)), normalisedTarget)); + } + return normalisedTarget; + } + } + return null; + } + + // https://stackoverflow.com/a/7988352 + public static IEnumerable<(string Name, long Size)> GetAlternateStreams(string path) + { + Win32PInvoke.WIN32_FIND_STREAM_DATA findStreamData = new Win32PInvoke.WIN32_FIND_STREAM_DATA(); + IntPtr hFile = Win32PInvoke.FindFirstStreamW(path, Win32PInvoke.StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); + + if (hFile.ToInt64() != -1) + { + do + { + // The documentation for FindFirstStreamW says that it is always a ::$DATA + // stream type, but FindNextStreamW doesn't guarantee that for subsequent + // streams so we check to make sure + if (findStreamData.cStreamName.EndsWith(":$DATA") && findStreamData.cStreamName != "::$DATA") + { + yield return (findStreamData.cStreamName, findStreamData.StreamSize); + } + } + while (Win32PInvoke.FindNextStreamW(hFile, findStreamData)); + + Win32PInvoke.FindClose(hFile); + } + } + + public static bool GetWin32FindDataForPath(string targetPath, out Win32PInvoke.WIN32_FIND_DATA findData) + { + Win32PInvoke.FINDEX_INFO_LEVELS findInfoLevel = Win32PInvoke.FINDEX_INFO_LEVELS.FindExInfoBasic; + + int additionalFlags = Win32PInvoke.FIND_FIRST_EX_LARGE_FETCH; + + IntPtr hFile = Win32PInvoke.FindFirstFileExFromApp( + targetPath, + findInfoLevel, + out findData, + Win32PInvoke.FINDEX_SEARCH_OPS.FindExSearchNameMatch, + IntPtr.Zero, + additionalFlags); + + if (hFile.ToInt64() != -1) + { + Win32PInvoke.FindClose(hFile); + + return true; + } + + return false; + } } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs index 0bdf6dfcf2e5..f1a573124ce9 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs @@ -26,10 +26,46 @@ public static partial class Win32PInvoke public const uint GENERIC_WRITE = 0x40000000; public const int FILE_SHARE_READ = 0x00000001; public const int FILE_SHARE_WRITE = 0x00000002; + public const uint FILE_SHARE_DELETE = 0x00000004; public const int OPEN_EXISTING = 3; public const int FSCTL_LOCK_VOLUME = 0x00090018; public const int FSCTL_DISMOUNT_VOLUME = 0x00090020; public const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808; public const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804; + + public const uint FILE_APPEND_DATA = 0x0004; + public const uint FILE_WRITE_ATTRIBUTES = 0x100; + + + public const uint FILE_BEGIN = 0; + public const uint FILE_END = 2; + + public const uint CREATE_ALWAYS = 2; + public const uint CREATE_NEW = 1; + public const uint OPEN_ALWAYS = 4; + public const uint TRUNCATE_EXISTING = 5; + + public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; + public const int FSCTL_GET_REPARSE_POINT = 0x000900A8; + public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; + public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C; + + public const int FIND_FIRST_EX_CASE_SENSITIVE = 1; + public const int FIND_FIRST_EX_LARGE_FETCH = 2; + + public const int NORM_IGNORECASE = 0x00000001; + public const int NORM_IGNORENONSPACE = 0x00000002; + public const int NORM_IGNORESYMBOLS = 0x00000004; + public const int LINGUISTIC_IGNORECASE = 0x00000010; + public const int LINGUISTIC_IGNOREDIACRITIC = 0x00000020; + public const int NORM_IGNOREKANATYPE = 0x00010000; + public const int NORM_IGNOREWIDTH = 0x00020000; + public const int NORM_LINGUISTIC_CASING = 0x08000000; + public const int SORT_STRINGSORT = 0x00001000; + public const int SORT_DIGITSASNUMBERS = 0x00000008; + + public const string LOCALE_NAME_USER_DEFAULT = null; + public const string LOCALE_NAME_INVARIANT = ""; + public const string LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale"; } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs index f12643361ab7..768505990b0a 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs @@ -15,5 +15,172 @@ public enum RM_APP_TYPE RmConsole = 5, RmCritical = 1000 } + + public enum File_Attributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Directory = 0x00000010, + Archive = 0x00000020, + Device = 0x00000040, + Normal = 0x00000080, + Temporary = 0x00000100, + SparseFile = 0x00000200, + ReparsePoint = 0x00000400, + Compressed = 0x00000800, + Offline = 0x00001000, + NotContentIndexed = 0x00002000, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + FirstPipeInstance = 0x00080000 + } + + public enum FILE_INFO_BY_HANDLE_CLASS + { + FileBasicInfo = 0, + FileStandardInfo = 1, + FileNameInfo = 2, + FileRenameInfo = 3, + FileDispositionInfo = 4, + FileAllocationInfo = 5, + FileEndOfFileInfo = 6, + FileStreamInfo = 7, + FileCompressionInfo = 8, + FileAttributeTagInfo = 9, + FileIdBothDirectoryInfo = 10,// 0x0A + FileIdBothDirectoryRestartInfo = 11, // 0xB + FileIoPriorityHintInfo = 12, // 0xC + FileRemoteProtocolInfo = 13, // 0xD + FileFullDirectoryInfo = 14, // 0xE + FileFullDirectoryRestartInfo = 15, // 0xF + FileStorageInfo = 16, // 0x10 + FileAlignmentInfo = 17, // 0x11 + FileIdInfo = 18, // 0x12 + FileIdExtdDirectoryInfo = 19, // 0x13 + FileIdExtdDirectoryRestartInfo = 20, // 0x14 + MaximumFileInfoByHandlesClass + } + + public enum GET_FILEEX_INFO_LEVELS + { + GetFileExInfoStandard, + } + + public enum StreamInfoLevels + { + FindStreamInfoStandard = 0 + } + + [Flags] + public enum CryptProtectFlags + { + CRYPTPROTECT_UI_FORBIDDEN = 0x1, + CRYPTPROTECT_LOCAL_MACHINE = 0x4, + CRYPTPROTECT_CRED_SYNC = 0x8, + CRYPTPROTECT_AUDIT = 0x10, + CRYPTPROTECT_NO_RECOVERY = 0x20, + CRYPTPROTECT_VERIFY_PROTECTION = 0x40, + CRYPTPROTECT_CRED_REGENERATE = 0x80 + } + + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted + } + + [Serializable] + public enum TOKEN_TYPE + { + TokenPrimary = 1, + + TokenImpersonation = 2 + } + + [Flags] + public enum TokenAccess : uint + { + TOKEN_ASSIGN_PRIMARY = 0x0001, + TOKEN_DUPLICATE = 0x0002, + TOKEN_IMPERSONATE = 0x0004, + TOKEN_QUERY = 0x0008, + TOKEN_QUERY_SOURCE = 0x0010, + TOKEN_ADJUST_PRIVILEGES = 0x0020, + TOKEN_ADJUST_GROUPS = 0x0040, + TOKEN_ADJUST_DEFAULT = 0x0080, + TOKEN_ADJUST_SESSIONID = 0x0100, + TOKEN_ALL_ACCESS_P = 0x000F00FF, + TOKEN_ALL_ACCESS = 0x000F01FF, + TOKEN_READ = 0x00020008, + TOKEN_WRITE = 0x000200E0, + TOKEN_EXECUTE = 0x00020000 + } + + public enum FINDEX_INFO_LEVELS + { + FindExInfoStandard = 0, + FindExInfoBasic = 1 + } + + public enum FINDEX_SEARCH_OPS + { + FindExSearchNameMatch = 0, + FindExSearchLimitToDirectories = 1, + FindExSearchLimitToDevices = 2 + } + + [Flags] + public enum ClassContext : uint + { + LocalServer = 0x4 + } } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs index 1cbdbb628d2e..c2f9c11fe88f 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs @@ -1,7 +1,9 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using System.IO; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; using Vanara.PInvoke; using static Vanara.PInvoke.User32; @@ -16,6 +18,11 @@ public delegate void LpoverlappedCompletionRoutine( OVERLAPPED lpOverlapped ); + public delegate void LPOVERLAPPED_COMPLETION_ROUTINE( + uint dwErrorCode, + uint dwNumberOfBytesTransfered, + ref NativeOverlapped lpOverlapped); + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] public static extern int RmRegisterResources( uint pSessionHandle, @@ -182,7 +189,7 @@ public static extern bool DeviceIoControl( uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, - IntPtr lpOutBuffer, + out IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped @@ -209,5 +216,194 @@ public static extern int ToUnicode( int bufferSize, uint flags ); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern IntPtr CreateFileFromApp( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr SecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile + ); + + [DllImport("api-ms-win-core-io-l1-1-0.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeviceIoControl( + IntPtr hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + //IntPtr lpOutBuffer, + out REPARSE_DATA_BUFFER outBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetFileAttributesExFromApp( + string lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetFileAttributesFromApp( + string lpFileName, + FileAttributes dwFileAttributes); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", ExactSpelling = true, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern uint SetFilePointer( + IntPtr hFile, + long lDistanceToMove, + IntPtr lpDistanceToMoveHigh, + uint dwMoveMethod + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public unsafe static extern bool ReadFile( + IntPtr hFile, + byte* lpBuffer, + int nBufferLength, + int* lpBytesReturned, + IntPtr lpOverlapped + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public unsafe static extern bool WriteFile( + IntPtr hFile, + byte* lpBuffer, + int nBufferLength, + int* lpBytesWritten, + IntPtr lpOverlapped + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern bool WriteFileEx( + IntPtr hFile, + byte[] lpBuffer, + uint nNumberOfBytesToWrite, + [In] ref NativeOverlapped lpOverlapped, + LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool GetFileTime([In] IntPtr hFile, out FILETIME lpCreationTime, out FILETIME lpLastAccessTime, out FILETIME lpLastWriteTime); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool SetFileTime([In] IntPtr hFile, in FILETIME lpCreationTime, in FILETIME lpLastAccessTime, in FILETIME lpLastWriteTime); + + [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, out FILE_ID_BOTH_DIR_INFO dirInfo, uint dwBufferSize); + + [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, IntPtr dirInfo, uint dwBufferSize); + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FindNextStreamW(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); + + [DllImport("Shcore.dll", SetLastError = true)] + public static extern int GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY); + + [DllImport("api-ms-win-core-processthreads-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken([In] IntPtr ProcessHandle, TokenAccess DesiredAccess, out IntPtr TokenHandle); + + [DllImport("api-ms-win-core-processthreads-l1-1-2.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("api-ms-win-security-base-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetTokenInformation(IntPtr hObject, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr pTokenInfo, int tokenInfoLength, out int returnLength); + + [DllImport("api-ms-win-security-base-l1-1-0.dll", ExactSpelling = true, SetLastError = true)] + public static extern int GetLengthSid(IntPtr pSid); + + [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptUnprotectData( + in CRYPTOAPI_BLOB pDataIn, + StringBuilder szDataDescr, + in CRYPTOAPI_BLOB pOptionalEntropy, + IntPtr pvReserved, + IntPtr pPromptStruct, + CryptProtectFlags dwFlags, + out CRYPTOAPI_BLOB pDataOut); + + [DllImport("api-ms-win-core-wow64-l1-1-1.dll", SetLastError = true)] + public static extern bool IsWow64Process2( + IntPtr process, + out ushort processMachine, + out ushort nativeMachine); + + [DllImport("api-ms-win-core-file-l1-1-0.dll", CharSet = CharSet.Unicode)] + public static extern bool FindNextFile( + IntPtr hFindFile, + out WIN32_FIND_DATA lpFindFileData); + + [DllImport("api-ms-win-core-file-l1-1-0.dll")] + public static extern bool FindClose( + IntPtr hFindFile); + + [DllImport("api-ms-win-core-timezone-l1-1-0.dll", SetLastError = true)] + public static extern bool FileTimeToSystemTime( + ref FILETIME lpFileTime, + out SYSTEMTIME lpSystemTime); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr FindFirstFileExFromApp( + string lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + out WIN32_FIND_DATA lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + IntPtr lpSearchFilter, + int dwAdditionalFlags); + + [DllImport("api-ms-win-core-string-l1-1-0.dll", CharSet = CharSet.Unicode)] + public static extern int CompareStringEx( + string localeName, + int flags, + string str1, + int count1, + string str2, + int count2, + IntPtr versionInformation, + IntPtr reserved, + int param + ); + + [DllImport("shell32.dll", EntryPoint = "#865", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool _IsElevationRequired([MarshalAs(UnmanagedType.LPWStr)] string pszPath); + + [DllImport("shlwapi.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] + public static extern HRESULT SHCreateStreamOnFileEx(string pszFile, STGM grfMode, uint dwAttributes, uint fCreate, IntPtr pstmTemplate, out IntPtr ppstm); + + [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] + public static extern HRESULT SHCreateItemFromParsingName(string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv); + + [DllImport("ole32.dll", CallingConvention = CallingConvention.StdCall)] + public static extern HRESULT CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, ClassContext dwClsContext, ref Guid riid, out IntPtr ppv); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + public static extern uint RegisterApplicationRestart(string pwzCommandLine, int dwFlags); + + [DllImport(Lib.Shell32, SetLastError = false, CharSet = CharSet.Unicode)] + public static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo); } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs index 584df725c71e..e0d5cb244240 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs @@ -1,7 +1,9 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using System.IO; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; namespace Files.App.Helpers { @@ -69,5 +71,160 @@ public unsafe struct FILE_NOTIFY_INFORMATION public uint FileNameLength; public fixed char FileName[1]; } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct REPARSE_DATA_BUFFER + { + public uint ReparseTag; + public short ReparseDataLength; + public short Reserved; + public short SubsNameOffset; + public short SubsNameLength; + public short PrintNameOffset; + public short PrintNameLength; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_REPARSE_DATA_BUFFER_SIZE)] + public char[] PathBuffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WIN32_FILE_ATTRIBUTE_DATA + { + public FileAttributes dwFileAttributes; + public FILETIME ftCreationTime; + public FILETIME ftLastAccessTime; + public FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct FILE_ID_BOTH_DIR_INFO + { + public uint NextEntryOffset; + public uint FileIndex; + public long CreationTime; + public long LastAccessTime; + public long LastWriteTime; + public long ChangeTime; + public long EndOfFile; + public long AllocationSize; + public uint FileAttributes; + public uint FileNameLength; + public uint EaSize; + public char ShortNameLength; + [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 12)] + public string ShortName; + public long FileId; + [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)] + public string FileName; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] + public struct FILE_STREAM_INFO + { + public uint NextEntryOffset; + public uint StreamNameLength; + public long StreamSize; + public long StreamAllocationSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] + public string StreamName; + } + + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public sealed class WIN32_FIND_STREAM_DATA + { + public long StreamSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] + public string cStreamName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_USER + { + public SID_AND_ATTRIBUTES User; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPTOAPI_BLOB + { + public uint cbData; + + public IntPtr pbData; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SYSTEMTIME + { + [MarshalAs(UnmanagedType.U2)] public short Year; + [MarshalAs(UnmanagedType.U2)] public short Month; + [MarshalAs(UnmanagedType.U2)] public short DayOfWeek; + [MarshalAs(UnmanagedType.U2)] public short Day; + [MarshalAs(UnmanagedType.U2)] public short Hour; + [MarshalAs(UnmanagedType.U2)] public short Minute; + [MarshalAs(UnmanagedType.U2)] public short Second; + [MarshalAs(UnmanagedType.U2)] public short Milliseconds; + + public SYSTEMTIME(DateTime dt) + { + dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC + Year = (short)dt.Year; + Month = (short)dt.Month; + DayOfWeek = (short)dt.DayOfWeek; + Day = (short)dt.Day; + Hour = (short)dt.Hour; + Minute = (short)dt.Minute; + Second = (short)dt.Second; + Milliseconds = (short)dt.Millisecond; + } + + public DateTime ToDateTime() + { + return new(Year, Month, Day, Hour, Minute, Second, Milliseconds, DateTimeKind.Utc); + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WIN32_FIND_DATA + { + public uint dwFileAttributes; + + public FILETIME ftCreationTime; + public FILETIME ftLastAccessTime; + public FILETIME ftLastWriteTime; + + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + // There is usually no need to define Win32 COM interfaces/P-Invoke methods here. + // The Vanara library contains the definitions for all members of Shell32.dll, User32.dll and more + // The ones below are due to bugs in the current version of the library and can be removed once fixed + // Structure used by SHQueryRecycleBin. + [StructLayout(LayoutKind.Sequential, Pack = 0)] + public struct SHQUERYRBINFO + { + public int cbSize; + + public long i64Size; + + public long i64NumItems; + } } } diff --git a/src/Files.App/Services/SideloadUpdateService.cs b/src/Files.App/Services/SideloadUpdateService.cs index 83549b8207b1..e015105e40c9 100644 --- a/src/Files.App/Services/SideloadUpdateService.cs +++ b/src/Files.App/Services/SideloadUpdateService.cs @@ -15,9 +15,6 @@ namespace Files.App.Services { public sealed class SideloadUpdateService : ObservableObject, IUpdateService, IDisposable { - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern uint RegisterApplicationRestart(string pwzCommandLine, int dwFlags); - private const string SIDELOAD_STABLE = "https://cdn.files.community/files/stable/Files.Package.appinstaller"; private const string SIDELOAD_PREVIEW = "https://cdn.files.community/files/preview/Files.Package.appinstaller"; @@ -229,7 +226,7 @@ private async Task ApplyPackageUpdateAsync() { PackageManager packageManager = new PackageManager(); - var restartStatus = RegisterApplicationRestart(null, 0); + var restartStatus = Win32PInvoke.RegisterApplicationRestart(null, 0); App.AppModel.ForceProcessTermination = true; Logger?.LogInformation($"Register for restart: {restartStatus}"); diff --git a/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs b/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs index 738aed5ef537..f54087260cc2 100644 --- a/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs +++ b/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs @@ -7,7 +7,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; namespace Files.App.Services.SizeProvider { diff --git a/src/Files.App/Utils/FileTags/FileTagsHelper.cs b/src/Files.App/Utils/FileTags/FileTagsHelper.cs index 4149057dfe0a..208a568f4eee 100644 --- a/src/Files.App/Utils/FileTags/FileTagsHelper.cs +++ b/src/Files.App/Utils/FileTags/FileTagsHelper.cs @@ -18,17 +18,17 @@ public static class FileTagsHelper public static string[] ReadFileTag(string filePath) { - var tagString = NativeFileOperationsHelper.ReadStringFromFile($"{filePath}:files"); + var tagString = Win32Helper.ReadStringFromFile($"{filePath}:files"); return tagString?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? []; } public static async void WriteFileTag(string filePath, string[] tag) { - var isDateOk = NativeFileOperationsHelper.GetFileDateModified(filePath, out var dateModified); // Backup date modified - var isReadOnly = NativeFileOperationsHelper.HasFileAttribute(filePath, IO.FileAttributes.ReadOnly); + var isDateOk = Win32Helper.GetFileDateModified(filePath, out var dateModified); // Backup date modified + var isReadOnly = Win32Helper.HasFileAttribute(filePath, IO.FileAttributes.ReadOnly); if (isReadOnly) // Unset read-only attribute (#7534) { - NativeFileOperationsHelper.UnsetFileAttribute(filePath, IO.FileAttributes.ReadOnly); + Win32Helper.UnsetFileAttribute(filePath, IO.FileAttributes.ReadOnly); } if (!tag.Any()) { @@ -36,7 +36,7 @@ public static async void WriteFileTag(string filePath, string[] tag) } else if (ReadFileTag(filePath) is not string[] arr || !tag.SequenceEqual(arr)) { - var result = NativeFileOperationsHelper.WriteStringToFile($"{filePath}:files", string.Join(',', tag)); + var result = Win32Helper.WriteStringToFile($"{filePath}:files", string.Join(',', tag)); if (result == false) { ContentDialog dialog = new() @@ -54,11 +54,11 @@ public static async void WriteFileTag(string filePath, string[] tag) } if (isReadOnly) // Restore read-only attribute (#7534) { - NativeFileOperationsHelper.SetFileAttribute(filePath, IO.FileAttributes.ReadOnly); + Win32Helper.SetFileAttribute(filePath, IO.FileAttributes.ReadOnly); } if (isDateOk) { - NativeFileOperationsHelper.SetFileDateModified(filePath, dateModified); // Restore date modified + Win32Helper.SetFileDateModified(filePath, dateModified); // Restore date modified } } @@ -105,7 +105,7 @@ public static void UpdateTagsDb() } } - public static ulong? GetFileFRN(string filePath) => NativeFileOperationsHelper.GetFileFRN(filePath); + public static ulong? GetFileFRN(string filePath) => Win32Helper.GetFileFRN(filePath); public static Task GetFileFRN(IStorageItem item) { diff --git a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs index c2309bee5608..25738c05f0f4 100644 --- a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs +++ b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs @@ -1,9 +1,10 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using Files.Shared.Extensions; +using System; using System.IO; -using Windows.Win32; -using static Files.App.Helpers.NativeFileOperationsHelper; +using static Files.App.Helpers.Win32Helper; namespace Files.App.Utils.Serialization.Implementation { @@ -13,7 +14,7 @@ internal sealed class DefaultSettingsSerializer : ISettingsSerializer public bool CreateFile(string path) { - PInvoke.CreateDirectoryFromApp(Path.GetDirectoryName(path), null); + CreateDirectoryFromApp(Path.GetDirectoryName(path), IntPtr.Zero); var hFile = CreateFileFromApp(path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero); if (hFile.IsHandleInvalid()) diff --git a/src/Files.App/Utils/Shell/ItemStreamHelper.cs b/src/Files.App/Utils/Shell/ItemStreamHelper.cs index 054cfc024974..523845e21d8c 100644 --- a/src/Files.App/Utils/Shell/ItemStreamHelper.cs +++ b/src/Files.App/Utils/Shell/ItemStreamHelper.cs @@ -6,19 +6,13 @@ namespace Files.App.Utils.Shell { public static class ItemStreamHelper { - [DllImport("shlwapi.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] - static extern HRESULT SHCreateStreamOnFileEx(string pszFile, STGM grfMode, uint dwAttributes, uint fCreate, IntPtr pstmTemplate, out IntPtr ppstm); - - [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] - static extern HRESULT SHCreateItemFromParsingName(string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv); - static readonly Guid IShellItemIid = Guid.ParseExact("43826d1e-e718-42ee-bc55-a1e261c37bfe", "d"); public static IntPtr IShellItemFromPath(string path) { IntPtr psi; Guid iid = IShellItemIid; - var hr = SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out psi); + var hr = Win32PInvoke.SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out psi); if ((int)hr < 0) return IntPtr.Zero; return psi; @@ -27,7 +21,7 @@ public static IntPtr IShellItemFromPath(string path) public static IntPtr IStreamFromPath(string path) { IntPtr pstm; - var hr = SHCreateStreamOnFileEx(path, + var hr = Win32PInvoke.SHCreateStreamOnFileEx(path, STGM.STGM_READ | STGM.STGM_FAILIFTHERE | STGM.STGM_SHARE_DENY_NONE, 0, 0, IntPtr.Zero, out pstm); if ((int)hr < 0) diff --git a/src/Files.App/Utils/Shell/PreviewHandler.cs b/src/Files.App/Utils/Shell/PreviewHandler.cs index 710f89c6a1cc..6cb5f4001798 100644 --- a/src/Files.App/Utils/Shell/PreviewHandler.cs +++ b/src/Files.App/Utils/Shell/PreviewHandler.cs @@ -147,15 +147,6 @@ public PreviewHandler(Guid clsid, nint frame) } } - [Flags] - enum ClassContext : uint - { - LocalServer = 0x4 - } - - [DllImport("ole32.dll", CallingConvention = CallingConvention.StdCall)] - static extern HRESULT CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, ClassContext dwClsContext, ref Guid riid, out IntPtr ppv); - static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d"); void SetupHandler(Guid clsid) @@ -169,7 +160,7 @@ void SetupHandler(Guid clsid) // If we use Activator.CreateInstance(Type.GetTypeFromCLSID(...)), // CLR will allow in-process server, which defeats isolation and // creates strange bugs. - HRESULT hr = CoCreateInstance(ref clsid, IntPtr.Zero, ClassContext.LocalServer, ref iid, out pph); + HRESULT hr = Win32PInvoke.CoCreateInstance(ref clsid, IntPtr.Zero, Win32PInvoke.ClassContext.LocalServer, ref iid, out pph); // See https://blogs.msdn.microsoft.com/adioltean/2005/06/24/when-cocreateinstance-returns-0x80080005-co_e_server_exec_failure/ // CO_E_SERVER_EXEC_FAILURE also tends to happen when debugging in Visual Studio. // Moreover, to create the instance in a server at low integrity level, we need diff --git a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs index 442efba22bba..4764416dfbc6 100644 --- a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs +++ b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs @@ -6,7 +6,7 @@ using System.IO; using Vanara.PInvoke; using Windows.Storage; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; using FileAttributes = System.IO.FileAttributes; namespace Files.App.Utils.Storage @@ -14,15 +14,15 @@ namespace Files.App.Utils.Storage public static class Win32StorageEnumerator { private static readonly ISizeProvider folderSizeProvider = Ioc.Default.GetService(); - private static readonly IStorageCacheService fileListCache = Ioc.Default.GetRequiredService(); private static readonly string folderTypeTextLocalized = "Folder".GetLocalizedResource(); + private static readonly StorageCacheController fileListCache = StorageCacheController.GetInstance(); public static async Task> ListEntries( string path, IntPtr hFile, - NativeFindStorageItemHelper.WIN32_FIND_DATA findData, + Win32Helper.WIN32_FIND_DATA findData, CancellationToken cancellationToken, int countLimit, Func, Task> intermediateAction @@ -108,7 +108,7 @@ Func, Task> intermediateAction private static IEnumerable EnumAdsForPath(string itemPath, ListedItem main) { - foreach (var ads in NativeFileOperationsHelper.GetAlternateStreams(itemPath)) + foreach (var ads in Win32Helper.GetAlternateStreams(itemPath)) yield return GetAlternateStream(ads, main); } @@ -145,7 +145,7 @@ public static ListedItem GetAlternateStream((string Name, long Size) ads, Listed } public static async Task GetFolder( - NativeFindStorageItemHelper.WIN32_FIND_DATA findData, + Win32Helper.WIN32_FIND_DATA findData, string pathRoot, bool isGitRepo, CancellationToken cancellationToken @@ -159,10 +159,10 @@ CancellationToken cancellationToken try { - FileTimeToSystemTime(ref findData.ftLastWriteTime, out NativeFindStorageItemHelper.SYSTEMTIME systemModifiedTimeOutput); + FileTimeToSystemTime(ref findData.ftLastWriteTime, out Win32Helper.SYSTEMTIME systemModifiedTimeOutput); itemModifiedDate = systemModifiedTimeOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftCreationTime, out NativeFindStorageItemHelper.SYSTEMTIME systemCreatedTimeOutput); + FileTimeToSystemTime(ref findData.ftCreationTime, out Win32Helper.SYSTEMTIME systemCreatedTimeOutput); itemCreatedDate = systemCreatedTimeOutput.ToDateTime(); } catch (ArgumentException) @@ -173,7 +173,7 @@ CancellationToken cancellationToken var itemPath = Path.Combine(pathRoot, findData.cFileName); - string itemName = await fileListCache.GetDisplayName(itemPath, cancellationToken); + string itemName = await fileListCache.ReadFileDisplayNameFromCache(itemPath, cancellationToken); if (string.IsNullOrEmpty(itemName)) itemName = findData.cFileName; @@ -222,7 +222,7 @@ CancellationToken cancellationToken } public static async Task GetFile( - NativeFindStorageItemHelper.WIN32_FIND_DATA findData, + Win32Helper.WIN32_FIND_DATA findData, string pathRoot, bool isGitRepo, CancellationToken cancellationToken @@ -235,13 +235,13 @@ CancellationToken cancellationToken try { - FileTimeToSystemTime(ref findData.ftLastWriteTime, out NativeFindStorageItemHelper.SYSTEMTIME systemModifiedDateOutput); + FileTimeToSystemTime(ref findData.ftLastWriteTime, out Win32Helper.SYSTEMTIME systemModifiedDateOutput); itemModifiedDate = systemModifiedDateOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftCreationTime, out NativeFindStorageItemHelper.SYSTEMTIME systemCreatedDateOutput); + FileTimeToSystemTime(ref findData.ftCreationTime, out Win32Helper.SYSTEMTIME systemCreatedDateOutput); itemCreatedDate = systemCreatedDateOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftLastAccessTime, out NativeFindStorageItemHelper.SYSTEMTIME systemLastAccessOutput); + FileTimeToSystemTime(ref findData.ftLastAccessTime, out Win32Helper.SYSTEMTIME systemLastAccessOutput); itemLastAccessDate = systemLastAccessOutput.ToDateTime(); } catch (ArgumentException) @@ -272,11 +272,11 @@ CancellationToken cancellationToken // https://learn.microsoft.com/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 bool isReparsePoint = ((FileAttributes)findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; - bool isSymlink = isReparsePoint && findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK; + bool isSymlink = isReparsePoint && findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK; if (isSymlink) { - var targetPath = NativeFileOperationsHelper.ParseSymLink(itemPath); + var targetPath = Win32Helper.ParseSymLink(itemPath); return new ShortcutItem(null) { diff --git a/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs index 2b98c3a28206..5c8d5d544f1c 100644 --- a/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. See the LICENSE. using System.IO; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; namespace Files.App.Utils.Storage { diff --git a/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs b/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs index 36e7d954f526..a29d2c2a24a6 100644 --- a/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs @@ -32,7 +32,7 @@ public static async Task ToStorageItem(string path) wher } // Fast get attributes - bool exists = NativeFileOperationsHelper.GetFileAttributesExFromApp(path, NativeFileOperationsHelper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out NativeFileOperationsHelper.WIN32_FILE_ATTRIBUTE_DATA itemAttributes); + bool exists = Win32Helper.GetFileAttributesExFromApp(path, Win32Helper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out Win32Helper.WIN32_FILE_ATTRIBUTE_DATA itemAttributes); if (exists) // Exists on local storage { // Directory @@ -160,7 +160,7 @@ public static async Task GetTypeFromPath(string path) public static bool Exists(string path) { - return NativeFileOperationsHelper.GetFileAttributesExFromApp(path, NativeFileOperationsHelper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out _); + return Win32Helper.GetFileAttributesExFromApp(path, Win32Helper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out _); } public static IStorageItemWithPath FromStorageItem(this IStorageItem item, string customPath = null, FilesystemItemType? itemType = null) diff --git a/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs index b5d6df77a544..5a0e058550f2 100644 --- a/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs +++ b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs @@ -25,7 +25,7 @@ public async Task ComputeSizeAsync(CancellationToken cancellationToken = default await Parallel.ForEachAsync(_paths, cancellationToken, async (path, token) => await Task.Factory.StartNew(() => { var queue = new Queue(); - if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory)) + if (!Win32Helper.HasFileAttribute(path, FileAttributes.Directory)) { ComputeFileSize(path); } @@ -101,7 +101,7 @@ private long ComputeFileSize(string path) public void ForceComputeFileSize(string path) { - if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory)) + if (!Win32Helper.HasFileAttribute(path, FileAttributes.Directory)) { ComputeFileSize(path); } diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs index 439b4bd84823..b0a077f20c5c 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs @@ -23,7 +23,7 @@ public sealed class FilesystemHelpers : IFilesystemHelpers private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); private IShellPage associatedInstance; - private readonly IWindowsJumpListService jumpListService; + private readonly IJumpListService jumpListService; private ShellFilesystemOperations filesystemOperations; private ItemManipulationModel? itemManipulationModel => associatedInstance.SlimContentPage?.ItemManipulationModel; @@ -56,7 +56,7 @@ public FilesystemHelpers(IShellPage associatedInstance, CancellationToken cancel { this.associatedInstance = associatedInstance; this.cancellationToken = cancellationToken; - jumpListService = Ioc.Default.GetRequiredService(); + jumpListService = Ioc.Default.GetRequiredService(); filesystemOperations = new ShellFilesystemOperations(this.associatedInstance); } public async Task<(ReturnResult, IStorageItem?)> CreateAsync(IStorageItemWithPath source, bool registerHistory) @@ -161,9 +161,11 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly && if (!permanently && registerHistory) App.HistoryWrapper.AddHistory(history); - - // Execute removal tasks concurrently in background - _ = Task.WhenAll(source.Select(x => jumpListService.RemoveFolderAsync(x.Path))); + + var itemsDeleted = history?.Source.Count ?? 0; + + // Remove items from jump list + source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); var itemsCount = banner.TotalItemsCount; @@ -474,8 +476,8 @@ public async Task MoveItemsAsync(IEnumerable App.HistoryWrapper.AddHistory(history); } - // Execute removal tasks concurrently in background - _ = Task.WhenAll(source.Select(x => jumpListService.RemoveFolderAsync(x.Path))); + // Remove items from jump list + source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); var itemsCount = banner.TotalItemsCount; @@ -821,7 +823,7 @@ public static async Task> GetDraggedStorageIte foreach (var path in itemPaths) { - var isDirectory = NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory); + var isDirectory = Win32Helper.HasFileAttribute(path, FileAttributes.Directory); itemsList.Add(StorageHelpers.FromPathAndType(path, isDirectory ? FilesystemItemType.Directory : FilesystemItemType.File)); } } diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs index 93226587cabf..4729b7810309 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs @@ -186,10 +186,10 @@ await DialogDisplayHelper.ShowDialogAsync( if (fsCopyResult) { - if (NativeFileOperationsHelper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) + if (Win32Helper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) { // The source folder was hidden, apply hidden attribute to destination - NativeFileOperationsHelper.SetFileAttribute(fsCopyResult.Result.Path, SystemIO.FileAttributes.Hidden); + Win32Helper.SetFileAttribute(fsCopyResult.Result.Path, SystemIO.FileAttributes.Hidden); } copiedItem = (BaseStorageFolder)fsCopyResult; @@ -403,10 +403,10 @@ await DialogDisplayHelper.ShowDialogAsync( if (fsResultMove) { - if (NativeFileOperationsHelper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) + if (Win32Helper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) { // The source folder was hidden, apply hidden attribute to destination - NativeFileOperationsHelper.SetFileAttribute(fsResultMove.Result.Path, SystemIO.FileAttributes.Hidden); + Win32Helper.SetFileAttribute(fsResultMove.Result.Path, SystemIO.FileAttributes.Hidden); } movedItem = (BaseStorageFolder)fsResultMove; diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs index 229230dfd4be..ad9713f3d661 100644 --- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs +++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs @@ -6,9 +6,9 @@ using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Search; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; using FileAttributes = System.IO.FileAttributes; -using WIN32_FIND_DATA = Files.App.Helpers.NativeFindStorageItemHelper.WIN32_FIND_DATA; +using WIN32_FIND_DATA = Files.App.Helpers.Win32Helper.WIN32_FIND_DATA; namespace Files.App.Utils.Storage { diff --git a/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs index 5ccf562cda07..fcd27323bb5b 100644 --- a/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs @@ -28,7 +28,7 @@ public sealed class NativeStorageFile : BaseStorageFile public override string FolderRelativeId => $"0\\{Name}"; public bool IsShortcut => FileExtensionHelpers.IsShortcutOrUrlFile(FileType); - public bool IsAlternateStream => RegexHelpers.AlternateStream().IsMatch(Path); + public bool IsAlternateStream => System.Text.RegularExpressions.Regex.IsMatch(Path, @"\w:\w"); public override string DisplayType { @@ -96,7 +96,7 @@ public override IAsyncOperation CopyAsync(IStorageFolder destin private void CreateFile() { - using var hFile = NativeFileOperationsHelper.CreateFileForWrite(Path, false); + using var hFile = Win32Helper.CreateFileForWrite(Path, false); if (hFile.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); @@ -154,14 +154,14 @@ public static IAsyncOperation FromPathAsync(string path) private static bool CheckAccess(string path) { - using var hFile = NativeFileOperationsHelper.OpenFileForRead(path); + using var hFile = Win32Helper.OpenFileForRead(path); return !hFile.IsInvalid; } private static bool IsNativePath(string path) { var isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); - var isAlternateStream = RegexHelpers.AlternateStream().IsMatch(path); + var isAlternateStream = System.Text.RegularExpressions.Regex.IsMatch(path, @"\w:\w"); return isShortcut || isAlternateStream; } @@ -203,7 +203,7 @@ public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string public override IAsyncOperation OpenAsync(FileAccessMode accessMode) { - var hFile = NativeFileOperationsHelper.OpenFileForRead(Path, accessMode == FileAccessMode.ReadWrite); + var hFile = Win32Helper.OpenFileForRead(Path, accessMode == FileAccessMode.ReadWrite); return Task.FromResult(new FileStream(hFile, accessMode == FileAccessMode.ReadWrite ? FileAccess.ReadWrite : FileAccess.Read).AsRandomAccessStream()).AsAsyncOperation(); } @@ -219,7 +219,7 @@ public override IAsyncOperation OpenReadAsyn public override IAsyncOperation OpenSequentialReadAsync() { - var hFile = NativeFileOperationsHelper.OpenFileForRead(Path); + var hFile = Win32Helper.OpenFileForRead(Path); return Task.FromResult(new FileStream(hFile, FileAccess.Read).AsInputStream()).AsAsyncOperation(); } diff --git a/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs index 9a812aa205ab..2d8c9d1ef150 100644 --- a/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs @@ -89,7 +89,7 @@ public override IAsyncOperation CopyAsync(IStorageFolder destin if (!string.IsNullOrEmpty(destFolder.Path)) { var destination = IO.Path.Combine(destFolder.Path, desiredNewName); - var hFile = NativeFileOperationsHelper.CreateFileForWrite(destination, + var hFile = Win32Helper.CreateFileForWrite(destination, option == NameCollisionOption.ReplaceExisting); if (!hFile.IsInvalid) { diff --git a/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs b/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs index 237d9703fc34..ab0d6de29e92 100644 --- a/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs +++ b/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs @@ -6,7 +6,7 @@ using Windows.Foundation; using Windows.Storage; using Windows.Storage.FileProperties; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; namespace Files.App.Utils.Storage { @@ -49,7 +49,7 @@ public static VirtualStorageItem FromPath(string path) { // https://learn.microsoft.com/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 bool isReparsePoint = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.ReparsePoint) == System.IO.FileAttributes.ReparsePoint; - bool isSymlink = isReparsePoint && findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK; + bool isSymlink = isReparsePoint && findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK; bool isHidden = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.Hidden) == System.IO.FileAttributes.Hidden; bool isDirectory = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.Directory) == System.IO.FileAttributes.Directory; diff --git a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs index 924487dd8c33..64f9d532ac90 100644 --- a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs @@ -105,7 +105,7 @@ public override IAsyncOperation OpenAsync(FileAccessMode ac return await backingFile.OpenAsync(accessMode); } - var file = NativeFileOperationsHelper.OpenFileForRead(containerPath, rw); + var file = Win32Helper.OpenFileForRead(containerPath, rw); return file.IsInvalid ? null : new FileStream(file, rw ? FileAccess.ReadWrite : FileAccess.Read).AsRandomAccessStream(); } @@ -150,7 +150,7 @@ public override IAsyncOperation OpenReadAsyn return await backingFile.OpenReadAsync(); } - var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath); + var hFile = Win32Helper.OpenFileForRead(containerPath); return hFile.IsInvalid ? null : new StreamWithContentType(new FileStream(hFile, FileAccess.Read).AsRandomAccessStream()); } @@ -189,7 +189,7 @@ public override IAsyncOperation OpenSequentialReadAsync() return await backingFile.OpenSequentialReadAsync(); } - var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath); + var hFile = Win32Helper.OpenFileForRead(containerPath); return hFile.IsInvalid ? null : new FileStream(hFile, FileAccess.Read).AsInputStream(); } @@ -400,7 +400,7 @@ private static bool CheckAccess(string path) { try { - var hFile = NativeFileOperationsHelper.OpenFileForRead(path); + var hFile = Win32Helper.OpenFileForRead(path); if (hFile.IsInvalid) { return false; @@ -475,7 +475,7 @@ private IAsyncOperation OpenZipFileAsync(FileAccessMode accessMode) } else { - var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath, readWrite); + var hFile = Win32Helper.OpenFileForRead(containerPath, readWrite); if (hFile.IsInvalid) { return null; diff --git a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs index 794bbeba7cb0..23897ee7d09d 100644 --- a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs +++ b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs @@ -101,7 +101,7 @@ public static async Task CheckDefaultZipApp(string filePath) { Func> queryFileAssoc = async () => { - var assoc = await NativeWinApiHelper.GetFileAssociationAsync(filePath); + var assoc = await Win32Helper.GetFileAssociationAsync(filePath); if (assoc is not null) { return assoc == Package.Current.Id.FamilyName @@ -489,7 +489,7 @@ private static bool CheckAccess(string path) { return SafetyExtensions.IgnoreExceptions(() => { - var hFile = NativeFileOperationsHelper.OpenFileForRead(path); + var hFile = Win32Helper.OpenFileForRead(path); if (hFile.IsInvalid) { return false; @@ -530,7 +530,7 @@ public static Task InitArchive(string path, OutArchiveFormat format) { return SafetyExtensions.IgnoreExceptions(() => { - var hFile = NativeFileOperationsHelper.OpenFileForRead(path, true); + var hFile = Win32Helper.OpenFileForRead(path, true); if (hFile.IsInvalid) { return Task.FromResult(false); @@ -581,7 +581,7 @@ private IAsyncOperation OpenZipFileAsync(FileAccessMode accessMode) } else { - var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath, readWrite); + var hFile = Win32Helper.OpenFileForRead(containerPath, readWrite); if (hFile.IsInvalid) { return null; diff --git a/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs b/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs index 6edfd9a03123..0909961f3347 100644 --- a/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs @@ -4,7 +4,7 @@ using Microsoft.UI.Dispatching; using System.IO; using Windows.Storage.FileProperties; -using static Files.App.Helpers.NativeFindStorageItemHelper; +using static Files.App.Helpers.Win32Helper; using FileAttributes = System.IO.FileAttributes; namespace Files.App.ViewModels.Properties @@ -75,7 +75,7 @@ public async Task GetOtherPropertiesAsync(IStorageItemExtraProperties properties if (((FileAttributes)findData.dwFileAttributes & FileAttributes.Directory) != FileAttributes.Directory) { size += findData.GetSize(); - var fileSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Path.Combine(path, findData.cFileName)); + var fileSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Path.Combine(path, findData.cFileName)); sizeOnDisk += fileSizeOnDisk ?? 0; ++count; ViewModel.FilesCount++; diff --git a/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs b/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs index ef9849f1b915..7dc2590be7a5 100644 --- a/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs @@ -60,7 +60,7 @@ public override async Task GetSpecialPropertiesAsync() { if (List.All(x => x.PrimaryItemAttribute == StorageItemTypes.File)) { - var fileAttributesReadOnly = List.Select(x => NativeFileOperationsHelper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.ReadOnly)); + var fileAttributesReadOnly = List.Select(x => Win32Helper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.ReadOnly)); if (fileAttributesReadOnly.All(x => x)) ViewModel.IsReadOnly = true; else if (!fileAttributesReadOnly.Any(x => x)) @@ -69,7 +69,7 @@ public override async Task GetSpecialPropertiesAsync() ViewModel.IsReadOnly = null; } - var fileAttributesHidden = List.Select(x => NativeFileOperationsHelper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.Hidden)); + var fileAttributesHidden = List.Select(x => Win32Helper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.Hidden)); if (fileAttributesHidden.All(x => x)) ViewModel.IsHidden = true; else if (!fileAttributesHidden.Any(x => x)) @@ -89,7 +89,7 @@ public override async Task GetSpecialPropertiesAsync() long totalSizeOnDisk = 0; long filesSizeOnDisk = List.Where(x => x.PrimaryItemAttribute == StorageItemTypes.File && x.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - .Sum(x => NativeFileOperationsHelper.GetFileSizeOnDisk(x.ItemPath) ?? 0); + .Sum(x => Win32Helper.GetFileSizeOnDisk(x.ItemPath) ?? 0); long foldersSizeOnDisk = 0; ViewModel.ItemSizeProgressVisibility = true; @@ -140,12 +140,12 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop { if ((bool)ViewModel.IsReadOnly) { - List.ForEach(x => NativeFileOperationsHelper.SetFileAttribute( + List.ForEach(x => Win32Helper.SetFileAttribute( x.ItemPath, System.IO.FileAttributes.ReadOnly)); } else { - List.ForEach(x => NativeFileOperationsHelper.UnsetFileAttribute( + List.ForEach(x => Win32Helper.UnsetFileAttribute( x.ItemPath, System.IO.FileAttributes.ReadOnly)); } } @@ -158,12 +158,12 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop { if ((bool)ViewModel.IsHidden) { - List.ForEach(x => NativeFileOperationsHelper.SetFileAttribute( + List.ForEach(x => Win32Helper.SetFileAttribute( x.ItemPath, System.IO.FileAttributes.Hidden)); } else { - List.ForEach(x => NativeFileOperationsHelper.UnsetFileAttribute( + List.ForEach(x => Win32Helper.UnsetFileAttribute( x.ItemPath, System.IO.FileAttributes.Hidden)); } } diff --git a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs index defe14c8c572..faf8cd7de753 100644 --- a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs @@ -44,7 +44,7 @@ public override void GetBaseProperties() ViewModel.LoadCustomIcon = Item.LoadCustomIcon; ViewModel.CustomIconSource = Item.CustomIconSource; ViewModel.LoadFileIcon = Item.LoadFileIcon; - ViewModel.IsDownloadedFile = NativeFileOperationsHelper.ReadStringFromFile($"{Item.ItemPath}:Zone.Identifier") is not null; + ViewModel.IsDownloadedFile = Win32Helper.ReadStringFromFile($"{Item.ItemPath}:Zone.Identifier") is not null; ViewModel.IsEditAlbumCoverVisible = FileExtensionHelpers.IsVideoFile(Item.FileExtension) || FileExtensionHelpers.IsAudioFile(Item.FileExtension); @@ -93,9 +93,9 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( public override async Task GetSpecialPropertiesAsync() { - ViewModel.IsReadOnly = NativeFileOperationsHelper.HasFileAttribute( + ViewModel.IsReadOnly = Win32Helper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.ReadOnly); - ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute( + ViewModel.IsHidden = Win32Helper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.Hidden); ViewModel.ItemSizeVisibility = true; @@ -103,7 +103,7 @@ public override async Task GetSpecialPropertiesAsync() // Only load the size for items on the device if (Item.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - ViewModel.ItemSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? + ViewModel.ItemSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? string.Empty; var result = await FileThumbnailHelper.GetIconAsync( @@ -282,9 +282,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsReadOnly is not null) { if ((bool)ViewModel.IsReadOnly) - NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); + Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); else - NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); + Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); } break; @@ -293,9 +293,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); else - NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); } break; diff --git a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs index 52f616258502..6de0ad95b165 100644 --- a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs @@ -71,7 +71,7 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( public async override Task GetSpecialPropertiesAsync() { - ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute( + ViewModel.IsHidden = Win32Helper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.Hidden); var result = await FileThumbnailHelper.GetIconAsync( @@ -94,7 +94,7 @@ public async override Task GetSpecialPropertiesAsync() // Only load the size for items on the device if (Item.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - ViewModel.ItemSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? + ViewModel.ItemSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? string.Empty; ViewModel.ItemCreatedTimestampReal = Item.ItemDateCreatedReal; @@ -203,9 +203,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); else - NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); } break; diff --git a/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs b/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs index 7871789bb773..0ae2cb2e26de 100644 --- a/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs @@ -46,8 +46,8 @@ public override void GetBaseProperties() public async override Task GetSpecialPropertiesAsync() { - ViewModel.IsReadOnly = NativeFileOperationsHelper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); - ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + ViewModel.IsReadOnly = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + ViewModel.IsHidden = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); var result = await FileThumbnailHelper.GetIconAsync( Library.ItemPath, @@ -144,9 +144,9 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop if (ViewModel.IsReadOnly is not null) { if ((bool)ViewModel.IsReadOnly) - NativeFileOperationsHelper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + Win32Helper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); else - NativeFileOperationsHelper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + Win32Helper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); } break; @@ -155,9 +155,9 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - NativeFileOperationsHelper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); else - NativeFileOperationsHelper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + Win32Helper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); } break; From 2d057b7681ed3f281d5cb30a14fd63554778bd7d Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:44:29 +0900 Subject: [PATCH 07/14] Revert "Init" This reverts commit 432c1790a92931eb3e885c13325b2572f3325a8b. --- src/Files.App/Data/Models/ItemViewModel.cs | 23 +- src/Files.App/Data/Models/RemovableDevice.cs | 8 +- .../Extensions/Win32FindDataExtensions.cs | 2 +- .../Helpers/Environment/ElevationHelpers.cs | 5 +- .../Interop/NativeFileOperationsHelper.cs | 519 ++++++++++++++++++ .../Helpers/Interop/NativeWinApiHelper.cs | 259 +++++++++ .../Helpers/Layout/AdaptiveLayoutHelpers.cs | 2 +- .../Layout/LayoutPreferencesManager.cs | 6 +- .../Helpers/NativeFindStorageItemHelper.cs | 131 +++++ .../Helpers/NaturalStringComparer.cs | 45 +- .../Helpers/Navigation/NavigationHelpers.cs | 24 +- .../Helpers/Win32/Win32Helper.Process.cs | 49 -- .../Helpers/Win32/Win32Helper.Storage.cs | 290 +--------- .../Helpers/Win32/Win32PInvoke.Consts.cs | 36 -- .../Helpers/Win32/Win32PInvoke.Enums.cs | 167 ------ .../Helpers/Win32/Win32PInvoke.Methods.cs | 198 +------ .../Helpers/Win32/Win32PInvoke.Structs.cs | 157 ------ .../Services/SideloadUpdateService.cs | 5 +- .../SizeProvider/CachedSizeProvider.cs | 2 +- .../Utils/FileTags/FileTagsHelper.cs | 16 +- .../DefaultSettingsSerializer.cs | 7 +- src/Files.App/Utils/Shell/ItemStreamHelper.cs | 10 +- src/Files.App/Utils/Shell/PreviewHandler.cs | 11 +- .../Enumerators/Win32StorageEnumerator.cs | 28 +- .../Utils/Storage/Helpers/FolderHelpers.cs | 2 +- .../Utils/Storage/Helpers/StorageHelpers.cs | 4 +- .../Storage/Operations/FileSizeCalculator.cs | 4 +- .../Storage/Operations/FilesystemHelpers.cs | 18 +- .../Operations/FilesystemOperations.cs | 8 +- .../Utils/Storage/Search/FolderSearch.cs | 4 +- .../Storage/StorageItems/NativeStorageFile.cs | 12 +- .../Storage/StorageItems/SystemStorageFile.cs | 2 +- .../StorageItems/VirtualStorageItem.cs | 4 +- .../Storage/StorageItems/ZipStorageFile.cs | 10 +- .../Storage/StorageItems/ZipStorageFolder.cs | 8 +- .../Properties/Items/BaseProperties.cs | 4 +- .../Properties/Items/CombinedProperties.cs | 14 +- .../Properties/Items/FileProperties.cs | 16 +- .../Properties/Items/FolderProperties.cs | 8 +- .../Properties/Items/LibraryProperties.cs | 12 +- 40 files changed, 1117 insertions(+), 1013 deletions(-) create mode 100644 src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs create mode 100644 src/Files.App/Helpers/Interop/NativeWinApiHelper.cs create mode 100644 src/Files.App/Helpers/NativeFindStorageItemHelper.cs diff --git a/src/Files.App/Data/Models/ItemViewModel.cs b/src/Files.App/Data/Models/ItemViewModel.cs index ebb69ff08a11..9e8464e922c4 100644 --- a/src/Files.App/Data/Models/ItemViewModel.cs +++ b/src/Files.App/Data/Models/ItemViewModel.cs @@ -20,7 +20,7 @@ using Windows.Storage.FileProperties; using Windows.Storage.Search; using static Files.App.Helpers.Win32PInvoke; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue; using FileAttributes = System.IO.FileAttributes; @@ -39,7 +39,6 @@ public sealed class ItemViewModel : ObservableObject, IDisposable private readonly AsyncManualResetEvent gitChangedEvent; private readonly DispatcherQueue dispatcherQueue; private readonly JsonElement defaultJson = JsonSerializer.SerializeToElement("{}"); - private readonly StorageCacheController fileListCache = StorageCacheController.GetInstance(); private readonly string folderTypeTextLocalized = "Folder".GetLocalizedResource(); private Task? aProcessQueueAction; @@ -50,8 +49,10 @@ public sealed class ItemViewModel : ObservableObject, IDisposable private readonly IWindowsJumpListService jumpListService = Ioc.Default.GetRequiredService(); private readonly IDialogService dialogService = Ioc.Default.GetRequiredService(); private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + private readonly INetworkDrivesService NetworkDrivesService = Ioc.Default.GetRequiredService(); private readonly IFileTagsSettingsService fileTagsSettingsService = Ioc.Default.GetRequiredService(); private readonly ISizeProvider folderSizeProvider = Ioc.Default.GetRequiredService(); + private readonly IStorageCacheService fileListCache = Ioc.Default.GetRequiredService(); // Only used for Binding and ApplyFilesAndFoldersChangesAsync, don't manipulate on this! public BulkConcurrentObservableCollection FilesAndFolders { get; } @@ -679,7 +680,7 @@ void ClearDisplay() DirectoryInfoUpdated?.Invoke(this, EventArgs.Empty); } - if (Win32Helper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) + if (NativeWinApiHelper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) ClearDisplay(); else await dispatcherQueue.EnqueueOrInvokeAsync(ClearDisplay); @@ -767,7 +768,7 @@ void OrderEntries() folderSettings.SortDirectoriesAlongsideFiles, folderSettings.SortFilesFirst)); } - if (Win32Helper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) + if (NativeWinApiHelper.IsHasThreadAccessPropertyPresent && dispatcherQueue.HasThreadAccess) return Task.Run(OrderEntries); OrderEntries(); @@ -1122,7 +1123,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => { item.ItemNameRaw = matchingStorageFolder.DisplayName; }); - await fileListCache.SaveFileDisplayNameToCache(item.ItemPath, matchingStorageFolder.DisplayName); + await fileListCache.AddDisplayName(item.ItemPath, matchingStorageFolder.DisplayName); if (folderSettings.DirectorySortOption == SortOption.Name && !isLoadingItems) { await OrderFilesAndFoldersAsync(); @@ -1506,7 +1507,7 @@ private async Task EnumerateItemsFromStandardFolderAsync(string path, Cance if (isNetwork) { - var auth = await NetworkDrivesAPI.AuthenticateNetworkShare(path); + var auth = await NetworkDrivesService.AuthenticateNetworkShare(path); if (!auth) return -1; } @@ -1864,8 +1865,8 @@ await dispatcherQueue.EnqueueOrInvokeAsync(() => private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStatus) { Debug.WriteLine($"WatchForDirectoryChanges: {path}"); - var hWatchDir = Win32Helper.CreateFileFromApp(path, 1, 1 | 2 | 4, - IntPtr.Zero, 3, (uint)Win32Helper.File_Attributes.BackupSemantics | (uint)Win32Helper.File_Attributes.Overlapped, IntPtr.Zero); + var hWatchDir = NativeFileOperationsHelper.CreateFileFromApp(path, 1, 1 | 2 | 4, + IntPtr.Zero, 3, (uint)NativeFileOperationsHelper.File_Attributes.BackupSemantics | (uint)NativeFileOperationsHelper.File_Attributes.Overlapped, IntPtr.Zero); if (hWatchDir.ToInt64() == -1) return; @@ -1973,13 +1974,13 @@ private void WatchForDirectoryChanges(string path, CloudDriveSyncStatus syncStat private void WatchForGitChanges() { - var hWatchDir = Win32Helper.CreateFileFromApp( + var hWatchDir = NativeFileOperationsHelper.CreateFileFromApp( GitDirectory!, 1, 1 | 2 | 4, IntPtr.Zero, 3, - (uint)Win32Helper.File_Attributes.BackupSemantics | (uint)Win32Helper.File_Attributes.Overlapped, + (uint)NativeFileOperationsHelper.File_Attributes.BackupSemantics | (uint)NativeFileOperationsHelper.File_Attributes.Overlapped, IntPtr.Zero); if (hWatchDir.ToInt64() == -1) @@ -2225,7 +2226,7 @@ private async Task AddFileOrFolderAsync(ListedItem? item) if (UserSettingsService.FoldersSettingsService.AreAlternateStreamsVisible) { // New file added, enumerate ADS - foreach (var ads in Win32Helper.GetAlternateStreams(item.ItemPath)) + foreach (var ads in NativeFileOperationsHelper.GetAlternateStreams(item.ItemPath)) { var adsItem = Win32StorageEnumerator.GetAlternateStream(ads, item); filesAndFolders.Add(adsItem); diff --git a/src/Files.App/Data/Models/RemovableDevice.cs b/src/Files.App/Data/Models/RemovableDevice.cs index 0923b17615b5..cd24e822e5ab 100644 --- a/src/Files.App/Data/Models/RemovableDevice.cs +++ b/src/Files.App/Data/Models/RemovableDevice.cs @@ -52,7 +52,7 @@ private async Task LockVolumeAsync() for (int i = 0; i < 5; i++) { - if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nint.Zero, 0, out _, 0, out _, nint.Zero)) + if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero)) { Debug.WriteLine("Lock successful!"); result = true; @@ -72,18 +72,18 @@ private async Task LockVolumeAsync() private bool DismountVolume() { - return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, out _, 0, out _, nint.Zero); + return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero); } private bool PreventRemovalOfVolume(bool prevent) { byte[] buf = [prevent ? (byte)1 : (byte)0]; - return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, out _, 0, out _, nint.Zero); + return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, nint.Zero, 0, out _, nint.Zero); } private bool AutoEjectVolume() { - return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nint.Zero, 0, out _, 0, out _, nint.Zero); + return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nint.Zero, 0, nint.Zero, 0, out _, nint.Zero); } private bool CloseVolume() diff --git a/src/Files.App/Extensions/Win32FindDataExtensions.cs b/src/Files.App/Extensions/Win32FindDataExtensions.cs index ccbb038692b9..7a81be8460c8 100644 --- a/src/Files.App/Extensions/Win32FindDataExtensions.cs +++ b/src/Files.App/Extensions/Win32FindDataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; namespace Files.App.Extensions { diff --git a/src/Files.App/Helpers/Environment/ElevationHelpers.cs b/src/Files.App/Helpers/Environment/ElevationHelpers.cs index 6902d8d4427a..ff1bf0ee1a65 100644 --- a/src/Files.App/Helpers/Environment/ElevationHelpers.cs +++ b/src/Files.App/Helpers/Environment/ElevationHelpers.cs @@ -8,12 +8,15 @@ namespace Files.App.Helpers { public static class ElevationHelpers { + [DllImport("shell32.dll", EntryPoint = "#865", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] private static extern bool _IsElevationRequired([MarshalAs(UnmanagedType.LPWStr)] string pszPath); + public static bool IsElevationRequired(string filePath) { if (string.IsNullOrEmpty(filePath)) return false; - return Win32PInvoke._IsElevationRequired(filePath); + return _IsElevationRequired(filePath); } public static bool IsAppRunAsAdmin() diff --git a/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs b/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs new file mode 100644 index 000000000000..ea093e24f98c --- /dev/null +++ b/src/Files.App/Helpers/Interop/NativeFileOperationsHelper.cs @@ -0,0 +1,519 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading; +using Vanara.PInvoke; +using static Files.App.Helpers.NativeFindStorageItemHelper; + +namespace Files.App.Helpers +{ + public sealed class NativeFileOperationsHelper + { + public enum File_Attributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Directory = 0x00000010, + Archive = 0x00000020, + Device = 0x00000040, + Normal = 0x00000080, + Temporary = 0x00000100, + SparseFile = 0x00000200, + ReparsePoint = 0x00000400, + Compressed = 0x00000800, + Offline = 0x00001000, + NotContentIndexed = 0x00002000, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + FirstPipeInstance = 0x00080000 + } + + public const uint GENERIC_READ = 0x80000000; + public const uint GENERIC_WRITE = 0x40000000; + public const uint FILE_APPEND_DATA = 0x0004; + public const uint FILE_WRITE_ATTRIBUTES = 0x100; + + public const uint FILE_SHARE_READ = 0x00000001; + public const uint FILE_SHARE_WRITE = 0x00000002; + public const uint FILE_SHARE_DELETE = 0x00000004; + + public const uint FILE_BEGIN = 0; + public const uint FILE_END = 2; + + public const uint CREATE_ALWAYS = 2; + public const uint CREATE_NEW = 1; + public const uint OPEN_ALWAYS = 4; + public const uint OPEN_EXISTING = 3; + public const uint TRUNCATE_EXISTING = 5; + + [DllImport("api-ms-win-core-handle-l1-1-0.dll")] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern IntPtr CreateFileFromApp( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr SecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile + ); + + public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite = true) + { + return new SafeFileHandle(CreateFileFromApp(filePath, + GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? CREATE_ALWAYS : OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); + } + + public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false, uint flags = 0) + { + return new SafeFileHandle(CreateFileFromApp(filePath, + GENERIC_READ | (readWrite ? GENERIC_WRITE : 0), FILE_SHARE_READ | (readWrite ? 0 : FILE_SHARE_WRITE), IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics | flags, IntPtr.Zero), true); + } + + private const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; + private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; + public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; + public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct REPARSE_DATA_BUFFER + { + public uint ReparseTag; + public short ReparseDataLength; + public short Reserved; + public short SubsNameOffset; + public short SubsNameLength; + public short PrintNameOffset; + public short PrintNameLength; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_REPARSE_DATA_BUFFER_SIZE)] + public char[] PathBuffer; + } + + [DllImport("api-ms-win-core-io-l1-1-0.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeviceIoControl( + IntPtr hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + //IntPtr lpOutBuffer, + out REPARSE_DATA_BUFFER outBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetFileAttributesExFromApp( + string lpFileName, + GET_FILEEX_INFO_LEVELS fInfoLevelId, + out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetFileAttributesFromApp( + string lpFileName, + FileAttributes dwFileAttributes); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", ExactSpelling = true, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern uint SetFilePointer( + IntPtr hFile, + long lDistanceToMove, + IntPtr lpDistanceToMoveHigh, + uint dwMoveMethod + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public unsafe static extern bool ReadFile( + IntPtr hFile, + byte* lpBuffer, + int nBufferLength, + int* lpBytesReturned, + IntPtr lpOverlapped + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public unsafe static extern bool WriteFile( + IntPtr hFile, + byte* lpBuffer, + int nBufferLength, + int* lpBytesWritten, + IntPtr lpOverlapped + ); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, + CallingConvention = CallingConvention.StdCall, + SetLastError = true)] + public static extern bool WriteFileEx( + IntPtr hFile, + byte[] lpBuffer, + uint nNumberOfBytesToWrite, + [In] ref NativeOverlapped lpOverlapped, + LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + + public delegate void LPOVERLAPPED_COMPLETION_ROUTINE(uint dwErrorCode, uint dwNumberOfBytesTransfered, ref NativeOverlapped lpOverlapped); + + public enum GET_FILEEX_INFO_LEVELS + { + GetFileExInfoStandard, + } + + [StructLayout(LayoutKind.Sequential)] + public struct WIN32_FILE_ATTRIBUTE_DATA + { + public FileAttributes dwFileAttributes; + public FILETIME ftCreationTime; + public FILETIME ftLastAccessTime; + public FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + } + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool GetFileTime([In] IntPtr hFile, out FILETIME lpCreationTime, out FILETIME lpLastAccessTime, out FILETIME lpLastWriteTime); + + [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + public static extern bool SetFileTime([In] IntPtr hFile, in FILETIME lpCreationTime, in FILETIME lpLastAccessTime, in FILETIME lpLastWriteTime); + + private enum FILE_INFO_BY_HANDLE_CLASS + { + FileBasicInfo = 0, + FileStandardInfo = 1, + FileNameInfo = 2, + FileRenameInfo = 3, + FileDispositionInfo = 4, + FileAllocationInfo = 5, + FileEndOfFileInfo = 6, + FileStreamInfo = 7, + FileCompressionInfo = 8, + FileAttributeTagInfo = 9, + FileIdBothDirectoryInfo = 10,// 0x0A + FileIdBothDirectoryRestartInfo = 11, // 0xB + FileIoPriorityHintInfo = 12, // 0xC + FileRemoteProtocolInfo = 13, // 0xD + FileFullDirectoryInfo = 14, // 0xE + FileFullDirectoryRestartInfo = 15, // 0xF + FileStorageInfo = 16, // 0x10 + FileAlignmentInfo = 17, // 0x11 + FileIdInfo = 18, // 0x12 + FileIdExtdDirectoryInfo = 19, // 0x13 + FileIdExtdDirectoryRestartInfo = 20, // 0x14 + MaximumFileInfoByHandlesClass + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct FILE_ID_BOTH_DIR_INFO + { + public uint NextEntryOffset; + public uint FileIndex; + public long CreationTime; + public long LastAccessTime; + public long LastWriteTime; + public long ChangeTime; + public long EndOfFile; + public long AllocationSize; + public uint FileAttributes; + public uint FileNameLength; + public uint EaSize; + public char ShortNameLength; + [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 12)] + public string ShortName; + public long FileId; + [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)] + public string FileName; + } + + [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, out FILE_ID_BOTH_DIR_INFO dirInfo, uint dwBufferSize); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] + private struct FILE_STREAM_INFO + { + public uint NextEntryOffset; + public uint StreamNameLength; + public long StreamSize; + public long StreamAllocationSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] + public string StreamName; + } + + [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + private static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, IntPtr dirInfo, uint dwBufferSize); + + private enum StreamInfoLevels { FindStreamInfoStandard = 0 } + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); + + [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FindNextStreamW(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private sealed class WIN32_FIND_STREAM_DATA { + public long StreamSize; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] + public string cStreamName; + } + + public static bool GetFileDateModified(string filePath, out FILETIME dateModified) + { + using var hFile = new SafeFileHandle(CreateFileFromApp(filePath, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); + return GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified); + } + + public static bool SetFileDateModified(string filePath, FILETIME dateModified) + { + using var hFile = new SafeFileHandle(CreateFileFromApp(filePath, FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, OPEN_EXISTING, (uint)File_Attributes.BackupSemantics, IntPtr.Zero), true); + return SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified); + } + + public static bool HasFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (GetFileAttributesExFromApp( + lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return (lpFileInfo.dwFileAttributes & dwAttrs) == dwAttrs; + } + return false; + } + + public static bool SetFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (!GetFileAttributesExFromApp( + lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return false; + } + return SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes | dwAttrs); + } + + public static bool UnsetFileAttribute(string lpFileName, FileAttributes dwAttrs) + { + if (!GetFileAttributesExFromApp( + lpFileName, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) + { + return false; + } + return SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes & ~dwAttrs); + } + + public static string ReadStringFromFile(string filePath) + { + IntPtr hFile = CreateFileFromApp(filePath, + GENERIC_READ, + FILE_SHARE_READ, + IntPtr.Zero, + OPEN_EXISTING, + (uint)File_Attributes.BackupSemantics, + IntPtr.Zero); + + if (hFile.ToInt64() == -1) + { + return null; + } + + const int BUFFER_LENGTH = 4096; + byte[] buffer = new byte[BUFFER_LENGTH]; + int dwBytesRead; + string szRead = string.Empty; + + unsafe + { + using (MemoryStream ms = new MemoryStream()) + using (StreamReader reader = new StreamReader(ms, true)) + { + while (true) + { + fixed (byte* pBuffer = buffer) + { + if (ReadFile(hFile, pBuffer, BUFFER_LENGTH - 1, &dwBytesRead, IntPtr.Zero) && dwBytesRead > 0) + { + ms.Write(buffer, 0, dwBytesRead); + } + else + { + break; + } + } + } + ms.Position = 0; + szRead = reader.ReadToEnd(); + } + } + + CloseHandle(hFile); + + return szRead; + } + + public static bool WriteStringToFile(string filePath, string str, File_Attributes flags = 0) + { + IntPtr hStream = CreateFileFromApp(filePath, + GENERIC_WRITE, 0, IntPtr.Zero, CREATE_ALWAYS, (uint)(File_Attributes.BackupSemantics | flags), IntPtr.Zero); + if (hStream.ToInt64() == -1) + { + return false; + } + byte[] buff = Encoding.UTF8.GetBytes(str); + int dwBytesWritten; + unsafe + { + fixed (byte* pBuff = buff) + { + WriteFile(hStream, pBuff, buff.Length, &dwBytesWritten, IntPtr.Zero); + } + } + CloseHandle(hStream); + return true; + } + + public static bool WriteBufferToFileWithProgress(string filePath, byte[] buffer, LPOVERLAPPED_COMPLETION_ROUTINE callback) + { + using var hFile = CreateFileForWrite(filePath); + + if (hFile.IsInvalid) + { + return false; + } + + NativeOverlapped nativeOverlapped = new NativeOverlapped(); + bool result = WriteFileEx(hFile.DangerousGetHandle(), buffer, (uint)buffer.LongLength, ref nativeOverlapped, callback); + + if (!result) + { + System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error()); + } + + return result; + } + + // https://www.pinvoke.net/default.aspx/kernel32/GetFileInformationByHandleEx.html + public static ulong? GetFolderFRN(string folderPath) + { + using var handle = OpenFileForRead(folderPath); + if (!handle.IsInvalid) + { + var fileStruct = new FILE_ID_BOTH_DIR_INFO(); + if (GetFileInformationByHandleEx(handle.DangerousGetHandle(), FILE_INFO_BY_HANDLE_CLASS.FileIdBothDirectoryInfo, out fileStruct, (uint)Marshal.SizeOf(fileStruct))) + { + return (ulong)fileStruct.FileId; + } + } + return null; + } + + public static ulong? GetFileFRN(string filePath) + { + using var handle = OpenFileForRead(filePath); + if (!handle.IsInvalid) + { + try + { + var fileID = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileIdInfo); + return BitConverter.ToUInt64(fileID.FileId.Identifier, 0); + } + catch { } + } + return null; + } + + public static long? GetFileSizeOnDisk(string filePath) + { + using var handle = OpenFileForRead(filePath); + if (!handle.IsInvalid) + { + try + { + var fileAllocationInfo = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo); + return fileAllocationInfo.AllocationSize; + } + catch { } + } + return null; + } + + // https://github.com/rad1oactive/BetterExplorer/blob/master/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Shell/ReparsePoint.cs + public static string ParseSymLink(string path) + { + using var handle = OpenFileForRead(path, false, 0x00200000); + if (!handle.IsInvalid) + { + REPARSE_DATA_BUFFER buffer = new REPARSE_DATA_BUFFER(); + if (DeviceIoControl(handle.DangerousGetHandle(), FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero)) + { + var subsString = new string(buffer.PathBuffer, ((buffer.SubsNameOffset / 2) + 2), buffer.SubsNameLength / 2); + var printString = new string(buffer.PathBuffer, ((buffer.PrintNameOffset / 2) + 2), buffer.PrintNameLength / 2); + var normalisedTarget = printString ?? subsString; + if (string.IsNullOrEmpty(normalisedTarget)) + { + normalisedTarget = subsString; + if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) + { + normalisedTarget = normalisedTarget.Substring(4); + } + } + if (buffer.ReparseTag == IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) + { + // Target is relative, get the absolute path + normalisedTarget = normalisedTarget.TrimStart(Path.DirectorySeparatorChar); + path = path.TrimEnd(Path.DirectorySeparatorChar); + normalisedTarget = Path.GetFullPath(Path.Combine(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar)), normalisedTarget)); + } + return normalisedTarget; + } + } + return null; + } + + // https://stackoverflow.com/a/7988352 + public static IEnumerable<(string Name, long Size)> GetAlternateStreams(string path) + { + WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA(); + IntPtr hFile = FindFirstStreamW(path, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); + + if (hFile.ToInt64() != -1) + { + do + { + // The documentation for FindFirstStreamW says that it is always a ::$DATA + // stream type, but FindNextStreamW doesn't guarantee that for subsequent + // streams so we check to make sure + if (findStreamData.cStreamName.EndsWith(":$DATA") && findStreamData.cStreamName != "::$DATA") + { + yield return (findStreamData.cStreamName, findStreamData.StreamSize); + } + } + while (FindNextStreamW(hFile, findStreamData)); + + FindClose(hFile); + } + } + } +} diff --git a/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs b/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs new file mode 100644 index 000000000000..5a625cb61ce2 --- /dev/null +++ b/src/Files.App/Helpers/Interop/NativeWinApiHelper.cs @@ -0,0 +1,259 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using Files.App.Utils.Shell; +using Microsoft.Extensions.Logging; +using System.Runtime.InteropServices; +using System.Text; +using Windows.Foundation.Metadata; +using Windows.System; + +namespace Files.App.Helpers +{ + public sealed class NativeWinApiHelper + { + [DllImport("Shcore.dll", SetLastError = true)] + public static extern int GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY); + + [DllImport("api-ms-win-core-processthreads-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken([In] IntPtr ProcessHandle, TokenAccess DesiredAccess, out IntPtr TokenHandle); + + [DllImport("api-ms-win-core-processthreads-l1-1-2.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("api-ms-win-security-base-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetTokenInformation(IntPtr hObject, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr pTokenInfo, int tokenInfoLength, out int returnLength); + + [DllImport("api-ms-win-core-handle-l1-1-0.dll")] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("api-ms-win-security-base-l1-1-0.dll", ExactSpelling = true, SetLastError = true)] + public static extern int GetLengthSid(IntPtr pSid); + + [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CryptUnprotectData( + in CRYPTOAPI_BLOB pDataIn, + StringBuilder szDataDescr, + in CRYPTOAPI_BLOB pOptionalEntropy, + IntPtr pvReserved, + IntPtr pPromptStruct, + CryptProtectFlags dwFlags, + out CRYPTOAPI_BLOB pDataOut); + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_USER + { + public SID_AND_ATTRIBUTES User; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + + public uint Attributes; + } + + [Flags] + public enum CryptProtectFlags + { + CRYPTPROTECT_UI_FORBIDDEN = 0x1, + + CRYPTPROTECT_LOCAL_MACHINE = 0x4, + + CRYPTPROTECT_CRED_SYNC = 0x8, + + CRYPTPROTECT_AUDIT = 0x10, + + CRYPTPROTECT_NO_RECOVERY = 0x20, + + CRYPTPROTECT_VERIFY_PROTECTION = 0x40, + + CRYPTPROTECT_CRED_REGENERATE = 0x80 + } + + [StructLayout(LayoutKind.Sequential)] + public struct CRYPTOAPI_BLOB + { + public uint cbData; + + public IntPtr pbData; + } + + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + + TokenGroups, + + TokenPrivileges, + + TokenOwner, + + TokenPrimaryGroup, + + TokenDefaultDacl, + + TokenSource, + + TokenType, + + TokenImpersonationLevel, + + TokenStatistics, + + TokenRestrictedSids, + + TokenSessionId, + + TokenGroupsAndPrivileges, + + TokenSessionReference, + + TokenSandBoxInert, + + TokenAuditPolicy, + + TokenOrigin, + + TokenElevationType, + + TokenLinkedToken, + + TokenElevation, + + TokenHasRestrictions, + + TokenAccessInformation, + + TokenVirtualizationAllowed, + + TokenVirtualizationEnabled, + + TokenIntegrityLevel, + + TokenUIAccess, + + TokenMandatoryPolicy, + + TokenLogonSid, + + TokenIsAppContainer, + + TokenCapabilities, + + TokenAppContainerSid, + + TokenAppContainerNumber, + + TokenUserClaimAttributes, + + TokenDeviceClaimAttributes, + + TokenRestrictedUserClaimAttributes, + + TokenRestrictedDeviceClaimAttributes, + + TokenDeviceGroups, + + TokenRestrictedDeviceGroups, + + TokenSecurityAttributes, + + TokenIsRestricted + } + + [Serializable] + public enum TOKEN_TYPE + { + TokenPrimary = 1, + + TokenImpersonation = 2 + } + + [Flags] + public enum TokenAccess : uint + { + TOKEN_ASSIGN_PRIMARY = 0x0001, + + TOKEN_DUPLICATE = 0x0002, + + TOKEN_IMPERSONATE = 0x0004, + + TOKEN_QUERY = 0x0008, + + TOKEN_QUERY_SOURCE = 0x0010, + + TOKEN_ADJUST_PRIVILEGES = 0x0020, + + TOKEN_ADJUST_GROUPS = 0x0040, + + TOKEN_ADJUST_DEFAULT = 0x0080, + + TOKEN_ADJUST_SESSIONID = 0x0100, + + TOKEN_ALL_ACCESS_P = 0x000F00FF, + + TOKEN_ALL_ACCESS = 0x000F01FF, + + TOKEN_READ = 0x00020008, + + TOKEN_WRITE = 0x000200E0, + + TOKEN_EXECUTE = 0x00020000 + } + + [DllImport("api-ms-win-core-wow64-l1-1-1.dll", SetLastError = true)] + private static extern bool IsWow64Process2( + IntPtr process, + out ushort processMachine, + out ushort nativeMachine); + + // https://stackoverflow.com/questions/54456140/how-to-detect-were-running-under-the-arm64-version-of-windows-10-in-net + // https://learn.microsoft.com/windows/win32/sysinfo/image-file-machine-constants + private static bool? isRunningOnArm = null; + + public static bool IsRunningOnArm + { + get + { + if (isRunningOnArm is null) + { + isRunningOnArm = IsArmProcessor(); + App.Logger.LogInformation("Running on ARM: {0}", isRunningOnArm); + } + return isRunningOnArm ?? false; + } + } + + private static bool IsArmProcessor() + { + var handle = System.Diagnostics.Process.GetCurrentProcess().Handle; + if (!IsWow64Process2(handle, out _, out var nativeMachine)) + { + return false; + } + return (nativeMachine == 0xaa64 || + nativeMachine == 0x01c0 || + nativeMachine == 0x01c2 || + nativeMachine == 0x01c4); + } + + private static bool? isHasThreadAccessPropertyPresent = null; + + public static bool IsHasThreadAccessPropertyPresent + { + get + { + isHasThreadAccessPropertyPresent ??= ApiInformation.IsPropertyPresent(typeof(DispatcherQueue).FullName, "HasThreadAccess"); + return isHasThreadAccessPropertyPresent ?? false; + } + } + + public static Task GetFileAssociationAsync(string filePath) + => Win32Helper.GetFileAssociationAsync(filePath, true); + } +} \ No newline at end of file diff --git a/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs b/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs index 27417b823517..6c47ff8c63d0 100644 --- a/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs +++ b/src/Files.App/Helpers/Layout/AdaptiveLayoutHelpers.cs @@ -49,7 +49,7 @@ private static Layouts GetPathLayout(string path) { var iniPath = IO.Path.Combine(path, "desktop.ini"); - var iniContents = Win32Helper.ReadStringFromFile(iniPath)?.Trim(); + var iniContents = NativeFileOperationsHelper.ReadStringFromFile(iniPath)?.Trim(); if (string.IsNullOrEmpty(iniContents)) return Layouts.None; diff --git a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs index a2f19e1b41a3..a024363c1146 100644 --- a/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs +++ b/src/Files.App/Helpers/Layout/LayoutPreferencesManager.cs @@ -396,7 +396,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte { if (!UserSettingsService.LayoutSettingsService.SyncFolderPreferencesAcrossDirectories) { - var folderFRN = Win32Helper.GetFolderFRN(path); + var folderFRN = NativeFileOperationsHelper.GetFolderFRN(path); var trimmedFolderPath = path.TrimPath(); if (trimmedFolderPath is not null) SetLayoutPreferencesToDatabase(trimmedFolderPath, folderFRN, preferencesItem); @@ -509,7 +509,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte { path = path.TrimPath() ?? string.Empty; - var folderFRN = Win32Helper.GetFolderFRN(path); + var folderFRN = NativeFileOperationsHelper.GetFolderFRN(path); return GetLayoutPreferencesFromDatabase(path, folderFRN) ?? GetLayoutPreferencesFromAds(path, folderFRN) @@ -521,7 +521,7 @@ public static void SetLayoutPreferencesForPath(string path, LayoutPreferencesIte private static LayoutPreferencesItem? GetLayoutPreferencesFromAds(string path, ulong? frn) { - var str = Win32Helper.ReadStringFromFile($"{path}:files_layoutmode"); + var str = NativeFileOperationsHelper.ReadStringFromFile($"{path}:files_layoutmode"); var layoutPreferences = SafetyExtensions.IgnoreExceptions(() => string.IsNullOrEmpty(str) ? null : JsonSerializer.Deserialize(str)); diff --git a/src/Files.App/Helpers/NativeFindStorageItemHelper.cs b/src/Files.App/Helpers/NativeFindStorageItemHelper.cs new file mode 100644 index 000000000000..bc9f3bc9f0f7 --- /dev/null +++ b/src/Files.App/Helpers/NativeFindStorageItemHelper.cs @@ -0,0 +1,131 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace Files.App.Helpers +{ + /// + /// Provides a bunch of Win32API for native find storage items. + /// + public sealed class NativeFindStorageItemHelper + { + [StructLayout(LayoutKind.Sequential)] + public struct SYSTEMTIME + { + [MarshalAs(UnmanagedType.U2)] public short Year; + [MarshalAs(UnmanagedType.U2)] public short Month; + [MarshalAs(UnmanagedType.U2)] public short DayOfWeek; + [MarshalAs(UnmanagedType.U2)] public short Day; + [MarshalAs(UnmanagedType.U2)] public short Hour; + [MarshalAs(UnmanagedType.U2)] public short Minute; + [MarshalAs(UnmanagedType.U2)] public short Second; + [MarshalAs(UnmanagedType.U2)] public short Milliseconds; + + public SYSTEMTIME(DateTime dt) + { + dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC + Year = (short)dt.Year; + Month = (short)dt.Month; + DayOfWeek = (short)dt.DayOfWeek; + Day = (short)dt.Day; + Hour = (short)dt.Hour; + Minute = (short)dt.Minute; + Second = (short)dt.Second; + Milliseconds = (short)dt.Millisecond; + } + + public DateTime ToDateTime() + { + return new(Year, Month, Day, Hour, Minute, Second, Milliseconds, DateTimeKind.Utc); + } + } + + public enum FINDEX_INFO_LEVELS + { + FindExInfoStandard = 0, + FindExInfoBasic = 1 + } + + public enum FINDEX_SEARCH_OPS + { + FindExSearchNameMatch = 0, + FindExSearchLimitToDirectories = 1, + FindExSearchLimitToDevices = 2 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WIN32_FIND_DATA + { + public uint dwFileAttributes; + + public FILETIME ftCreationTime; + public FILETIME ftLastAccessTime; + public FILETIME ftLastWriteTime; + + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] + public string cFileName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } + + [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr FindFirstFileExFromApp( + string lpFileName, + FINDEX_INFO_LEVELS fInfoLevelId, + out WIN32_FIND_DATA lpFindFileData, + FINDEX_SEARCH_OPS fSearchOp, + IntPtr lpSearchFilter, + int dwAdditionalFlags); + + public const int FIND_FIRST_EX_CASE_SENSITIVE = 1; + public const int FIND_FIRST_EX_LARGE_FETCH = 2; + + [DllImport("api-ms-win-core-file-l1-1-0.dll", CharSet = CharSet.Unicode)] + public static extern bool FindNextFile( + IntPtr hFindFile, + out WIN32_FIND_DATA lpFindFileData); + + [DllImport("api-ms-win-core-file-l1-1-0.dll")] + public static extern bool FindClose( + IntPtr hFindFile); + + [DllImport("api-ms-win-core-timezone-l1-1-0.dll", SetLastError = true)] + public static extern bool FileTimeToSystemTime( + ref FILETIME lpFileTime, + out SYSTEMTIME lpSystemTime); + + public static bool GetWin32FindDataForPath( + string targetPath, + out WIN32_FIND_DATA findData) + { + FINDEX_INFO_LEVELS findInfoLevel = FINDEX_INFO_LEVELS.FindExInfoBasic; + + int additionalFlags = FIND_FIRST_EX_LARGE_FETCH; + + IntPtr hFile = FindFirstFileExFromApp( + targetPath, + findInfoLevel, + out findData, + FINDEX_SEARCH_OPS.FindExSearchNameMatch, + IntPtr.Zero, + additionalFlags); + + if (hFile.ToInt64() != -1) + { + FindClose(hFile); + + return true; + } + + return false; + } + } +} diff --git a/src/Files.App/Helpers/NaturalStringComparer.cs b/src/Files.App/Helpers/NaturalStringComparer.cs index 2e22c266417c..e786d92e7a36 100644 --- a/src/Files.App/Helpers/NaturalStringComparer.cs +++ b/src/Files.App/Helpers/NaturalStringComparer.cs @@ -1,13 +1,48 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + namespace Files.App.Helpers { + internal static class SafeNativeMethods + { + public const Int32 NORM_IGNORECASE = 0x00000001; + public const Int32 NORM_IGNORENONSPACE = 0x00000002; + public const Int32 NORM_IGNORESYMBOLS = 0x00000004; + public const Int32 LINGUISTIC_IGNORECASE = 0x00000010; + public const Int32 LINGUISTIC_IGNOREDIACRITIC = 0x00000020; + public const Int32 NORM_IGNOREKANATYPE = 0x00010000; + public const Int32 NORM_IGNOREWIDTH = 0x00020000; + public const Int32 NORM_LINGUISTIC_CASING = 0x08000000; + public const Int32 SORT_STRINGSORT = 0x00001000; + public const Int32 SORT_DIGITSASNUMBERS = 0x00000008; + + public const String LOCALE_NAME_USER_DEFAULT = null; + public const String LOCALE_NAME_INVARIANT = ""; + public const String LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale"; + + [DllImport("api-ms-win-core-string-l1-1-0.dll", CharSet = CharSet.Unicode)] + public static extern Int32 CompareStringEx( + String localeName, + Int32 flags, + String str1, + Int32 count1, + String str2, + Int32 count2, + IntPtr versionInformation, + IntPtr reserved, + Int32 param + ); + } + public sealed class NaturalStringComparer { public static IComparer GetForProcessor() { - return Win32Helper.IsRunningOnArm ? new StringComparerArm64() : new StringComparerDefault(); + return NativeWinApiHelper.IsRunningOnArm ? new StringComparerArm64() : new StringComparerDefault(); } private sealed class StringComparerArm64 : IComparer @@ -22,9 +57,9 @@ private sealed class StringComparerDefault : IComparer { public int Compare(object a, object b) { - return Win32PInvoke.CompareStringEx( - Win32PInvoke.LOCALE_NAME_USER_DEFAULT, - Win32PInvoke.SORT_DIGITSASNUMBERS, // Add other flags if required. + return SafeNativeMethods.CompareStringEx( + SafeNativeMethods.LOCALE_NAME_USER_DEFAULT, + SafeNativeMethods.SORT_DIGITSASNUMBERS, // Add other flags if required. a?.ToString(), a?.ToString().Length ?? 0, b?.ToString(), @@ -35,4 +70,4 @@ public int Compare(object a, object b) } } } -} +} \ No newline at end of file diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index cb701523f36b..e76430b91f90 100644 --- a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs +++ b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs @@ -15,7 +15,7 @@ public static class NavigationHelpers { private static MainPageViewModel MainPageViewModel { get; } = Ioc.Default.GetRequiredService(); private static DrivesViewModel DrivesViewModel { get; } = Ioc.Default.GetRequiredService(); - private static NetworkDrivesViewModel NetworkDrivesViewModel { get; } = Ioc.Default.GetRequiredService(); + private static INetworkDrivesService NetworkDrivesService { get; } = Ioc.Default.GetRequiredService(); public static Task OpenPathInNewTab(string? path, bool focusNewTab) { @@ -169,7 +169,7 @@ private static async Task UpdateTabInfoAsync(TabBarItem tabItem, object navigati } else if (PathNormalization.NormalizePath(PathNormalization.GetPathRoot(currentPath)) == normalizedCurrentPath) // If path is a drive's root { - var matchingDrive = NetworkDrivesViewModel.Drives.Cast().FirstOrDefault(netDrive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(netDrive.Path), StringComparison.OrdinalIgnoreCase)); + var matchingDrive = NetworkDrivesService.Drives.Cast().FirstOrDefault(netDrive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(netDrive.Path), StringComparison.OrdinalIgnoreCase)); matchingDrive ??= DrivesViewModel.Drives.Cast().FirstOrDefault(drive => normalizedCurrentPath.Contains(PathNormalization.NormalizePath(drive.Path), StringComparison.OrdinalIgnoreCase)); tabLocationHeader = matchingDrive is not null ? matchingDrive.Text : normalizedCurrentPath; } @@ -336,9 +336,9 @@ public static async Task OpenItemsWithExecutableAsync(IShellPage associatedInsta public static async Task OpenPath(string path, IShellPage associatedInstance, FilesystemItemType? itemType = null, bool openSilent = false, bool openViaApplicationPicker = false, IEnumerable? selectItems = null, string? args = default, bool forceOpenInNewTab = false) { string previousDir = associatedInstance.FilesystemViewModel.WorkingDirectory; - bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); - bool isDirectory = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Directory); - bool isReparsePoint = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.ReparsePoint); + bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isDirectory = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Directory); + bool isReparsePoint = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.ReparsePoint); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); bool isScreenSaver = FileExtensionHelpers.IsScreenSaverFile(path); bool isTag = path.StartsWith("tag:"); @@ -392,16 +392,16 @@ public static async Task OpenPath(string path, IShellPage associatedInstan else if (isReparsePoint) { if (!isDirectory && - Win32Helper.GetWin32FindDataForPath(path, out var findData) && - findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK) + NativeFindStorageItemHelper.GetWin32FindDataForPath(path, out var findData) && + findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK) { - shortcutInfo.TargetPath = Win32Helper.ParseSymLink(path); + shortcutInfo.TargetPath = NativeFileOperationsHelper.ParseSymLink(path); } itemType ??= isDirectory ? FilesystemItemType.Directory : FilesystemItemType.File; } else if (isHiddenItem) { - itemType = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Directory) ? FilesystemItemType.Directory : FilesystemItemType.File; + itemType = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Directory) ? FilesystemItemType.Directory : FilesystemItemType.File; } else { @@ -443,7 +443,7 @@ private static async Task OpenLibrary(string path, IShellPage IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService(); var opened = (FilesystemResult)false; - bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); if (isHiddenItem) { await OpenPath(forceOpenInNewTab, UserSettingsService.FoldersSettingsService.OpenFoldersInNewTab, path, associatedInstance); @@ -463,7 +463,7 @@ private static async Task OpenDirectory(string path, IShellPag IUserSettingsService UserSettingsService = Ioc.Default.GetRequiredService(); var opened = (FilesystemResult)false; - bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); if (isShortcut) @@ -507,7 +507,7 @@ private static async Task OpenDirectory(string path, IShellPag private static async Task OpenFile(string path, IShellPage associatedInstance, ShellLinkItem shortcutInfo, bool openViaApplicationPicker = false, string? args = default) { var opened = (FilesystemResult)false; - bool isHiddenItem = Win32Helper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); + bool isHiddenItem = NativeFileOperationsHelper.HasFileAttribute(path, System.IO.FileAttributes.Hidden); bool isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path) || !string.IsNullOrEmpty(shortcutInfo.TargetPath); if (isShortcut) diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs index 71763e791da8..6165db4610c6 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Process.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Process.cs @@ -1,10 +1,6 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Microsoft.Extensions.Logging; -using Windows.Foundation.Metadata; -using Windows.System; - namespace Files.App.Helpers { /// @@ -112,50 +108,5 @@ public static List WhoIsLocking(string[] resources) return processes; } - - // https://stackoverflow.com/questions/54456140/how-to-detect-were-running-under-the-arm64-version-of-windows-10-in-net - // https://learn.microsoft.com/windows/win32/sysinfo/image-file-machine-constants - private static bool? isRunningOnArm = null; - public static bool IsRunningOnArm - { - get - { - if (isRunningOnArm is null) - { - isRunningOnArm = IsArmProcessor(); - App.Logger.LogInformation("Running on ARM: {0}", isRunningOnArm); - } - return isRunningOnArm ?? false; - } - } - - private static bool IsArmProcessor() - { - var handle = System.Diagnostics.Process.GetCurrentProcess().Handle; - if (!Win32PInvoke.IsWow64Process2(handle, out _, out var nativeMachine)) - { - return false; - } - return (nativeMachine == 0xaa64 || - nativeMachine == 0x01c0 || - nativeMachine == 0x01c2 || - nativeMachine == 0x01c4); - } - - private static bool? isHasThreadAccessPropertyPresent = null; - - public static bool IsHasThreadAccessPropertyPresent - { - get - { - isHasThreadAccessPropertyPresent ??= ApiInformation.IsPropertyPresent(typeof(DispatcherQueue).FullName, "HasThreadAccess"); - return isHasThreadAccessPropertyPresent ?? false; - } - } - - public static Task GetFileAssociationAsync(string filePath) - { - return Win32Helper.GetFileAssociationAsync(filePath, true); - } } } diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs index 1a989574f050..138bae36e0f5 100644 --- a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs +++ b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs @@ -2,13 +2,11 @@ // Licensed under the MIT License. See the LICENSE. using Microsoft.Extensions.Logging; -using Microsoft.Win32.SafeHandles; using System.Collections.Concurrent; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Windows.Forms; using Vanara.PInvoke; @@ -640,6 +638,20 @@ private static bool IsAlphaBitmap(BitmapData bmpData) return false; } + // There is usually no need to define Win32 COM interfaces/P-Invoke methods here. + // The Vanara library contains the definitions for all members of Shell32.dll, User32.dll and more + // The ones below are due to bugs in the current version of the library and can be removed once fixed + // Structure used by SHQueryRecycleBin. + [StructLayout(LayoutKind.Sequential, Pack = 0)] + public struct SHQUERYRBINFO + { + public int cbSize; + + public long i64Size; + + public long i64NumItems; + } + public static IEnumerable GetDesktopWindows() { HWND prevHwnd = HWND.NULL; @@ -806,6 +818,10 @@ public static void OpenFolderInExistingShellWindow(string folderPath) } } + // Get information from recycle bin. + [DllImport(Lib.Shell32, SetLastError = false, CharSet = CharSet.Unicode)] + public static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo); + public static async Task InstallInf(string filePath) { try @@ -877,275 +893,5 @@ private static Process CreatePowershellProcess(string command, bool runAsAdmin) return process; } - - public static SafeFileHandle CreateFileForWrite(string filePath, bool overwrite = true) - { - return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, - Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, overwrite ? Win32PInvoke.CREATE_ALWAYS : Win32PInvoke.OPEN_ALWAYS, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); - } - - public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = false, uint flags = 0) - { - return new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, - Win32PInvoke.GENERIC_READ | (readWrite ? Win32PInvoke.GENERIC_WRITE : 0), (uint)(Win32PInvoke.FILE_SHARE_READ | (readWrite ? 0 : Win32PInvoke.FILE_SHARE_WRITE)), IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics | flags, IntPtr.Zero), true); - } - - public static bool GetFileDateModified(string filePath, out FILETIME dateModified) - { - using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.GENERIC_READ, Win32PInvoke.FILE_SHARE_READ, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); - return Win32PInvoke.GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified); - } - - public static bool SetFileDateModified(string filePath, FILETIME dateModified) - { - using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, Win32PInvoke.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true); - return Win32PInvoke.SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified); - } - - public static bool HasFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (Win32PInvoke.GetFileAttributesExFromApp( - lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return (lpFileInfo.dwFileAttributes & dwAttrs) == dwAttrs; - } - return false; - } - - public static bool SetFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (!Win32PInvoke.GetFileAttributesExFromApp( - lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return false; - } - return Win32PInvoke.SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes | dwAttrs); - } - - public static bool UnsetFileAttribute(string lpFileName, FileAttributes dwAttrs) - { - if (!Win32PInvoke.GetFileAttributesExFromApp( - lpFileName, Win32PInvoke.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out var lpFileInfo)) - { - return false; - } - return Win32PInvoke.SetFileAttributesFromApp(lpFileName, lpFileInfo.dwFileAttributes & ~dwAttrs); - } - - public static string ReadStringFromFile(string filePath) - { - IntPtr hFile = Win32PInvoke.CreateFileFromApp(filePath, - Win32PInvoke.GENERIC_READ, - Win32PInvoke.FILE_SHARE_READ, - IntPtr.Zero, - Win32PInvoke.OPEN_EXISTING, - (uint)Win32PInvoke.File_Attributes.BackupSemantics, - IntPtr.Zero); - - if (hFile.ToInt64() == -1) - { - return null; - } - - const int BUFFER_LENGTH = 4096; - byte[] buffer = new byte[BUFFER_LENGTH]; - int dwBytesRead; - string szRead = string.Empty; - - unsafe - { - using (MemoryStream ms = new MemoryStream()) - using (StreamReader reader = new StreamReader(ms, true)) - { - while (true) - { - fixed (byte* pBuffer = buffer) - { - if (Win32PInvoke.ReadFile(hFile, pBuffer, BUFFER_LENGTH - 1, &dwBytesRead, IntPtr.Zero) && dwBytesRead > 0) - { - ms.Write(buffer, 0, dwBytesRead); - } - else - { - break; - } - } - } - ms.Position = 0; - szRead = reader.ReadToEnd(); - } - } - - Win32PInvoke.CloseHandle(hFile); - - return szRead; - } - - public static bool WriteStringToFile(string filePath, string str, Win32PInvoke.File_Attributes flags = 0) - { - IntPtr hStream = Win32PInvoke.CreateFileFromApp(filePath, - Win32PInvoke.GENERIC_WRITE, 0, IntPtr.Zero, Win32PInvoke.CREATE_ALWAYS, (uint)(Win32PInvoke.File_Attributes.BackupSemantics | flags), IntPtr.Zero); - if (hStream.ToInt64() == -1) - { - return false; - } - byte[] buff = Encoding.UTF8.GetBytes(str); - int dwBytesWritten; - unsafe - { - fixed (byte* pBuff = buff) - { - Win32PInvoke.WriteFile(hStream, pBuff, buff.Length, &dwBytesWritten, IntPtr.Zero); - } - } - Win32PInvoke.CloseHandle(hStream); - return true; - } - - public static bool WriteBufferToFileWithProgress(string filePath, byte[] buffer, Win32PInvoke.LPOVERLAPPED_COMPLETION_ROUTINE callback) - { - using var hFile = CreateFileForWrite(filePath); - - if (hFile.IsInvalid) - { - return false; - } - - NativeOverlapped nativeOverlapped = new NativeOverlapped(); - bool result = Win32PInvoke.WriteFileEx(hFile.DangerousGetHandle(), buffer, (uint)buffer.LongLength, ref nativeOverlapped, callback); - - if (!result) - { - System.Diagnostics.Debug.WriteLine(Marshal.GetLastWin32Error()); - } - - return result; - } - - // https://www.pinvoke.net/default.aspx/kernel32/GetFileInformationByHandleEx.html - public static ulong? GetFolderFRN(string folderPath) - { - using var handle = OpenFileForRead(folderPath); - if (!handle.IsInvalid) - { - var fileStruct = new Win32PInvoke.FILE_ID_BOTH_DIR_INFO(); - if (Win32PInvoke.GetFileInformationByHandleEx(handle.DangerousGetHandle(), Win32PInvoke.FILE_INFO_BY_HANDLE_CLASS.FileIdBothDirectoryInfo, out fileStruct, (uint)Marshal.SizeOf(fileStruct))) - { - return (ulong)fileStruct.FileId; - } - } - return null; - } - - public static ulong? GetFileFRN(string filePath) - { - using var handle = OpenFileForRead(filePath); - if (!handle.IsInvalid) - { - try - { - var fileID = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileIdInfo); - return BitConverter.ToUInt64(fileID.FileId.Identifier, 0); - } - catch { } - } - return null; - } - - public static long? GetFileSizeOnDisk(string filePath) - { - using var handle = OpenFileForRead(filePath); - if (!handle.IsInvalid) - { - try - { - var fileAllocationInfo = Kernel32.GetFileInformationByHandleEx(handle, Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo); - return fileAllocationInfo.AllocationSize; - } - catch { } - } - return null; - } - - // https://github.com/rad1oactive/BetterExplorer/blob/master/Windows%20API%20Code%20Pack%201.1/source/WindowsAPICodePack/Shell/ReparsePoint.cs - public static string ParseSymLink(string path) - { - using var handle = OpenFileForRead(path, false, 0x00200000); - if (!handle.IsInvalid) - { - Win32PInvoke.REPARSE_DATA_BUFFER buffer = new Win32PInvoke.REPARSE_DATA_BUFFER(); - if (Win32PInvoke.DeviceIoControl(handle.DangerousGetHandle(), Win32PInvoke.FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0, out (nint)buffer, Win32PInvoke.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, out _, IntPtr.Zero)) - { - var subsString = new string(buffer.PathBuffer, ((buffer.SubsNameOffset / 2) + 2), buffer.SubsNameLength / 2); - var printString = new string(buffer.PathBuffer, ((buffer.PrintNameOffset / 2) + 2), buffer.PrintNameLength / 2); - var normalisedTarget = printString ?? subsString; - if (string.IsNullOrEmpty(normalisedTarget)) - { - normalisedTarget = subsString; - if (normalisedTarget.StartsWith(@"\??\", StringComparison.Ordinal)) - { - normalisedTarget = normalisedTarget.Substring(4); - } - } - if (buffer.ReparseTag == Win32PInvoke.IO_REPARSE_TAG_SYMLINK && (normalisedTarget.Length < 2 || normalisedTarget[1] != ':')) - { - // Target is relative, get the absolute path - normalisedTarget = normalisedTarget.TrimStart(Path.DirectorySeparatorChar); - path = path.TrimEnd(Path.DirectorySeparatorChar); - normalisedTarget = Path.GetFullPath(Path.Combine(path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar)), normalisedTarget)); - } - return normalisedTarget; - } - } - return null; - } - - // https://stackoverflow.com/a/7988352 - public static IEnumerable<(string Name, long Size)> GetAlternateStreams(string path) - { - Win32PInvoke.WIN32_FIND_STREAM_DATA findStreamData = new Win32PInvoke.WIN32_FIND_STREAM_DATA(); - IntPtr hFile = Win32PInvoke.FindFirstStreamW(path, Win32PInvoke.StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); - - if (hFile.ToInt64() != -1) - { - do - { - // The documentation for FindFirstStreamW says that it is always a ::$DATA - // stream type, but FindNextStreamW doesn't guarantee that for subsequent - // streams so we check to make sure - if (findStreamData.cStreamName.EndsWith(":$DATA") && findStreamData.cStreamName != "::$DATA") - { - yield return (findStreamData.cStreamName, findStreamData.StreamSize); - } - } - while (Win32PInvoke.FindNextStreamW(hFile, findStreamData)); - - Win32PInvoke.FindClose(hFile); - } - } - - public static bool GetWin32FindDataForPath(string targetPath, out Win32PInvoke.WIN32_FIND_DATA findData) - { - Win32PInvoke.FINDEX_INFO_LEVELS findInfoLevel = Win32PInvoke.FINDEX_INFO_LEVELS.FindExInfoBasic; - - int additionalFlags = Win32PInvoke.FIND_FIRST_EX_LARGE_FETCH; - - IntPtr hFile = Win32PInvoke.FindFirstFileExFromApp( - targetPath, - findInfoLevel, - out findData, - Win32PInvoke.FINDEX_SEARCH_OPS.FindExSearchNameMatch, - IntPtr.Zero, - additionalFlags); - - if (hFile.ToInt64() != -1) - { - Win32PInvoke.FindClose(hFile); - - return true; - } - - return false; - } } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs index f1a573124ce9..0bdf6dfcf2e5 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Consts.cs @@ -26,46 +26,10 @@ public static partial class Win32PInvoke public const uint GENERIC_WRITE = 0x40000000; public const int FILE_SHARE_READ = 0x00000001; public const int FILE_SHARE_WRITE = 0x00000002; - public const uint FILE_SHARE_DELETE = 0x00000004; public const int OPEN_EXISTING = 3; public const int FSCTL_LOCK_VOLUME = 0x00090018; public const int FSCTL_DISMOUNT_VOLUME = 0x00090020; public const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808; public const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804; - - public const uint FILE_APPEND_DATA = 0x0004; - public const uint FILE_WRITE_ATTRIBUTES = 0x100; - - - public const uint FILE_BEGIN = 0; - public const uint FILE_END = 2; - - public const uint CREATE_ALWAYS = 2; - public const uint CREATE_NEW = 1; - public const uint OPEN_ALWAYS = 4; - public const uint TRUNCATE_EXISTING = 5; - - public const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; - public const int FSCTL_GET_REPARSE_POINT = 0x000900A8; - public const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; - public const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C; - - public const int FIND_FIRST_EX_CASE_SENSITIVE = 1; - public const int FIND_FIRST_EX_LARGE_FETCH = 2; - - public const int NORM_IGNORECASE = 0x00000001; - public const int NORM_IGNORENONSPACE = 0x00000002; - public const int NORM_IGNORESYMBOLS = 0x00000004; - public const int LINGUISTIC_IGNORECASE = 0x00000010; - public const int LINGUISTIC_IGNOREDIACRITIC = 0x00000020; - public const int NORM_IGNOREKANATYPE = 0x00010000; - public const int NORM_IGNOREWIDTH = 0x00020000; - public const int NORM_LINGUISTIC_CASING = 0x08000000; - public const int SORT_STRINGSORT = 0x00001000; - public const int SORT_DIGITSASNUMBERS = 0x00000008; - - public const string LOCALE_NAME_USER_DEFAULT = null; - public const string LOCALE_NAME_INVARIANT = ""; - public const string LOCALE_NAME_SYSTEM_DEFAULT = "!sys-default-locale"; } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs index 768505990b0a..f12643361ab7 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Enums.cs @@ -15,172 +15,5 @@ public enum RM_APP_TYPE RmConsole = 5, RmCritical = 1000 } - - public enum File_Attributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Directory = 0x00000010, - Archive = 0x00000020, - Device = 0x00000040, - Normal = 0x00000080, - Temporary = 0x00000100, - SparseFile = 0x00000200, - ReparsePoint = 0x00000400, - Compressed = 0x00000800, - Offline = 0x00001000, - NotContentIndexed = 0x00002000, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - FirstPipeInstance = 0x00080000 - } - - public enum FILE_INFO_BY_HANDLE_CLASS - { - FileBasicInfo = 0, - FileStandardInfo = 1, - FileNameInfo = 2, - FileRenameInfo = 3, - FileDispositionInfo = 4, - FileAllocationInfo = 5, - FileEndOfFileInfo = 6, - FileStreamInfo = 7, - FileCompressionInfo = 8, - FileAttributeTagInfo = 9, - FileIdBothDirectoryInfo = 10,// 0x0A - FileIdBothDirectoryRestartInfo = 11, // 0xB - FileIoPriorityHintInfo = 12, // 0xC - FileRemoteProtocolInfo = 13, // 0xD - FileFullDirectoryInfo = 14, // 0xE - FileFullDirectoryRestartInfo = 15, // 0xF - FileStorageInfo = 16, // 0x10 - FileAlignmentInfo = 17, // 0x11 - FileIdInfo = 18, // 0x12 - FileIdExtdDirectoryInfo = 19, // 0x13 - FileIdExtdDirectoryRestartInfo = 20, // 0x14 - MaximumFileInfoByHandlesClass - } - - public enum GET_FILEEX_INFO_LEVELS - { - GetFileExInfoStandard, - } - - public enum StreamInfoLevels - { - FindStreamInfoStandard = 0 - } - - [Flags] - public enum CryptProtectFlags - { - CRYPTPROTECT_UI_FORBIDDEN = 0x1, - CRYPTPROTECT_LOCAL_MACHINE = 0x4, - CRYPTPROTECT_CRED_SYNC = 0x8, - CRYPTPROTECT_AUDIT = 0x10, - CRYPTPROTECT_NO_RECOVERY = 0x20, - CRYPTPROTECT_VERIFY_PROTECTION = 0x40, - CRYPTPROTECT_CRED_REGENERATE = 0x80 - } - - public enum TOKEN_INFORMATION_CLASS - { - TokenUser = 1, - TokenGroups, - TokenPrivileges, - TokenOwner, - TokenPrimaryGroup, - TokenDefaultDacl, - TokenSource, - TokenType, - TokenImpersonationLevel, - TokenStatistics, - TokenRestrictedSids, - TokenSessionId, - TokenGroupsAndPrivileges, - TokenSessionReference, - TokenSandBoxInert, - TokenAuditPolicy, - TokenOrigin, - TokenElevationType, - TokenLinkedToken, - TokenElevation, - TokenHasRestrictions, - TokenAccessInformation, - TokenVirtualizationAllowed, - TokenVirtualizationEnabled, - TokenIntegrityLevel, - TokenUIAccess, - TokenMandatoryPolicy, - TokenLogonSid, - TokenIsAppContainer, - TokenCapabilities, - TokenAppContainerSid, - TokenAppContainerNumber, - TokenUserClaimAttributes, - TokenDeviceClaimAttributes, - TokenRestrictedUserClaimAttributes, - TokenRestrictedDeviceClaimAttributes, - TokenDeviceGroups, - TokenRestrictedDeviceGroups, - TokenSecurityAttributes, - TokenIsRestricted - } - - [Serializable] - public enum TOKEN_TYPE - { - TokenPrimary = 1, - - TokenImpersonation = 2 - } - - [Flags] - public enum TokenAccess : uint - { - TOKEN_ASSIGN_PRIMARY = 0x0001, - TOKEN_DUPLICATE = 0x0002, - TOKEN_IMPERSONATE = 0x0004, - TOKEN_QUERY = 0x0008, - TOKEN_QUERY_SOURCE = 0x0010, - TOKEN_ADJUST_PRIVILEGES = 0x0020, - TOKEN_ADJUST_GROUPS = 0x0040, - TOKEN_ADJUST_DEFAULT = 0x0080, - TOKEN_ADJUST_SESSIONID = 0x0100, - TOKEN_ALL_ACCESS_P = 0x000F00FF, - TOKEN_ALL_ACCESS = 0x000F01FF, - TOKEN_READ = 0x00020008, - TOKEN_WRITE = 0x000200E0, - TOKEN_EXECUTE = 0x00020000 - } - - public enum FINDEX_INFO_LEVELS - { - FindExInfoStandard = 0, - FindExInfoBasic = 1 - } - - public enum FINDEX_SEARCH_OPS - { - FindExSearchNameMatch = 0, - FindExSearchLimitToDirectories = 1, - FindExSearchLimitToDevices = 2 - } - - [Flags] - public enum ClassContext : uint - { - LocalServer = 0x4 - } } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs index c2f9c11fe88f..1cbdbb628d2e 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs @@ -1,9 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using System.IO; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Text; using Vanara.PInvoke; using static Vanara.PInvoke.User32; @@ -18,11 +16,6 @@ public delegate void LpoverlappedCompletionRoutine( OVERLAPPED lpOverlapped ); - public delegate void LPOVERLAPPED_COMPLETION_ROUTINE( - uint dwErrorCode, - uint dwNumberOfBytesTransfered, - ref NativeOverlapped lpOverlapped); - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] public static extern int RmRegisterResources( uint pSessionHandle, @@ -189,7 +182,7 @@ public static extern bool DeviceIoControl( uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, - out IntPtr lpOutBuffer, + IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped @@ -216,194 +209,5 @@ public static extern int ToUnicode( int bufferSize, uint flags ); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern IntPtr CreateFileFromApp( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr SecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile - ); - - [DllImport("api-ms-win-core-io-l1-1-0.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeviceIoControl( - IntPtr hDevice, - uint dwIoControlCode, - IntPtr lpInBuffer, - uint nInBufferSize, - //IntPtr lpOutBuffer, - out REPARSE_DATA_BUFFER outBuffer, - uint nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetFileAttributesExFromApp( - string lpFileName, - GET_FILEEX_INFO_LEVELS fInfoLevelId, - out WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetFileAttributesFromApp( - string lpFileName, - FileAttributes dwFileAttributes); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", ExactSpelling = true, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern uint SetFilePointer( - IntPtr hFile, - long lDistanceToMove, - IntPtr lpDistanceToMoveHigh, - uint dwMoveMethod - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public unsafe static extern bool ReadFile( - IntPtr hFile, - byte* lpBuffer, - int nBufferLength, - int* lpBytesReturned, - IntPtr lpOverlapped - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public unsafe static extern bool WriteFile( - IntPtr hFile, - byte* lpBuffer, - int nBufferLength, - int* lpBytesWritten, - IntPtr lpOverlapped - ); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, - CallingConvention = CallingConvention.StdCall, - SetLastError = true)] - public static extern bool WriteFileEx( - IntPtr hFile, - byte[] lpBuffer, - uint nNumberOfBytesToWrite, - [In] ref NativeOverlapped lpOverlapped, - LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool GetFileTime([In] IntPtr hFile, out FILETIME lpCreationTime, out FILETIME lpLastAccessTime, out FILETIME lpLastWriteTime); - - [DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool SetFileTime([In] IntPtr hFile, in FILETIME lpCreationTime, in FILETIME lpLastAccessTime, in FILETIME lpLastWriteTime); - - [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, out FILE_ID_BOTH_DIR_INFO dirInfo, uint dwBufferSize); - - [DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] - public static extern bool GetFileInformationByHandleEx(IntPtr hFile, FILE_INFO_BY_HANDLE_CLASS infoClass, IntPtr dirInfo, uint dwBufferSize); - - [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); - - [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FindNextStreamW(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); - - [DllImport("Shcore.dll", SetLastError = true)] - public static extern int GetDpiForMonitor(IntPtr hmonitor, uint dpiType, out uint dpiX, out uint dpiY); - - [DllImport("api-ms-win-core-processthreads-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool OpenProcessToken([In] IntPtr ProcessHandle, TokenAccess DesiredAccess, out IntPtr TokenHandle); - - [DllImport("api-ms-win-core-processthreads-l1-1-2.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr GetCurrentProcess(); - - [DllImport("api-ms-win-security-base-l1-1-0.dll", SetLastError = true, ExactSpelling = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetTokenInformation(IntPtr hObject, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr pTokenInfo, int tokenInfoLength, out int returnLength); - - [DllImport("api-ms-win-security-base-l1-1-0.dll", ExactSpelling = true, SetLastError = true)] - public static extern int GetLengthSid(IntPtr pSid); - - [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CryptUnprotectData( - in CRYPTOAPI_BLOB pDataIn, - StringBuilder szDataDescr, - in CRYPTOAPI_BLOB pOptionalEntropy, - IntPtr pvReserved, - IntPtr pPromptStruct, - CryptProtectFlags dwFlags, - out CRYPTOAPI_BLOB pDataOut); - - [DllImport("api-ms-win-core-wow64-l1-1-1.dll", SetLastError = true)] - public static extern bool IsWow64Process2( - IntPtr process, - out ushort processMachine, - out ushort nativeMachine); - - [DllImport("api-ms-win-core-file-l1-1-0.dll", CharSet = CharSet.Unicode)] - public static extern bool FindNextFile( - IntPtr hFindFile, - out WIN32_FIND_DATA lpFindFileData); - - [DllImport("api-ms-win-core-file-l1-1-0.dll")] - public static extern bool FindClose( - IntPtr hFindFile); - - [DllImport("api-ms-win-core-timezone-l1-1-0.dll", SetLastError = true)] - public static extern bool FileTimeToSystemTime( - ref FILETIME lpFileTime, - out SYSTEMTIME lpSystemTime); - - [DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr FindFirstFileExFromApp( - string lpFileName, - FINDEX_INFO_LEVELS fInfoLevelId, - out WIN32_FIND_DATA lpFindFileData, - FINDEX_SEARCH_OPS fSearchOp, - IntPtr lpSearchFilter, - int dwAdditionalFlags); - - [DllImport("api-ms-win-core-string-l1-1-0.dll", CharSet = CharSet.Unicode)] - public static extern int CompareStringEx( - string localeName, - int flags, - string str1, - int count1, - string str2, - int count2, - IntPtr versionInformation, - IntPtr reserved, - int param - ); - - [DllImport("shell32.dll", EntryPoint = "#865", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool _IsElevationRequired([MarshalAs(UnmanagedType.LPWStr)] string pszPath); - - [DllImport("shlwapi.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] - public static extern HRESULT SHCreateStreamOnFileEx(string pszFile, STGM grfMode, uint dwAttributes, uint fCreate, IntPtr pstmTemplate, out IntPtr ppstm); - - [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] - public static extern HRESULT SHCreateItemFromParsingName(string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv); - - [DllImport("ole32.dll", CallingConvention = CallingConvention.StdCall)] - public static extern HRESULT CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, ClassContext dwClsContext, ref Guid riid, out IntPtr ppv); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - public static extern uint RegisterApplicationRestart(string pwzCommandLine, int dwFlags); - - [DllImport(Lib.Shell32, SetLastError = false, CharSet = CharSet.Unicode)] - public static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo); } } diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs index e0d5cb244240..584df725c71e 100644 --- a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs +++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs @@ -1,9 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using System.IO; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; namespace Files.App.Helpers { @@ -71,160 +69,5 @@ public unsafe struct FILE_NOTIFY_INFORMATION public uint FileNameLength; public fixed char FileName[1]; } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct REPARSE_DATA_BUFFER - { - public uint ReparseTag; - public short ReparseDataLength; - public short Reserved; - public short SubsNameOffset; - public short SubsNameLength; - public short PrintNameOffset; - public short PrintNameLength; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_REPARSE_DATA_BUFFER_SIZE)] - public char[] PathBuffer; - } - - [StructLayout(LayoutKind.Sequential)] - public struct WIN32_FILE_ATTRIBUTE_DATA - { - public FileAttributes dwFileAttributes; - public FILETIME ftCreationTime; - public FILETIME ftLastAccessTime; - public FILETIME ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct FILE_ID_BOTH_DIR_INFO - { - public uint NextEntryOffset; - public uint FileIndex; - public long CreationTime; - public long LastAccessTime; - public long LastWriteTime; - public long ChangeTime; - public long EndOfFile; - public long AllocationSize; - public uint FileAttributes; - public uint FileNameLength; - public uint EaSize; - public char ShortNameLength; - [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 12)] - public string ShortName; - public long FileId; - [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 1)] - public string FileName; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)] - public struct FILE_STREAM_INFO - { - public uint NextEntryOffset; - public uint StreamNameLength; - public long StreamSize; - public long StreamAllocationSize; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] - public string StreamName; - } - - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public sealed class WIN32_FIND_STREAM_DATA - { - public long StreamSize; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] - public string cStreamName; - } - - [StructLayout(LayoutKind.Sequential)] - public struct TOKEN_USER - { - public SID_AND_ATTRIBUTES User; - } - - [StructLayout(LayoutKind.Sequential)] - public struct SID_AND_ATTRIBUTES - { - public IntPtr Sid; - - public uint Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - public struct CRYPTOAPI_BLOB - { - public uint cbData; - - public IntPtr pbData; - } - - [StructLayout(LayoutKind.Sequential)] - public struct SYSTEMTIME - { - [MarshalAs(UnmanagedType.U2)] public short Year; - [MarshalAs(UnmanagedType.U2)] public short Month; - [MarshalAs(UnmanagedType.U2)] public short DayOfWeek; - [MarshalAs(UnmanagedType.U2)] public short Day; - [MarshalAs(UnmanagedType.U2)] public short Hour; - [MarshalAs(UnmanagedType.U2)] public short Minute; - [MarshalAs(UnmanagedType.U2)] public short Second; - [MarshalAs(UnmanagedType.U2)] public short Milliseconds; - - public SYSTEMTIME(DateTime dt) - { - dt = dt.ToUniversalTime(); // SetSystemTime expects the SYSTEMTIME in UTC - Year = (short)dt.Year; - Month = (short)dt.Month; - DayOfWeek = (short)dt.DayOfWeek; - Day = (short)dt.Day; - Hour = (short)dt.Hour; - Minute = (short)dt.Minute; - Second = (short)dt.Second; - Milliseconds = (short)dt.Millisecond; - } - - public DateTime ToDateTime() - { - return new(Year, Month, Day, Hour, Minute, Second, Milliseconds, DateTimeKind.Utc); - } - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct WIN32_FIND_DATA - { - public uint dwFileAttributes; - - public FILETIME ftCreationTime; - public FILETIME ftLastAccessTime; - public FILETIME ftLastWriteTime; - - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string cFileName; - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } - - // There is usually no need to define Win32 COM interfaces/P-Invoke methods here. - // The Vanara library contains the definitions for all members of Shell32.dll, User32.dll and more - // The ones below are due to bugs in the current version of the library and can be removed once fixed - // Structure used by SHQueryRecycleBin. - [StructLayout(LayoutKind.Sequential, Pack = 0)] - public struct SHQUERYRBINFO - { - public int cbSize; - - public long i64Size; - - public long i64NumItems; - } } } diff --git a/src/Files.App/Services/SideloadUpdateService.cs b/src/Files.App/Services/SideloadUpdateService.cs index e015105e40c9..83549b8207b1 100644 --- a/src/Files.App/Services/SideloadUpdateService.cs +++ b/src/Files.App/Services/SideloadUpdateService.cs @@ -15,6 +15,9 @@ namespace Files.App.Services { public sealed class SideloadUpdateService : ObservableObject, IUpdateService, IDisposable { + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern uint RegisterApplicationRestart(string pwzCommandLine, int dwFlags); + private const string SIDELOAD_STABLE = "https://cdn.files.community/files/stable/Files.Package.appinstaller"; private const string SIDELOAD_PREVIEW = "https://cdn.files.community/files/preview/Files.Package.appinstaller"; @@ -226,7 +229,7 @@ private async Task ApplyPackageUpdateAsync() { PackageManager packageManager = new PackageManager(); - var restartStatus = Win32PInvoke.RegisterApplicationRestart(null, 0); + var restartStatus = RegisterApplicationRestart(null, 0); App.AppModel.ForceProcessTermination = true; Logger?.LogInformation($"Register for restart: {restartStatus}"); diff --git a/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs b/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs index f54087260cc2..738aed5ef537 100644 --- a/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs +++ b/src/Files.App/Services/SizeProvider/CachedSizeProvider.cs @@ -7,7 +7,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; namespace Files.App.Services.SizeProvider { diff --git a/src/Files.App/Utils/FileTags/FileTagsHelper.cs b/src/Files.App/Utils/FileTags/FileTagsHelper.cs index 208a568f4eee..4149057dfe0a 100644 --- a/src/Files.App/Utils/FileTags/FileTagsHelper.cs +++ b/src/Files.App/Utils/FileTags/FileTagsHelper.cs @@ -18,17 +18,17 @@ public static class FileTagsHelper public static string[] ReadFileTag(string filePath) { - var tagString = Win32Helper.ReadStringFromFile($"{filePath}:files"); + var tagString = NativeFileOperationsHelper.ReadStringFromFile($"{filePath}:files"); return tagString?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? []; } public static async void WriteFileTag(string filePath, string[] tag) { - var isDateOk = Win32Helper.GetFileDateModified(filePath, out var dateModified); // Backup date modified - var isReadOnly = Win32Helper.HasFileAttribute(filePath, IO.FileAttributes.ReadOnly); + var isDateOk = NativeFileOperationsHelper.GetFileDateModified(filePath, out var dateModified); // Backup date modified + var isReadOnly = NativeFileOperationsHelper.HasFileAttribute(filePath, IO.FileAttributes.ReadOnly); if (isReadOnly) // Unset read-only attribute (#7534) { - Win32Helper.UnsetFileAttribute(filePath, IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.UnsetFileAttribute(filePath, IO.FileAttributes.ReadOnly); } if (!tag.Any()) { @@ -36,7 +36,7 @@ public static async void WriteFileTag(string filePath, string[] tag) } else if (ReadFileTag(filePath) is not string[] arr || !tag.SequenceEqual(arr)) { - var result = Win32Helper.WriteStringToFile($"{filePath}:files", string.Join(',', tag)); + var result = NativeFileOperationsHelper.WriteStringToFile($"{filePath}:files", string.Join(',', tag)); if (result == false) { ContentDialog dialog = new() @@ -54,11 +54,11 @@ public static async void WriteFileTag(string filePath, string[] tag) } if (isReadOnly) // Restore read-only attribute (#7534) { - Win32Helper.SetFileAttribute(filePath, IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.SetFileAttribute(filePath, IO.FileAttributes.ReadOnly); } if (isDateOk) { - Win32Helper.SetFileDateModified(filePath, dateModified); // Restore date modified + NativeFileOperationsHelper.SetFileDateModified(filePath, dateModified); // Restore date modified } } @@ -105,7 +105,7 @@ public static void UpdateTagsDb() } } - public static ulong? GetFileFRN(string filePath) => Win32Helper.GetFileFRN(filePath); + public static ulong? GetFileFRN(string filePath) => NativeFileOperationsHelper.GetFileFRN(filePath); public static Task GetFileFRN(IStorageItem item) { diff --git a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs index 25738c05f0f4..c2309bee5608 100644 --- a/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs +++ b/src/Files.App/Utils/Serialization/Implementation/DefaultSettingsSerializer.cs @@ -1,10 +1,9 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.Shared.Extensions; -using System; using System.IO; -using static Files.App.Helpers.Win32Helper; +using Windows.Win32; +using static Files.App.Helpers.NativeFileOperationsHelper; namespace Files.App.Utils.Serialization.Implementation { @@ -14,7 +13,7 @@ internal sealed class DefaultSettingsSerializer : ISettingsSerializer public bool CreateFile(string path) { - CreateDirectoryFromApp(Path.GetDirectoryName(path), IntPtr.Zero); + PInvoke.CreateDirectoryFromApp(Path.GetDirectoryName(path), null); var hFile = CreateFileFromApp(path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS, (uint)File_Attributes.BackupSemantics, IntPtr.Zero); if (hFile.IsHandleInvalid()) diff --git a/src/Files.App/Utils/Shell/ItemStreamHelper.cs b/src/Files.App/Utils/Shell/ItemStreamHelper.cs index 523845e21d8c..054cfc024974 100644 --- a/src/Files.App/Utils/Shell/ItemStreamHelper.cs +++ b/src/Files.App/Utils/Shell/ItemStreamHelper.cs @@ -6,13 +6,19 @@ namespace Files.App.Utils.Shell { public static class ItemStreamHelper { + [DllImport("shlwapi.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] + static extern HRESULT SHCreateStreamOnFileEx(string pszFile, STGM grfMode, uint dwAttributes, uint fCreate, IntPtr pstmTemplate, out IntPtr ppstm); + + [DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = true, CharSet = CharSet.Unicode)] + static extern HRESULT SHCreateItemFromParsingName(string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv); + static readonly Guid IShellItemIid = Guid.ParseExact("43826d1e-e718-42ee-bc55-a1e261c37bfe", "d"); public static IntPtr IShellItemFromPath(string path) { IntPtr psi; Guid iid = IShellItemIid; - var hr = Win32PInvoke.SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out psi); + var hr = SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out psi); if ((int)hr < 0) return IntPtr.Zero; return psi; @@ -21,7 +27,7 @@ public static IntPtr IShellItemFromPath(string path) public static IntPtr IStreamFromPath(string path) { IntPtr pstm; - var hr = Win32PInvoke.SHCreateStreamOnFileEx(path, + var hr = SHCreateStreamOnFileEx(path, STGM.STGM_READ | STGM.STGM_FAILIFTHERE | STGM.STGM_SHARE_DENY_NONE, 0, 0, IntPtr.Zero, out pstm); if ((int)hr < 0) diff --git a/src/Files.App/Utils/Shell/PreviewHandler.cs b/src/Files.App/Utils/Shell/PreviewHandler.cs index 6cb5f4001798..710f89c6a1cc 100644 --- a/src/Files.App/Utils/Shell/PreviewHandler.cs +++ b/src/Files.App/Utils/Shell/PreviewHandler.cs @@ -147,6 +147,15 @@ public PreviewHandler(Guid clsid, nint frame) } } + [Flags] + enum ClassContext : uint + { + LocalServer = 0x4 + } + + [DllImport("ole32.dll", CallingConvention = CallingConvention.StdCall)] + static extern HRESULT CoCreateInstance(ref Guid rclsid, IntPtr pUnkOuter, ClassContext dwClsContext, ref Guid riid, out IntPtr ppv); + static readonly Guid IPreviewHandlerIid = Guid.ParseExact("8895b1c6-b41f-4c1c-a562-0d564250836f", "d"); void SetupHandler(Guid clsid) @@ -160,7 +169,7 @@ void SetupHandler(Guid clsid) // If we use Activator.CreateInstance(Type.GetTypeFromCLSID(...)), // CLR will allow in-process server, which defeats isolation and // creates strange bugs. - HRESULT hr = Win32PInvoke.CoCreateInstance(ref clsid, IntPtr.Zero, Win32PInvoke.ClassContext.LocalServer, ref iid, out pph); + HRESULT hr = CoCreateInstance(ref clsid, IntPtr.Zero, ClassContext.LocalServer, ref iid, out pph); // See https://blogs.msdn.microsoft.com/adioltean/2005/06/24/when-cocreateinstance-returns-0x80080005-co_e_server_exec_failure/ // CO_E_SERVER_EXEC_FAILURE also tends to happen when debugging in Visual Studio. // Moreover, to create the instance in a server at low integrity level, we need diff --git a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs index 4764416dfbc6..442efba22bba 100644 --- a/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs +++ b/src/Files.App/Utils/Storage/Enumerators/Win32StorageEnumerator.cs @@ -6,7 +6,7 @@ using System.IO; using Vanara.PInvoke; using Windows.Storage; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; using FileAttributes = System.IO.FileAttributes; namespace Files.App.Utils.Storage @@ -14,15 +14,15 @@ namespace Files.App.Utils.Storage public static class Win32StorageEnumerator { private static readonly ISizeProvider folderSizeProvider = Ioc.Default.GetService(); + private static readonly IStorageCacheService fileListCache = Ioc.Default.GetRequiredService(); private static readonly string folderTypeTextLocalized = "Folder".GetLocalizedResource(); - private static readonly StorageCacheController fileListCache = StorageCacheController.GetInstance(); public static async Task> ListEntries( string path, IntPtr hFile, - Win32Helper.WIN32_FIND_DATA findData, + NativeFindStorageItemHelper.WIN32_FIND_DATA findData, CancellationToken cancellationToken, int countLimit, Func, Task> intermediateAction @@ -108,7 +108,7 @@ Func, Task> intermediateAction private static IEnumerable EnumAdsForPath(string itemPath, ListedItem main) { - foreach (var ads in Win32Helper.GetAlternateStreams(itemPath)) + foreach (var ads in NativeFileOperationsHelper.GetAlternateStreams(itemPath)) yield return GetAlternateStream(ads, main); } @@ -145,7 +145,7 @@ public static ListedItem GetAlternateStream((string Name, long Size) ads, Listed } public static async Task GetFolder( - Win32Helper.WIN32_FIND_DATA findData, + NativeFindStorageItemHelper.WIN32_FIND_DATA findData, string pathRoot, bool isGitRepo, CancellationToken cancellationToken @@ -159,10 +159,10 @@ CancellationToken cancellationToken try { - FileTimeToSystemTime(ref findData.ftLastWriteTime, out Win32Helper.SYSTEMTIME systemModifiedTimeOutput); + FileTimeToSystemTime(ref findData.ftLastWriteTime, out NativeFindStorageItemHelper.SYSTEMTIME systemModifiedTimeOutput); itemModifiedDate = systemModifiedTimeOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftCreationTime, out Win32Helper.SYSTEMTIME systemCreatedTimeOutput); + FileTimeToSystemTime(ref findData.ftCreationTime, out NativeFindStorageItemHelper.SYSTEMTIME systemCreatedTimeOutput); itemCreatedDate = systemCreatedTimeOutput.ToDateTime(); } catch (ArgumentException) @@ -173,7 +173,7 @@ CancellationToken cancellationToken var itemPath = Path.Combine(pathRoot, findData.cFileName); - string itemName = await fileListCache.ReadFileDisplayNameFromCache(itemPath, cancellationToken); + string itemName = await fileListCache.GetDisplayName(itemPath, cancellationToken); if (string.IsNullOrEmpty(itemName)) itemName = findData.cFileName; @@ -222,7 +222,7 @@ CancellationToken cancellationToken } public static async Task GetFile( - Win32Helper.WIN32_FIND_DATA findData, + NativeFindStorageItemHelper.WIN32_FIND_DATA findData, string pathRoot, bool isGitRepo, CancellationToken cancellationToken @@ -235,13 +235,13 @@ CancellationToken cancellationToken try { - FileTimeToSystemTime(ref findData.ftLastWriteTime, out Win32Helper.SYSTEMTIME systemModifiedDateOutput); + FileTimeToSystemTime(ref findData.ftLastWriteTime, out NativeFindStorageItemHelper.SYSTEMTIME systemModifiedDateOutput); itemModifiedDate = systemModifiedDateOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftCreationTime, out Win32Helper.SYSTEMTIME systemCreatedDateOutput); + FileTimeToSystemTime(ref findData.ftCreationTime, out NativeFindStorageItemHelper.SYSTEMTIME systemCreatedDateOutput); itemCreatedDate = systemCreatedDateOutput.ToDateTime(); - FileTimeToSystemTime(ref findData.ftLastAccessTime, out Win32Helper.SYSTEMTIME systemLastAccessOutput); + FileTimeToSystemTime(ref findData.ftLastAccessTime, out NativeFindStorageItemHelper.SYSTEMTIME systemLastAccessOutput); itemLastAccessDate = systemLastAccessOutput.ToDateTime(); } catch (ArgumentException) @@ -272,11 +272,11 @@ CancellationToken cancellationToken // https://learn.microsoft.com/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 bool isReparsePoint = ((FileAttributes)findData.dwFileAttributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint; - bool isSymlink = isReparsePoint && findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK; + bool isSymlink = isReparsePoint && findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK; if (isSymlink) { - var targetPath = Win32Helper.ParseSymLink(itemPath); + var targetPath = NativeFileOperationsHelper.ParseSymLink(itemPath); return new ShortcutItem(null) { diff --git a/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs b/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs index 5c8d5d544f1c..2b98c3a28206 100644 --- a/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/FolderHelpers.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. See the LICENSE. using System.IO; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; namespace Files.App.Utils.Storage { diff --git a/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs b/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs index a29d2c2a24a6..36e7d954f526 100644 --- a/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs +++ b/src/Files.App/Utils/Storage/Helpers/StorageHelpers.cs @@ -32,7 +32,7 @@ public static async Task ToStorageItem(string path) wher } // Fast get attributes - bool exists = Win32Helper.GetFileAttributesExFromApp(path, Win32Helper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out Win32Helper.WIN32_FILE_ATTRIBUTE_DATA itemAttributes); + bool exists = NativeFileOperationsHelper.GetFileAttributesExFromApp(path, NativeFileOperationsHelper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out NativeFileOperationsHelper.WIN32_FILE_ATTRIBUTE_DATA itemAttributes); if (exists) // Exists on local storage { // Directory @@ -160,7 +160,7 @@ public static async Task GetTypeFromPath(string path) public static bool Exists(string path) { - return Win32Helper.GetFileAttributesExFromApp(path, Win32Helper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out _); + return NativeFileOperationsHelper.GetFileAttributesExFromApp(path, NativeFileOperationsHelper.GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, out _); } public static IStorageItemWithPath FromStorageItem(this IStorageItem item, string customPath = null, FilesystemItemType? itemType = null) diff --git a/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs index 5a0e058550f2..b5d6df77a544 100644 --- a/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs +++ b/src/Files.App/Utils/Storage/Operations/FileSizeCalculator.cs @@ -25,7 +25,7 @@ public async Task ComputeSizeAsync(CancellationToken cancellationToken = default await Parallel.ForEachAsync(_paths, cancellationToken, async (path, token) => await Task.Factory.StartNew(() => { var queue = new Queue(); - if (!Win32Helper.HasFileAttribute(path, FileAttributes.Directory)) + if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory)) { ComputeFileSize(path); } @@ -101,7 +101,7 @@ private long ComputeFileSize(string path) public void ForceComputeFileSize(string path) { - if (!Win32Helper.HasFileAttribute(path, FileAttributes.Directory)) + if (!NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory)) { ComputeFileSize(path); } diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs index b0a077f20c5c..439b4bd84823 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemHelpers.cs @@ -23,7 +23,7 @@ public sealed class FilesystemHelpers : IFilesystemHelpers private readonly static StatusCenterViewModel _statusCenterViewModel = Ioc.Default.GetRequiredService(); private IShellPage associatedInstance; - private readonly IJumpListService jumpListService; + private readonly IWindowsJumpListService jumpListService; private ShellFilesystemOperations filesystemOperations; private ItemManipulationModel? itemManipulationModel => associatedInstance.SlimContentPage?.ItemManipulationModel; @@ -56,7 +56,7 @@ public FilesystemHelpers(IShellPage associatedInstance, CancellationToken cancel { this.associatedInstance = associatedInstance; this.cancellationToken = cancellationToken; - jumpListService = Ioc.Default.GetRequiredService(); + jumpListService = Ioc.Default.GetRequiredService(); filesystemOperations = new ShellFilesystemOperations(this.associatedInstance); } public async Task<(ReturnResult, IStorageItem?)> CreateAsync(IStorageItemWithPath source, bool registerHistory) @@ -161,11 +161,9 @@ showDialog is DeleteConfirmationPolicies.PermanentOnly && if (!permanently && registerHistory) App.HistoryWrapper.AddHistory(history); - - var itemsDeleted = history?.Source.Count ?? 0; - - // Remove items from jump list - source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); + + // Execute removal tasks concurrently in background + _ = Task.WhenAll(source.Select(x => jumpListService.RemoveFolderAsync(x.Path))); var itemsCount = banner.TotalItemsCount; @@ -476,8 +474,8 @@ public async Task MoveItemsAsync(IEnumerable App.HistoryWrapper.AddHistory(history); } - // Remove items from jump list - source.ForEach(async x => await jumpListService.RemoveFolderAsync(x.Path)); + // Execute removal tasks concurrently in background + _ = Task.WhenAll(source.Select(x => jumpListService.RemoveFolderAsync(x.Path))); var itemsCount = banner.TotalItemsCount; @@ -823,7 +821,7 @@ public static async Task> GetDraggedStorageIte foreach (var path in itemPaths) { - var isDirectory = Win32Helper.HasFileAttribute(path, FileAttributes.Directory); + var isDirectory = NativeFileOperationsHelper.HasFileAttribute(path, FileAttributes.Directory); itemsList.Add(StorageHelpers.FromPathAndType(path, isDirectory ? FilesystemItemType.Directory : FilesystemItemType.File)); } } diff --git a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs index 4729b7810309..93226587cabf 100644 --- a/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs +++ b/src/Files.App/Utils/Storage/Operations/FilesystemOperations.cs @@ -186,10 +186,10 @@ await DialogDisplayHelper.ShowDialogAsync( if (fsCopyResult) { - if (Win32Helper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) + if (NativeFileOperationsHelper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) { // The source folder was hidden, apply hidden attribute to destination - Win32Helper.SetFileAttribute(fsCopyResult.Result.Path, SystemIO.FileAttributes.Hidden); + NativeFileOperationsHelper.SetFileAttribute(fsCopyResult.Result.Path, SystemIO.FileAttributes.Hidden); } copiedItem = (BaseStorageFolder)fsCopyResult; @@ -403,10 +403,10 @@ await DialogDisplayHelper.ShowDialogAsync( if (fsResultMove) { - if (Win32Helper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) + if (NativeFileOperationsHelper.HasFileAttribute(source.Path, SystemIO.FileAttributes.Hidden)) { // The source folder was hidden, apply hidden attribute to destination - Win32Helper.SetFileAttribute(fsResultMove.Result.Path, SystemIO.FileAttributes.Hidden); + NativeFileOperationsHelper.SetFileAttribute(fsResultMove.Result.Path, SystemIO.FileAttributes.Hidden); } movedItem = (BaseStorageFolder)fsResultMove; diff --git a/src/Files.App/Utils/Storage/Search/FolderSearch.cs b/src/Files.App/Utils/Storage/Search/FolderSearch.cs index ad9713f3d661..229230dfd4be 100644 --- a/src/Files.App/Utils/Storage/Search/FolderSearch.cs +++ b/src/Files.App/Utils/Storage/Search/FolderSearch.cs @@ -6,9 +6,9 @@ using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Search; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; using FileAttributes = System.IO.FileAttributes; -using WIN32_FIND_DATA = Files.App.Helpers.Win32Helper.WIN32_FIND_DATA; +using WIN32_FIND_DATA = Files.App.Helpers.NativeFindStorageItemHelper.WIN32_FIND_DATA; namespace Files.App.Utils.Storage { diff --git a/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs index fcd27323bb5b..5ccf562cda07 100644 --- a/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/NativeStorageFile.cs @@ -28,7 +28,7 @@ public sealed class NativeStorageFile : BaseStorageFile public override string FolderRelativeId => $"0\\{Name}"; public bool IsShortcut => FileExtensionHelpers.IsShortcutOrUrlFile(FileType); - public bool IsAlternateStream => System.Text.RegularExpressions.Regex.IsMatch(Path, @"\w:\w"); + public bool IsAlternateStream => RegexHelpers.AlternateStream().IsMatch(Path); public override string DisplayType { @@ -96,7 +96,7 @@ public override IAsyncOperation CopyAsync(IStorageFolder destin private void CreateFile() { - using var hFile = Win32Helper.CreateFileForWrite(Path, false); + using var hFile = NativeFileOperationsHelper.CreateFileForWrite(Path, false); if (hFile.IsInvalid) { throw new Win32Exception(Marshal.GetLastWin32Error()); @@ -154,14 +154,14 @@ public static IAsyncOperation FromPathAsync(string path) private static bool CheckAccess(string path) { - using var hFile = Win32Helper.OpenFileForRead(path); + using var hFile = NativeFileOperationsHelper.OpenFileForRead(path); return !hFile.IsInvalid; } private static bool IsNativePath(string path) { var isShortcut = FileExtensionHelpers.IsShortcutOrUrlFile(path); - var isAlternateStream = System.Text.RegularExpressions.Regex.IsMatch(path, @"\w:\w"); + var isAlternateStream = RegexHelpers.AlternateStream().IsMatch(path); return isShortcut || isAlternateStream; } @@ -203,7 +203,7 @@ public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string public override IAsyncOperation OpenAsync(FileAccessMode accessMode) { - var hFile = Win32Helper.OpenFileForRead(Path, accessMode == FileAccessMode.ReadWrite); + var hFile = NativeFileOperationsHelper.OpenFileForRead(Path, accessMode == FileAccessMode.ReadWrite); return Task.FromResult(new FileStream(hFile, accessMode == FileAccessMode.ReadWrite ? FileAccess.ReadWrite : FileAccess.Read).AsRandomAccessStream()).AsAsyncOperation(); } @@ -219,7 +219,7 @@ public override IAsyncOperation OpenReadAsyn public override IAsyncOperation OpenSequentialReadAsync() { - var hFile = Win32Helper.OpenFileForRead(Path); + var hFile = NativeFileOperationsHelper.OpenFileForRead(Path); return Task.FromResult(new FileStream(hFile, FileAccess.Read).AsInputStream()).AsAsyncOperation(); } diff --git a/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs index 2d8c9d1ef150..9a812aa205ab 100644 --- a/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/SystemStorageFile.cs @@ -89,7 +89,7 @@ public override IAsyncOperation CopyAsync(IStorageFolder destin if (!string.IsNullOrEmpty(destFolder.Path)) { var destination = IO.Path.Combine(destFolder.Path, desiredNewName); - var hFile = Win32Helper.CreateFileForWrite(destination, + var hFile = NativeFileOperationsHelper.CreateFileForWrite(destination, option == NameCollisionOption.ReplaceExisting); if (!hFile.IsInvalid) { diff --git a/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs b/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs index ab0d6de29e92..237d9703fc34 100644 --- a/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs +++ b/src/Files.App/Utils/Storage/StorageItems/VirtualStorageItem.cs @@ -6,7 +6,7 @@ using Windows.Foundation; using Windows.Storage; using Windows.Storage.FileProperties; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; namespace Files.App.Utils.Storage { @@ -49,7 +49,7 @@ public static VirtualStorageItem FromPath(string path) { // https://learn.microsoft.com/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 bool isReparsePoint = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.ReparsePoint) == System.IO.FileAttributes.ReparsePoint; - bool isSymlink = isReparsePoint && findData.dwReserved0 == Win32Helper.IO_REPARSE_TAG_SYMLINK; + bool isSymlink = isReparsePoint && findData.dwReserved0 == NativeFileOperationsHelper.IO_REPARSE_TAG_SYMLINK; bool isHidden = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.Hidden) == System.IO.FileAttributes.Hidden; bool isDirectory = ((System.IO.FileAttributes)findData.dwFileAttributes & System.IO.FileAttributes.Directory) == System.IO.FileAttributes.Directory; diff --git a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs index 64f9d532ac90..924487dd8c33 100644 --- a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs +++ b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFile.cs @@ -105,7 +105,7 @@ public override IAsyncOperation OpenAsync(FileAccessMode ac return await backingFile.OpenAsync(accessMode); } - var file = Win32Helper.OpenFileForRead(containerPath, rw); + var file = NativeFileOperationsHelper.OpenFileForRead(containerPath, rw); return file.IsInvalid ? null : new FileStream(file, rw ? FileAccess.ReadWrite : FileAccess.Read).AsRandomAccessStream(); } @@ -150,7 +150,7 @@ public override IAsyncOperation OpenReadAsyn return await backingFile.OpenReadAsync(); } - var hFile = Win32Helper.OpenFileForRead(containerPath); + var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath); return hFile.IsInvalid ? null : new StreamWithContentType(new FileStream(hFile, FileAccess.Read).AsRandomAccessStream()); } @@ -189,7 +189,7 @@ public override IAsyncOperation OpenSequentialReadAsync() return await backingFile.OpenSequentialReadAsync(); } - var hFile = Win32Helper.OpenFileForRead(containerPath); + var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath); return hFile.IsInvalid ? null : new FileStream(hFile, FileAccess.Read).AsInputStream(); } @@ -400,7 +400,7 @@ private static bool CheckAccess(string path) { try { - var hFile = Win32Helper.OpenFileForRead(path); + var hFile = NativeFileOperationsHelper.OpenFileForRead(path); if (hFile.IsInvalid) { return false; @@ -475,7 +475,7 @@ private IAsyncOperation OpenZipFileAsync(FileAccessMode accessMode) } else { - var hFile = Win32Helper.OpenFileForRead(containerPath, readWrite); + var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath, readWrite); if (hFile.IsInvalid) { return null; diff --git a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs index 23897ee7d09d..794bbeba7cb0 100644 --- a/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs +++ b/src/Files.App/Utils/Storage/StorageItems/ZipStorageFolder.cs @@ -101,7 +101,7 @@ public static async Task CheckDefaultZipApp(string filePath) { Func> queryFileAssoc = async () => { - var assoc = await Win32Helper.GetFileAssociationAsync(filePath); + var assoc = await NativeWinApiHelper.GetFileAssociationAsync(filePath); if (assoc is not null) { return assoc == Package.Current.Id.FamilyName @@ -489,7 +489,7 @@ private static bool CheckAccess(string path) { return SafetyExtensions.IgnoreExceptions(() => { - var hFile = Win32Helper.OpenFileForRead(path); + var hFile = NativeFileOperationsHelper.OpenFileForRead(path); if (hFile.IsInvalid) { return false; @@ -530,7 +530,7 @@ public static Task InitArchive(string path, OutArchiveFormat format) { return SafetyExtensions.IgnoreExceptions(() => { - var hFile = Win32Helper.OpenFileForRead(path, true); + var hFile = NativeFileOperationsHelper.OpenFileForRead(path, true); if (hFile.IsInvalid) { return Task.FromResult(false); @@ -581,7 +581,7 @@ private IAsyncOperation OpenZipFileAsync(FileAccessMode accessMode) } else { - var hFile = Win32Helper.OpenFileForRead(containerPath, readWrite); + var hFile = NativeFileOperationsHelper.OpenFileForRead(containerPath, readWrite); if (hFile.IsInvalid) { return null; diff --git a/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs b/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs index 0909961f3347..6edfd9a03123 100644 --- a/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/BaseProperties.cs @@ -4,7 +4,7 @@ using Microsoft.UI.Dispatching; using System.IO; using Windows.Storage.FileProperties; -using static Files.App.Helpers.Win32Helper; +using static Files.App.Helpers.NativeFindStorageItemHelper; using FileAttributes = System.IO.FileAttributes; namespace Files.App.ViewModels.Properties @@ -75,7 +75,7 @@ public async Task GetOtherPropertiesAsync(IStorageItemExtraProperties properties if (((FileAttributes)findData.dwFileAttributes & FileAttributes.Directory) != FileAttributes.Directory) { size += findData.GetSize(); - var fileSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Path.Combine(path, findData.cFileName)); + var fileSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Path.Combine(path, findData.cFileName)); sizeOnDisk += fileSizeOnDisk ?? 0; ++count; ViewModel.FilesCount++; diff --git a/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs b/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs index 7dc2590be7a5..ef9849f1b915 100644 --- a/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/CombinedProperties.cs @@ -60,7 +60,7 @@ public override async Task GetSpecialPropertiesAsync() { if (List.All(x => x.PrimaryItemAttribute == StorageItemTypes.File)) { - var fileAttributesReadOnly = List.Select(x => Win32Helper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.ReadOnly)); + var fileAttributesReadOnly = List.Select(x => NativeFileOperationsHelper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.ReadOnly)); if (fileAttributesReadOnly.All(x => x)) ViewModel.IsReadOnly = true; else if (!fileAttributesReadOnly.Any(x => x)) @@ -69,7 +69,7 @@ public override async Task GetSpecialPropertiesAsync() ViewModel.IsReadOnly = null; } - var fileAttributesHidden = List.Select(x => Win32Helper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.Hidden)); + var fileAttributesHidden = List.Select(x => NativeFileOperationsHelper.HasFileAttribute(x.ItemPath, System.IO.FileAttributes.Hidden)); if (fileAttributesHidden.All(x => x)) ViewModel.IsHidden = true; else if (!fileAttributesHidden.Any(x => x)) @@ -89,7 +89,7 @@ public override async Task GetSpecialPropertiesAsync() long totalSizeOnDisk = 0; long filesSizeOnDisk = List.Where(x => x.PrimaryItemAttribute == StorageItemTypes.File && x.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - .Sum(x => Win32Helper.GetFileSizeOnDisk(x.ItemPath) ?? 0); + .Sum(x => NativeFileOperationsHelper.GetFileSizeOnDisk(x.ItemPath) ?? 0); long foldersSizeOnDisk = 0; ViewModel.ItemSizeProgressVisibility = true; @@ -140,12 +140,12 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop { if ((bool)ViewModel.IsReadOnly) { - List.ForEach(x => Win32Helper.SetFileAttribute( + List.ForEach(x => NativeFileOperationsHelper.SetFileAttribute( x.ItemPath, System.IO.FileAttributes.ReadOnly)); } else { - List.ForEach(x => Win32Helper.UnsetFileAttribute( + List.ForEach(x => NativeFileOperationsHelper.UnsetFileAttribute( x.ItemPath, System.IO.FileAttributes.ReadOnly)); } } @@ -158,12 +158,12 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop { if ((bool)ViewModel.IsHidden) { - List.ForEach(x => Win32Helper.SetFileAttribute( + List.ForEach(x => NativeFileOperationsHelper.SetFileAttribute( x.ItemPath, System.IO.FileAttributes.Hidden)); } else { - List.ForEach(x => Win32Helper.UnsetFileAttribute( + List.ForEach(x => NativeFileOperationsHelper.UnsetFileAttribute( x.ItemPath, System.IO.FileAttributes.Hidden)); } } diff --git a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs index faf8cd7de753..defe14c8c572 100644 --- a/src/Files.App/ViewModels/Properties/Items/FileProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FileProperties.cs @@ -44,7 +44,7 @@ public override void GetBaseProperties() ViewModel.LoadCustomIcon = Item.LoadCustomIcon; ViewModel.CustomIconSource = Item.CustomIconSource; ViewModel.LoadFileIcon = Item.LoadFileIcon; - ViewModel.IsDownloadedFile = Win32Helper.ReadStringFromFile($"{Item.ItemPath}:Zone.Identifier") is not null; + ViewModel.IsDownloadedFile = NativeFileOperationsHelper.ReadStringFromFile($"{Item.ItemPath}:Zone.Identifier") is not null; ViewModel.IsEditAlbumCoverVisible = FileExtensionHelpers.IsVideoFile(Item.FileExtension) || FileExtensionHelpers.IsAudioFile(Item.FileExtension); @@ -93,9 +93,9 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( public override async Task GetSpecialPropertiesAsync() { - ViewModel.IsReadOnly = Win32Helper.HasFileAttribute( + ViewModel.IsReadOnly = NativeFileOperationsHelper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.ReadOnly); - ViewModel.IsHidden = Win32Helper.HasFileAttribute( + ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.Hidden); ViewModel.ItemSizeVisibility = true; @@ -103,7 +103,7 @@ public override async Task GetSpecialPropertiesAsync() // Only load the size for items on the device if (Item.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - ViewModel.ItemSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? + ViewModel.ItemSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? string.Empty; var result = await FileThumbnailHelper.GetIconAsync( @@ -282,9 +282,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsReadOnly is not null) { if ((bool)ViewModel.IsReadOnly) - Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); else - Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.ReadOnly); } break; @@ -293,9 +293,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); else - Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); } break; diff --git a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs index 6de0ad95b165..52f616258502 100644 --- a/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/FolderProperties.cs @@ -71,7 +71,7 @@ await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync( public async override Task GetSpecialPropertiesAsync() { - ViewModel.IsHidden = Win32Helper.HasFileAttribute( + ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute( Item.ItemPath, System.IO.FileAttributes.Hidden); var result = await FileThumbnailHelper.GetIconAsync( @@ -94,7 +94,7 @@ public async override Task GetSpecialPropertiesAsync() // Only load the size for items on the device if (Item.SyncStatusUI.SyncStatus is not CloudDriveSyncStatus.FileOnline and not CloudDriveSyncStatus.FolderOnline) - ViewModel.ItemSizeOnDisk = Win32Helper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? + ViewModel.ItemSizeOnDisk = NativeFileOperationsHelper.GetFileSizeOnDisk(Item.ItemPath)?.ToLongSizeString() ?? string.Empty; ViewModel.ItemCreatedTimestampReal = Item.ItemDateCreatedReal; @@ -203,9 +203,9 @@ private async void ViewModel_PropertyChanged(object sender, System.ComponentMode if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - Win32Helper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.SetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); else - Win32Helper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.UnsetFileAttribute(Item.ItemPath, System.IO.FileAttributes.Hidden); } break; diff --git a/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs b/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs index 0ae2cb2e26de..7871789bb773 100644 --- a/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs +++ b/src/Files.App/ViewModels/Properties/Items/LibraryProperties.cs @@ -46,8 +46,8 @@ public override void GetBaseProperties() public async override Task GetSpecialPropertiesAsync() { - ViewModel.IsReadOnly = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); - ViewModel.IsHidden = Win32Helper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + ViewModel.IsReadOnly = NativeFileOperationsHelper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + ViewModel.IsHidden = NativeFileOperationsHelper.HasFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); var result = await FileThumbnailHelper.GetIconAsync( Library.ItemPath, @@ -144,9 +144,9 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop if (ViewModel.IsReadOnly is not null) { if ((bool)ViewModel.IsReadOnly) - Win32Helper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); else - Win32Helper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); + NativeFileOperationsHelper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.ReadOnly); } break; @@ -155,9 +155,9 @@ private void ViewModel_PropertyChanged(object sender, System.ComponentModel.Prop if (ViewModel.IsHidden is not null) { if ((bool)ViewModel.IsHidden) - Win32Helper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.SetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); else - Win32Helper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); + NativeFileOperationsHelper.UnsetFileAttribute(Library.ItemPath, System.IO.FileAttributes.Hidden); } break; From 943b5c774458eafc57d6e4f060707be4cd46810b Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:55:22 +0900 Subject: [PATCH 08/14] Format --- .../Services/WindowsQuickAccessService.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index 3bcfd986d46d..cf3c1564ddff 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -60,9 +60,17 @@ public WindowsQuickAccessService() /// public async Task InitializeAsync() { - PinnedItemsModified += LoadAsync; + PinnedItemsModified += async (s, e) => + { + _quickAccessFolderWatcher.EnableRaisingEvents = false; + + await UpdateItemsWithExplorerAsync(); + UpdateQuickAccessWidget?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); + + _quickAccessFolderWatcher.EnableRaisingEvents = true; + }; - //if (!Model.PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) + //if (!PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) // await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); await UpdateItemsWithExplorerAsync(); @@ -120,6 +128,7 @@ await SafetyExtensions.IgnoreExceptions(async () => { await fi.InvokeVerb("unpinfromhome"); }); + continue; } } @@ -159,10 +168,7 @@ public async Task NotifyPinnedItemsChangesAsync(string[] items) await PinToSidebarAsync(items, false); _quickAccessFolderWatcher.EnableRaisingEvents = true; - UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(items, true) - { - Reorder = true - }); + UpdateQuickAccessWidget?.Invoke(this, new(items, true) { Reorder = true }); } /// @@ -294,18 +300,6 @@ public void RemoveStaleSidebarItems() DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Reset)); } - /// - public async void LoadAsync(object? sender, SystemIO.FileSystemEventArgs e) - { - _quickAccessFolderWatcher.EnableRaisingEvents = false; - - await UpdateItemsWithExplorerAsync(); - - UpdateQuickAccessWidget?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); - - _quickAccessFolderWatcher.EnableRaisingEvents = true; - } - private void AddLocationItemToSidebar(LocationItem locationItem) { int insertIndex = -1; From d35c77a9b6182f9c0a526ad7c9601c9106460be9 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 00:47:02 +0900 Subject: [PATCH 09/14] Update --- src/Files.App/Services/WindowsQuickAccessService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index cf3c1564ddff..dd0c82ba2937 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -1,6 +1,7 @@ // Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. +using CommunityToolkit.WinUI.Helpers; using System.Collections.Specialized; namespace Files.App.Services @@ -70,8 +71,8 @@ public async Task InitializeAsync() _quickAccessFolderWatcher.EnableRaisingEvents = true; }; - //if (!PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) - // await QuickAccessService.PinToSidebar(Constants.UserEnvironmentPaths.RecycleBinPath); + if (!PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) + await PinToSidebarAsync([Constants.UserEnvironmentPaths.RecycleBinPath]); await UpdateItemsWithExplorerAsync(); } From bdf0fe6a72927b2f640b31d8fb700eb25daa5d4b Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 01:33:34 +0900 Subject: [PATCH 10/14] Update --- .../Sidebar/PinFolderToSidebarAction.cs | 8 +- .../Sidebar/UnpinFolderToSidebarAction.cs | 8 +- .../Data/Contexts/SideBar/SideBarContext.cs | 2 +- .../Contracts/IWindowsQuickAccessService.cs | 39 +-- src/Files.App/Data/Items/DriveItem.cs | 2 +- src/Files.App/Data/Items/ListedItem.cs | 2 +- src/Files.App/Data/Items/LocationItem.cs | 65 ++++- .../Services/WindowsJumpListService.cs | 6 +- .../Services/WindowsQuickAccessService.cs | 262 +++++++----------- .../Widgets/FileTagsWidget.xaml.cs | 2 +- .../Widgets/QuickAccessWidget.xaml.cs | 14 +- .../Widgets/RecentFilesWidget.xaml.cs | 2 +- .../ReorderSidebarItemsDialogViewModel.cs | 2 +- .../UserControls/SidebarViewModel.cs | 16 +- .../Widgets/BaseWidgetViewModel.cs | 6 +- .../Views/Layouts/ColumnsLayoutPage.xaml.cs | 2 +- 16 files changed, 206 insertions(+), 232 deletions(-) diff --git a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs index 78d5855d485d..f85fdde31e6b 100644 --- a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs @@ -25,7 +25,7 @@ public bool IsExecutable public PinFolderToSidebarAction() { context.PropertyChanged += Context_PropertyChanged; - QuickAccessService.UpdateQuickAccessWidget += QuickAccessManager_DataChanged; + QuickAccessService.PinnedItemsChanged += QuickAccessManager_DataChanged; } public async Task ExecuteAsync() @@ -34,17 +34,17 @@ public async Task ExecuteAsync() { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await QuickAccessService.PinToSidebarAsync(items); + await QuickAccessService.PinFolderToSidebarAsync(items); } else if (context.Folder is not null) { - await QuickAccessService.PinToSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.PinFolderToSidebarAsync([context.Folder.ItemPath]); } } private bool GetIsExecutable() { - string[] pinnedFolders = [.. QuickAccessService.PinnedFolders]; + string[] pinnedFolders = [.. QuickAccessService.PinnedFolderPaths]; return context.HasSelection ? context.SelectedItems.All(IsPinnable) diff --git a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs index b25e4a9895c4..f19319f9dab4 100644 --- a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs @@ -23,7 +23,7 @@ public bool IsExecutable public UnpinFolderFromSidebarAction() { context.PropertyChanged += Context_PropertyChanged; - QuickAccessService.UpdateQuickAccessWidget += QuickAccessService_DataChanged; + QuickAccessService.PinnedItemsChanged += QuickAccessService_DataChanged; } public async Task ExecuteAsync() @@ -31,17 +31,17 @@ public async Task ExecuteAsync() if (context.HasSelection) { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await QuickAccessService.UnpinFromSidebarAsync(items); + await QuickAccessService.UnpinFolderFromSidebarAsync(items); } else if (context.Folder is not null) { - await QuickAccessService.UnpinFromSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.UnpinFolderFromSidebarAsync([context.Folder.ItemPath]); } } private bool GetIsExecutable() { - string[] pinnedFolders = [.. QuickAccessService.PinnedFolders]; + string[] pinnedFolders = [.. QuickAccessService.PinnedFolderPaths]; return context.HasSelection ? context.SelectedItems.All(IsPinned) diff --git a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs index 51758ad43c4a..db84b2db6d6d 100644 --- a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs +++ b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs @@ -10,7 +10,7 @@ internal sealed class SidebarContext : ObservableObject, ISidebarContext private int PinnedFolderItemIndex => IsItemRightClicked - ? QuickAccessService.IndexOfItem(_RightClickedItem!) + ? QuickAccessService.IndexOfItem(_RightClickedItem!.Path) : -1; private INavigationControlItem? _RightClickedItem = null; diff --git a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs index ba70e9e9a273..98916eaa4b2d 100644 --- a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs @@ -9,11 +9,9 @@ public interface IWindowsQuickAccessService { event EventHandler? DataChanged; - event SystemIO.FileSystemEventHandler? PinnedItemsModified; + event EventHandler? PinnedItemsChanged; - event EventHandler? UpdateQuickAccessWidget; - - List PinnedFolders { get; } + List PinnedFolderPaths { get; } IReadOnlyList PinnedFolderItems { get; } @@ -30,63 +28,44 @@ public interface IWindowsQuickAccessService /// /// The array of folders to pin /// - Task PinToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); + Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Unpins folders from the quick access list /// /// The array of folders to unpin /// - Task UnpinFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); + Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Checks if a folder is pinned to the quick access list /// /// The path of the folder /// true if the item is pinned - bool IsPinnedToSidebar(string folderPath); + bool IsPinnedFolder(string folderPath); /// /// Saves a state of pinned folder items in the sidebar /// /// The array of items to save /// - Task NotifyPinnedItemsChangesAsync(string[] items); + Task RefreshPinnedFolders(string[] items); /// /// Updates items with the pinned items from the explorer sidebar /// - Task UpdateItemsWithExplorerAsync(); + Task UpdatePinnedFolders(); /// /// Returns the index of the location item in the navigation sidebar /// /// The location item /// Index of the item - int IndexOfItem(INavigationControlItem locationItem); - - /// - /// CreateLocationItemFromPathAsync - /// - /// - /// - Task CreateLocationItemFromPathAsync(string path); - - /// - /// Adds the item (from a path) to the navigation sidebar - /// - /// The path which to save - /// Task - Task AddItemToSidebarAsync(string path); + int IndexOf(string path); /// /// Adds all items to the navigation sidebar /// - Task AddAllItemsToSidebarAsync(); - - /// - /// Removes stale items in the navigation sidebar - /// - void RemoveStaleSidebarItems(); + Task SyncPinnedItemsAsync(); } } diff --git a/src/Files.App/Data/Items/DriveItem.cs b/src/Files.App/Data/Items/DriveItem.cs index b4118393fb82..41837794313e 100644 --- a/src/Files.App/Data/Items/DriveItem.cs +++ b/src/Files.App/Data/Items/DriveItem.cs @@ -50,7 +50,7 @@ public bool IsNetwork => Type == DriveType.Network; public bool IsPinned - => QuickAccessService.PinnedFolders.Contains(path); + => QuickAccessService.PinnedFolderPaths.Contains(path); public string MaxSpaceText => MaxSpace.ToSizeString(); diff --git a/src/Files.App/Data/Items/ListedItem.cs b/src/Files.App/Data/Items/ListedItem.cs index 9759efd50653..5832f53afdee 100644 --- a/src/Files.App/Data/Items/ListedItem.cs +++ b/src/Files.App/Data/Items/ListedItem.cs @@ -375,7 +375,7 @@ public override string ToString() public bool IsGitItem => this is GitItem; public virtual bool IsExecutable => FileExtensionHelpers.IsExecutableFile(ItemPath); public virtual bool IsScriptFile => FileExtensionHelpers.IsScriptFile(ItemPath); - public bool IsPinned => QuickAccessService.PinnedFolders.Contains(itemPath); + public bool IsPinned => QuickAccessService.PinnedFolderPaths.Contains(itemPath); public bool IsDriveRoot => ItemPath == PathNormalization.GetPathRoot(ItemPath); public bool IsElevationRequired { get; set; } diff --git a/src/Files.App/Data/Items/LocationItem.cs b/src/Files.App/Data/Items/LocationItem.cs index 760cdd24fc25..de47816523ed 100644 --- a/src/Files.App/Data/Items/LocationItem.cs +++ b/src/Files.App/Data/Items/LocationItem.cs @@ -81,7 +81,7 @@ public bool IsExpanded public bool IsInvalid { get; set; } = false; - public bool IsPinned => QuickAccessService.PinnedFolders.Contains(path); + public bool IsPinned => QuickAccessService.PinnedFolderPaths.Contains(path); public SectionType Section { get; set; } @@ -121,6 +121,69 @@ public int CompareTo(INavigationControlItem other) { return new T(); } + + public static async Task CreateLocationItemFromPathAsync(string path) + { + var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(path)); + var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, item)); + LocationItem locationItem; + + if (string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase)) + { + locationItem = LocationItem.Create(); + } + else + { + locationItem = LocationItem.Create(); + + if (path.Equals(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase)) + locationItem.Text = "ThisPC".GetLocalizedResource(); + else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) + locationItem.Text = "Network".GetLocalizedResource(); + } + + locationItem.Path = path; + locationItem.Section = SectionType.Pinned; + locationItem.MenuOptions = new ContextMenuOptions + { + IsLocationItem = true, + ShowProperties = true, + ShowUnpinItem = true, + ShowShellItems = true, + ShowEmptyRecycleBin = string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) + }; + locationItem.IsDefaultLocation = false; + locationItem.Text = res.Result?.DisplayName ?? SystemIO.Path.GetFileName(path.TrimEnd('\\')); + + if (res) + { + locationItem.IsInvalid = false; + + if (res && res.Result is not null) + { + var result = await FileThumbnailHelper.GetIconAsync( + res.Result.Path, + Constants.ShellIconSizes.Small, + true, + IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); + + locationItem.IconData = result; + + var bitmapImage = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal); + if (bitmapImage is not null) + locationItem.Icon = bitmapImage; + } + } + else + { + locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); + locationItem.IsInvalid = true; + + Debug.WriteLine($"Pinned item was invalid {res.ErrorCode}, item: {path}"); + } + + return locationItem; + } } public sealed class RecycleBinLocationItem : LocationItem diff --git a/src/Files.App/Services/WindowsJumpListService.cs b/src/Files.App/Services/WindowsJumpListService.cs index 277d7aee74df..de562fc825b3 100644 --- a/src/Files.App/Services/WindowsJumpListService.cs +++ b/src/Files.App/Services/WindowsJumpListService.cs @@ -19,8 +19,8 @@ public async Task InitializeAsync() { try { - QuickAccessService.UpdateQuickAccessWidget -= UpdateQuickAccessWidget_Invoked; - QuickAccessService.UpdateQuickAccessWidget += UpdateQuickAccessWidget_Invoked; + QuickAccessService.PinnedItemsChanged -= UpdateQuickAccessWidget_Invoked; + QuickAccessService.PinnedItemsChanged += UpdateQuickAccessWidget_Invoked; await RefreshPinnedFoldersAsync(); } @@ -90,7 +90,7 @@ public async Task RefreshPinnedFoldersAsync() var itemsToRemove = instance.Items.Where(x => string.Equals(x.GroupName, JumpListPinnedGroupHeader, StringComparison.OrdinalIgnoreCase)).ToList(); itemsToRemove.ForEach(x => instance.Items.Remove(x)); - QuickAccessService.PinnedFolders.ForEach(x => AddFolder(x, JumpListPinnedGroupHeader, instance)); + QuickAccessService.PinnedFolderPaths.ForEach(x => AddFolder(x, JumpListPinnedGroupHeader, instance)); await instance.SaveAsync(); } } diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index dd0c82ba2937..28482f9639d3 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -8,6 +8,16 @@ namespace Files.App.Services { internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService { + private static readonly string ShellContextMenuVerbPinToHome = "PinToHome"; + + private static readonly string ShellContextMenuVerbUnpinToHome = "UnpinFromHome"; + + private static readonly string ShellPropertyIsPinned = "System.Home.IsPinned"; + + private static readonly string ShellMemberNameSpace = "NameSpace"; + + private static readonly string ShellProgIDApplication = "Shell.Application"; + // Dependency injections private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); @@ -21,7 +31,7 @@ internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService // Properties /// - public List PinnedFolders { get; set; } = []; + public List PinnedFolderPaths { get; set; } = []; private readonly List _PinnedFolderItems = []; /// @@ -40,10 +50,7 @@ public IReadOnlyList PinnedFolderItems public event EventHandler? DataChanged; /// - public event SystemIO.FileSystemEventHandler? PinnedItemsModified; - - /// - public event EventHandler? UpdateQuickAccessWidget; + public event EventHandler? PinnedItemsChanged; public WindowsQuickAccessService() { @@ -52,140 +59,165 @@ public WindowsQuickAccessService() Path = SystemIO.Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft", "Windows", "Recent", "AutomaticDestinations"), Filter = "f01b4d95cf55d32a.automaticDestinations-ms", NotifyFilter = SystemIO.NotifyFilters.LastAccess | SystemIO.NotifyFilters.LastWrite | SystemIO.NotifyFilters.FileName, - EnableRaisingEvents = true + EnableRaisingEvents = true, }; - _quickAccessFolderWatcher.Changed += PinnedItemsWatcher_Changed; - } - - /// - public async Task InitializeAsync() - { - PinnedItemsModified += async (s, e) => + _quickAccessFolderWatcher.Changed += async (s, e) => { _quickAccessFolderWatcher.EnableRaisingEvents = false; - await UpdateItemsWithExplorerAsync(); - UpdateQuickAccessWidget?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); + await UpdatePinnedFolders(); + PinnedItemsChanged?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); _quickAccessFolderWatcher.EnableRaisingEvents = true; }; + } - if (!PinnedFolders.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) - await PinToSidebarAsync([Constants.UserEnvironmentPaths.RecycleBinPath]); + /// + public async Task InitializeAsync() + { + // Pin RecycleBin folder + if (!PinnedFolderPaths.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) + await PinFolderToSidebarAsync([Constants.UserEnvironmentPaths.RecycleBinPath]); - await UpdateItemsWithExplorerAsync(); + // Refresh + await UpdatePinnedFolders(); } /// public async Task> GetPinnedFoldersAsync() { + // TODO: Return IAsyncEnumerable, instead return (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")) .Enumerate .Where(link => link.IsFolder); } /// - public async Task PinToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) + public async Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { foreach (string folderPath in folderPaths) - await ContextMenu.InvokeVerb("pintohome", [folderPath]); + await ContextMenu.InvokeVerb(ShellContextMenuVerbPinToHome, [folderPath]); - await UpdateItemsWithExplorerAsync(); + await UpdatePinnedFolders(); if (invokeQuickAccessChangedEvent) - UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); + PinnedItemsChanged?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, true)); } /// - public async Task UnpinFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) + public async Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { - var shellAppType = Type.GetTypeFromProgID("Shell.Application")!; + // Get the shell application Program ID + var shellAppType = Type.GetTypeFromProgID(ShellProgIDApplication)!; + // Create shell instance var shell = Activator.CreateInstance(shellAppType); - dynamic? f2 = shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, [$"shell:{Constants.CLID.QuickAccess}"]); + // Get the QuickAccess shell folder contents + dynamic? shellItems = + shellAppType.InvokeMember( + ShellMemberNameSpace, + System.Reflection.BindingFlags.InvokeMethod, + null, + shell, + [$"Shell:{Constants.CLID.QuickAccess}"]); - if (f2 is null) + if (shellItems is null) return; if (folderPaths.Length == 0) + { folderPaths = (await GetPinnedFoldersAsync()) - .Where(link => (bool?)link.Properties["System.Home.IsPinned"] ?? false) + .Where(link => (bool?)link.Properties[ShellPropertyIsPinned] ?? false) .Select(link => link.FilePath).ToArray(); + } - foreach (var fi in f2.Items()) + foreach (var shellItem in shellItems.Items()) { - if (ShellStorageFolder.IsShellPath((string)fi.Path)) + if (ShellStorageFolder.IsShellPath((string)shellItem.Path)) { - var folder = await ShellStorageFolder.FromPathAsync((string)fi.Path); - var path = folder?.Path; + //var folder = await ShellStorageFolder.FromPathAsync((string)shellItem.Path); + var path = (string)shellItem.Path; - // Fix for the Linux header - if (path is not null && + if (path is not null && (folderPaths.Contains(path) || (path.StartsWith(@"\\SHELL\") && folderPaths.Any(x => x.StartsWith(@"\\SHELL\"))))) { + // Unpin await SafetyExtensions.IgnoreExceptions(async () => { - await fi.InvokeVerb("unpinfromhome"); + await shellItem.InvokeVerb(ShellContextMenuVerbUnpinToHome); }); continue; } } - if (folderPaths.Contains((string)fi.Path)) + if (folderPaths.Contains((string)shellItem.Path)) { + // Unpin await SafetyExtensions.IgnoreExceptions(async () => { - await fi.InvokeVerb("unpinfromhome"); + await shellItem.InvokeVerb(ShellContextMenuVerbUnpinToHome); }); } } - await UpdateItemsWithExplorerAsync(); + await UpdatePinnedFolders(); if (invokeQuickAccessChangedEvent) - UpdateQuickAccessWidget?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, false)); + PinnedItemsChanged?.Invoke(this, new ModifyQuickAccessEventArgs(folderPaths, false)); } /// - public bool IsPinnedToSidebar(string folderPath) + public bool IsPinnedFolder(string folderPath) { - return PinnedFolders.Contains(folderPath); + return PinnedFolderPaths.Contains(folderPath); } /// - public async Task NotifyPinnedItemsChangesAsync(string[] items) + public async Task RefreshPinnedFolders(string[] items) { - if (Equals(items, PinnedFolders.ToArray())) + if (Equals(items, PinnedFolderPaths.ToArray())) return; _quickAccessFolderWatcher.EnableRaisingEvents = false; - // Unpin every item that is below this index and then pin them all in order - await UnpinFromSidebarAsync([], false); + // Unpin every item and pin new items in order + await UnpinFolderFromSidebarAsync([], false); + await PinFolderToSidebarAsync(items, false); - await PinToSidebarAsync(items, false); _quickAccessFolderWatcher.EnableRaisingEvents = true; - UpdateQuickAccessWidget?.Invoke(this, new(items, true) { Reorder = true }); + PinnedItemsChanged?.Invoke(this, new(items, true) { Reorder = true }); } /// - public async Task UpdateItemsWithExplorerAsync() + public async Task UpdatePinnedFolders() { await _addSyncSemaphore.WaitAsync(); try { - PinnedFolders = (await GetPinnedFoldersAsync()) - .Where(link => (bool?)link.Properties["System.Home.IsPinned"] ?? false) - .Select(link => link.FilePath).ToList(); + PinnedFolderPaths = (await GetPinnedFoldersAsync()) + .Where(x => (bool?)x.Properties[ShellPropertyIsPinned] ?? false) + .Select(x => x.FilePath).ToList(); - RemoveStaleSidebarItems(); + // Sync pinned items + foreach (var childItem in PinnedFolderItems) + { + if (childItem is LocationItem item && !item.IsDefaultLocation && !PinnedFolderPaths.Contains(item.Path)) + { + lock (_PinnedFolderItems) + _PinnedFolderItems.Remove(item); + + DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Remove, item)); + } + } - await AddAllItemsToSidebarAsync(); + DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Reset)); + + await SyncPinnedItemsAsync(); } finally { @@ -194,135 +226,35 @@ public async Task UpdateItemsWithExplorerAsync() } /// - public int IndexOfItem(INavigationControlItem locationItem) + public int IndexOf(string path) { lock (_PinnedFolderItems) - return _PinnedFolderItems.FindIndex(x => x.Path == locationItem.Path); + return _PinnedFolderItems.FindIndex(x => x.Path == path); } /// - public async Task CreateLocationItemFromPathAsync(string path) + public async Task SyncPinnedItemsAsync() { - var item = await FilesystemTasks.Wrap(() => DriveHelpers.GetRootFromPathAsync(path)); - var res = await FilesystemTasks.Wrap(() => StorageFileExtensions.DangerousGetFolderFromPathAsync(path, item)); - LocationItem locationItem; - - if (string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase)) - { - locationItem = LocationItem.Create(); - } - else + foreach (string path in PinnedFolderPaths) { - locationItem = LocationItem.Create(); + var locationItem = await LocationItem.CreateLocationItemFromPathAsync(path); - if (path.Equals(Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase)) - locationItem.Text = "ThisPC".GetLocalizedResource(); - else if (path.Equals(Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase)) - locationItem.Text = "Network".GetLocalizedResource(); - } + int insertIndex = -1; - locationItem.Path = path; - locationItem.Section = SectionType.Pinned; - locationItem.MenuOptions = new ContextMenuOptions - { - IsLocationItem = true, - ShowProperties = true, - ShowUnpinItem = true, - ShowShellItems = true, - ShowEmptyRecycleBin = string.Equals(path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) - }; - locationItem.IsDefaultLocation = false; - locationItem.Text = res.Result?.DisplayName ?? SystemIO.Path.GetFileName(path.TrimEnd('\\')); - - if (res) - { - locationItem.IsInvalid = false; - - if (res && res.Result is not null) + lock (_PinnedFolderItems) { - var result = await FileThumbnailHelper.GetIconAsync( - res.Result.Path, - Constants.ShellIconSizes.Small, - true, - IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); + if (_PinnedFolderItems.Any(x => x.Path == locationItem.Path)) + return; - locationItem.IconData = result; + var lastItem = _PinnedFolderItems.LastOrDefault(x => x.ItemType is NavigationControlItemType.Location); - var bitmapImage = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync(), Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal); - if (bitmapImage is not null) - locationItem.Icon = bitmapImage; - } - } - else - { - locationItem.Icon = await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => UIHelpers.GetSidebarIconResource(Constants.ImageRes.Folder)); - locationItem.IsInvalid = true; - - Debug.WriteLine($"Pinned item was invalid {res.ErrorCode}, item: {path}"); - } - - return locationItem; - } - - /// - public async Task AddItemToSidebarAsync(string path) - { - var locationItem = await CreateLocationItemFromPathAsync(path); - - AddLocationItemToSidebar(locationItem); - } + insertIndex = lastItem is not null ? _PinnedFolderItems.IndexOf(lastItem) + 1 : 0; - /// - public async Task AddAllItemsToSidebarAsync() - { - if (UserSettingsService.GeneralSettingsService.ShowPinnedSection) - { - foreach (string path in PinnedFolders) - await AddItemToSidebarAsync(path); - } - } - - /// - public void RemoveStaleSidebarItems() - { - // Remove unpinned items from PinnedFolderItems - foreach (var childItem in PinnedFolderItems) - { - if (childItem is LocationItem item && !item.IsDefaultLocation && !PinnedFolders.Contains(item.Path)) - { - lock (_PinnedFolderItems) - _PinnedFolderItems.Remove(item); - - DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Remove, item)); + _PinnedFolderItems.Insert(insertIndex, locationItem); } - } - // Remove unpinned items from sidebar - DataChanged?.Invoke(SectionType.Pinned, new(NotifyCollectionChangedAction.Reset)); - } - - private void AddLocationItemToSidebar(LocationItem locationItem) - { - int insertIndex = -1; - - lock (_PinnedFolderItems) - { - if (_PinnedFolderItems.Any(x => x.Path == locationItem.Path)) - return; - - var lastItem = _PinnedFolderItems.LastOrDefault(x => x.ItemType is NavigationControlItemType.Location); - - insertIndex = lastItem is not null ? _PinnedFolderItems.IndexOf(lastItem) + 1 : 0; - - _PinnedFolderItems.Insert(insertIndex, locationItem); + DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, locationItem, insertIndex)); } - - DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, locationItem, insertIndex)); - } - - private void PinnedItemsWatcher_Changed(object sender, SystemIO.FileSystemEventArgs e) - { - PinnedItemsModified?.Invoke(this, e); } } } diff --git a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs index 8c68c63db2aa..579c0a5a140f 100644 --- a/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs @@ -121,7 +121,7 @@ private void AdaptiveGridView_RightTapped(object sender, RightTappedRoutedEventA OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path), item.IsFolder); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedFolder(item.Path), item.IsFolder); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index 8763da4a5fde..5d0c34ba47fc 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -170,7 +170,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in itemsToAdd) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); + var item = await LocationItem.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); var isPinned = (bool?)e.Items.Where(x => x.FilePath == itemToAdd).FirstOrDefault()?.Properties["System.Home.IsPinned"] ?? false; if (interimItemsAdded.Any(x => x.Path == itemToAdd)) @@ -194,7 +194,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in e.Paths) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); + var item = await LocationItem.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); if (interimItemsAdded.Any(x => x.Path == itemToAdd)) continue; @@ -212,7 +212,7 @@ await DispatcherQueue.EnqueueOrInvokeAsync(async () => foreach (var itemToAdd in e.Paths) { var interimItemsAdded = ItemsAdded.ToList(); - var item = await QuickAccessService.CreateLocationItemFromPathAsync(itemToAdd); + var item = await LocationItem.CreateLocationItemFromPathAsync(itemToAdd); var lastIndex = ItemsAdded.IndexOf(interimItemsAdded.FirstOrDefault(x => !x.IsPinned)); if (interimItemsAdded.Any(x => x.Path == itemToAdd)) continue; @@ -238,13 +238,13 @@ private async void QuickAccessWidget_Loaded(object sender, RoutedEventArgs e) Reset = true }); - QuickAccessService.UpdateQuickAccessWidget += ModifyItemAsync; + QuickAccessService.PinnedItemsChanged += ModifyItemAsync; } private void QuickAccessWidget_Unloaded(object sender, RoutedEventArgs e) { Unloaded -= QuickAccessWidget_Unloaded; - QuickAccessService.UpdateQuickAccessWidget -= ModifyItemAsync; + QuickAccessService.PinnedItemsChanged -= ModifyItemAsync; } private static async void ItemsAdded_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -305,7 +305,7 @@ private void OpenProperties(WidgetFolderCardItem item) public override async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinToSidebarAsync([item.Path]); + await QuickAccessService.PinFolderToSidebarAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); @@ -324,7 +324,7 @@ public override async Task PinToSidebarAsync(WidgetCardItem item) public override async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFromSidebarAsync([item.Path]); + await QuickAccessService.UnpinFolderFromSidebarAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); } diff --git a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs index 83040030af7f..ea9ffe70c2eb 100644 --- a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -129,7 +129,7 @@ private void ListView_RightTapped(object sender, RightTappedRoutedEventArgs e) OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path)); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedFolder(item.Path)); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout diff --git a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs index 3863cb05a124..e3889de0b471 100644 --- a/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs +++ b/src/Files.App/ViewModels/Dialogs/ReorderSidebarItemsDialogViewModel.cs @@ -27,7 +27,7 @@ public ReorderSidebarItemsDialogViewModel() public void SaveChanges() { - quickAccessService.NotifyPinnedItemsChangesAsync(SidebarPinnedFolderItems.Select(x => x.Path).ToArray()); + quickAccessService.RefreshPinnedFolders(SidebarPinnedFolderItems.Select(x => x.Path).ToArray()); } } } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 27f11be709c7..9d18d2036a24 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -580,7 +580,7 @@ public async Task UpdateSectionVisibilityAsync(SectionType sectionType, bool sho SectionType.WSL when generalSettingsService.ShowWslSection => WSLDistroManager.UpdateDrivesAsync, SectionType.FileTag when generalSettingsService.ShowFileTagsSection => App.FileTagsManager.UpdateFileTagsAsync, SectionType.Library => App.LibraryManager.UpdateLibrariesAsync, - SectionType.Pinned => QuickAccessService.AddAllItemsToSidebarAsync, + SectionType.Pinned => QuickAccessService.SyncPinnedItemsAsync, _ => () => Task.CompletedTask }; @@ -841,12 +841,12 @@ private async Task OpenInNewWindowAsync() private void PinItem() { if (rightClickedItem is DriveItem) - _ = QuickAccessService.PinToSidebarAsync(new[] { rightClickedItem.Path }); + _ = QuickAccessService.PinFolderToSidebarAsync(new[] { rightClickedItem.Path }); } private void UnpinItem() { if (rightClickedItem.Section == SectionType.Pinned || rightClickedItem is DriveItem) - _ = QuickAccessService.UnpinFromSidebarAsync([rightClickedItem.Path]); + _ = QuickAccessService.UnpinFolderFromSidebarAsync([rightClickedItem.Path]); } private void HideSection() @@ -935,8 +935,8 @@ private List GetLocationItemMenuItems(INavigatio { var options = item.MenuOptions; - var pinnedFolderIndex = QuickAccessService.IndexOfItem(item); - var pinnedFolderCount = QuickAccessService.PinnedFolders.Count; + var pinnedFolderIndex = QuickAccessService.IndexOfItem(item.Path); + var pinnedFolderCount = QuickAccessService.PinnedFolderPaths.Count; var isPinnedItem = item.Section is SectionType.Pinned && pinnedFolderIndex is not -1; var showMoveItemUp = isPinnedItem && pinnedFolderIndex > 0; @@ -1096,7 +1096,7 @@ private async Task HandleLocationItemDragOverAsync(LocationItem locationItem, It if (isPathNull && hasStorageItems && SectionType.Pinned.Equals(locationItem.Section)) { - var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolders.Contains(item.Path)); + var haveFoldersToPin = storageItems.Any(item => item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolderPaths.Contains(item.Path)); if (!haveFoldersToPin) { @@ -1261,8 +1261,8 @@ private async Task HandleLocationItemDroppedAsync(LocationItem locationItem, Ite var storageItems = await Utils.Storage.FilesystemHelpers.GetDraggedStorageItems(args.DroppedItem); foreach (var item in storageItems) { - if (item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolders.Contains(item.Path)) - await QuickAccessService.PinToSidebarAsync([item.Path]); + if (item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolderPaths.Contains(item.Path)) + await QuickAccessService.PinFolderToSidebarAsync([item.Path]); } } else diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 8c463b2955e6..7f4df13444a0 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -69,7 +69,7 @@ public void Button_RightTapped(object sender, RightTappedRoutedEventArgs e) OnRightClickedItemChanged(item, itemContextMenuFlyout); // Get items for the flyout - var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedToSidebar(item.Path)); + var menuItems = GetItemMenuItems(item, QuickAccessService.IsPinnedFolder(item.Path)); var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); // Set max width of the flyout @@ -103,12 +103,12 @@ public async Task OpenInNewWindowAsync(WidgetCardItem item) public virtual async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinToSidebarAsync([item.Path]); + await QuickAccessService.PinFolderToSidebarAsync([item.Path]); } public virtual async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFromSidebarAsync([item.Path]); + await QuickAccessService.UnpinFolderFromSidebarAsync([item.Path]); } protected void OnRightClickedItemChanged(WidgetCardItem? item, CommandBarFlyout? flyout) diff --git a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs index 8a586c2b7b5f..1a869778f401 100644 --- a/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs +++ b/src/Files.App/Views/Layouts/ColumnsLayoutPage.xaml.cs @@ -100,7 +100,7 @@ protected override void OnNavigatedTo(NavigationEventArgs eventArgs) if (!string.IsNullOrEmpty(pathRoot)) { - var rootPathList = QuickAccessService.PinnedFolders.Select(NormalizePath) + var rootPathList = QuickAccessService.PinnedFolderPaths.Select(NormalizePath) .Concat(CloudDrivesManager.Drives.Select(x => NormalizePath(x.Path))).ToList() .Concat(App.LibraryManager.Libraries.Select(x => NormalizePath(x.Path))).ToList(); rootPathList.Add(NormalizePath(pathRoot)); From d625e993eb54ff6cd38aa020453e9eada5747ebc Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 01:40:06 +0900 Subject: [PATCH 11/14] Added comments --- .../Contracts/IWindowsQuickAccessService.cs | 50 ++++++++++++------- .../Services/WindowsQuickAccessService.cs | 33 ++++++------ 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs index 98916eaa4b2d..1d8d3f070da5 100644 --- a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs @@ -7,65 +7,81 @@ namespace Files.App.Data.Contracts { public interface IWindowsQuickAccessService { + /// + /// Gets invoked when the pinned folder collection has been changed. + /// event EventHandler? DataChanged; + /// + /// Gets invoked when pinned items have changed. + /// event EventHandler? PinnedItemsChanged; + /// + /// Gets all pinned folder paths. + /// List PinnedFolderPaths { get; } + /// + /// Gets all pinned folder items. + /// IReadOnlyList PinnedFolderItems { get; } + /// + /// Initializes Quick Access item list. + /// + /// Task InitializeAsync(); /// - /// Gets the list of quick access items + /// Gets the list of Quick Access items. /// /// Task> GetPinnedFoldersAsync(); /// - /// Pins folders to the quick access list + /// Pins folders to the Quick Access list. /// - /// The array of folders to pin + /// The array of folders to pin. /// Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// - /// Unpins folders from the quick access list + /// Unpins folders from the Quick Access list. /// - /// The array of folders to unpin + /// The array of folders to unpin. /// Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// - /// Checks if a folder is pinned to the quick access list + /// Checks if a folder is pinned to the Quick Access list. /// - /// The path of the folder - /// true if the item is pinned + /// The path of the folder. + /// true if the item is pinned. bool IsPinnedFolder(string folderPath); /// - /// Saves a state of pinned folder items in the sidebar + /// Refreshes Quick Access pinned items. /// - /// The array of items to save + /// The array of items to pin. /// Task RefreshPinnedFolders(string[] items); /// - /// Updates items with the pinned items from the explorer sidebar + /// Fetches items from File Explorer. /// Task UpdatePinnedFolders(); /// - /// Returns the index of the location item in the navigation sidebar + /// Syncs all pinned items with File Explorer. /// - /// The location item - /// Index of the item - int IndexOf(string path); + Task SyncPinnedItemsAsync(); /// - /// Adds all items to the navigation sidebar + /// Returns the index of the location item in Sidebar. /// - Task SyncPinnedItemsAsync(); + /// The path to look up. + /// Index of the item. + int IndexOf(string path); } } diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index 28482f9639d3..d2406f2b4431 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -8,19 +8,21 @@ namespace Files.App.Services { internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService { - private static readonly string ShellContextMenuVerbPinToHome = "PinToHome"; + // Dependency injections - private static readonly string ShellContextMenuVerbUnpinToHome = "UnpinFromHome"; + private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); - private static readonly string ShellPropertyIsPinned = "System.Home.IsPinned"; + // Constants - private static readonly string ShellMemberNameSpace = "NameSpace"; + private const string ShellContextMenuVerbPinToHome = "PinToHome"; - private static readonly string ShellProgIDApplication = "Shell.Application"; + private const string ShellContextMenuVerbUnpinToHome = "UnpinFromHome"; - // Dependency injections + private const string ShellPropertyIsPinned = "System.Home.IsPinned"; - private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + private const string ShellMemberNameSpace = "NameSpace"; + + private const string ShellProgIDApplication = "Shell.Application"; // Fields @@ -52,6 +54,9 @@ public IReadOnlyList PinnedFolderItems /// public event EventHandler? PinnedItemsChanged; + /// + /// Initializes an instance of . + /// public WindowsQuickAccessService() { _quickAccessFolderWatcher = new() @@ -225,13 +230,6 @@ public async Task UpdatePinnedFolders() } } - /// - public int IndexOf(string path) - { - lock (_PinnedFolderItems) - return _PinnedFolderItems.FindIndex(x => x.Path == path); - } - /// public async Task SyncPinnedItemsAsync() { @@ -256,5 +254,12 @@ public async Task SyncPinnedItemsAsync() DataChanged?.Invoke(SectionType.Pinned, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, locationItem, insertIndex)); } } + + /// + public int IndexOf(string path) + { + lock (_PinnedFolderItems) + return _PinnedFolderItems.FindIndex(x => x.Path == path); + } } } From c51494f43b188a8c2359014ecba1efacd16c1ebc Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 01:41:32 +0900 Subject: [PATCH 12/14] Added comments --- src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs | 3 +++ src/Files.App/Services/WindowsQuickAccessService.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs index 1d8d3f070da5..02ac6cd46927 100644 --- a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs @@ -5,6 +5,9 @@ namespace Files.App.Data.Contracts { + /// + /// Provides service to manage the QuickAccess shell folder for Windows. + /// public interface IWindowsQuickAccessService { /// diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index d2406f2b4431..86bc2d87edc9 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -6,6 +6,7 @@ namespace Files.App.Services { + /// internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService { // Dependency injections From 95e64da596fb158d9a0ccc4262be9dc943cbc17d Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 13:11:51 +0900 Subject: [PATCH 13/14] Update --- .../Sidebar/PinFolderToSidebarAction.cs | 4 +-- .../Sidebar/UnpinFolderToSidebarAction.cs | 4 +-- .../Contracts/IWindowsQuickAccessService.cs | 10 +++---- .../Services/WindowsQuickAccessService.cs | 28 ++++++++----------- .../Widgets/QuickAccessWidget.xaml.cs | 8 +++--- .../UserControls/SidebarViewModel.cs | 6 ++-- .../Widgets/BaseWidgetViewModel.cs | 4 +-- 7 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs index f85fdde31e6b..3d6c311b06ea 100644 --- a/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/PinFolderToSidebarAction.cs @@ -34,11 +34,11 @@ public async Task ExecuteAsync() { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await QuickAccessService.PinFolderToSidebarAsync(items); + await QuickAccessService.PinFolderAsync(items); } else if (context.Folder is not null) { - await QuickAccessService.PinFolderToSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.PinFolderAsync([context.Folder.ItemPath]); } } diff --git a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs index f19319f9dab4..d652067ab1ad 100644 --- a/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/UnpinFolderToSidebarAction.cs @@ -31,11 +31,11 @@ public async Task ExecuteAsync() if (context.HasSelection) { var items = context.SelectedItems.Select(x => x.ItemPath).ToArray(); - await QuickAccessService.UnpinFolderFromSidebarAsync(items); + await QuickAccessService.UnpinFolderAsync(items); } else if (context.Folder is not null) { - await QuickAccessService.UnpinFolderFromSidebarAsync([context.Folder.ItemPath]); + await QuickAccessService.UnpinFolderAsync([context.Folder.ItemPath]); } } diff --git a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs index 02ac6cd46927..27f1867a7892 100644 --- a/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs +++ b/src/Files.App/Data/Contracts/IWindowsQuickAccessService.cs @@ -40,21 +40,21 @@ public interface IWindowsQuickAccessService /// Gets the list of Quick Access items. /// /// - Task> GetPinnedFoldersAsync(); + Task> GetFoldersAsync(); /// /// Pins folders to the Quick Access list. /// /// The array of folders to pin. /// - Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); + Task PinFolderAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Unpins folders from the Quick Access list. /// /// The array of folders to unpin. /// - Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); + Task UnpinFolderAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true); /// /// Checks if a folder is pinned to the Quick Access list. @@ -66,9 +66,9 @@ public interface IWindowsQuickAccessService /// /// Refreshes Quick Access pinned items. /// - /// The array of items to pin. + /// The array of paths to pin. /// - Task RefreshPinnedFolders(string[] items); + Task RefreshPinnedFolders(string[] folderPaths); /// /// Fetches items from File Explorer. diff --git a/src/Files.App/Services/WindowsQuickAccessService.cs b/src/Files.App/Services/WindowsQuickAccessService.cs index 86bc2d87edc9..b53d3c5795a2 100644 --- a/src/Files.App/Services/WindowsQuickAccessService.cs +++ b/src/Files.App/Services/WindowsQuickAccessService.cs @@ -16,13 +16,9 @@ internal sealed class WindowsQuickAccessService : IWindowsQuickAccessService // Constants private const string ShellContextMenuVerbPinToHome = "PinToHome"; - private const string ShellContextMenuVerbUnpinToHome = "UnpinFromHome"; - private const string ShellPropertyIsPinned = "System.Home.IsPinned"; - private const string ShellMemberNameSpace = "NameSpace"; - private const string ShellProgIDApplication = "Shell.Application"; // Fields @@ -73,7 +69,7 @@ public WindowsQuickAccessService() _quickAccessFolderWatcher.EnableRaisingEvents = false; await UpdatePinnedFolders(); - PinnedItemsChanged?.Invoke(null, new((await GetPinnedFoldersAsync()).ToArray(), true) { Reset = true }); + PinnedItemsChanged?.Invoke(null, new((await GetFoldersAsync()).ToArray(), true) { Reset = true }); _quickAccessFolderWatcher.EnableRaisingEvents = true; }; @@ -84,14 +80,14 @@ public async Task InitializeAsync() { // Pin RecycleBin folder if (!PinnedFolderPaths.Contains(Constants.UserEnvironmentPaths.RecycleBinPath) && SystemInformation.Instance.IsFirstRun) - await PinFolderToSidebarAsync([Constants.UserEnvironmentPaths.RecycleBinPath]); + await PinFolderAsync([Constants.UserEnvironmentPaths.RecycleBinPath]); // Refresh await UpdatePinnedFolders(); } /// - public async Task> GetPinnedFoldersAsync() + public async Task> GetFoldersAsync() { // TODO: Return IAsyncEnumerable, instead return (await Win32Helper.GetShellFolderAsync(Constants.CLID.QuickAccess, false, true, 0, int.MaxValue, "System.Home.IsPinned")) @@ -100,7 +96,7 @@ public async Task> GetPinnedFoldersAsync() } /// - public async Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) + public async Task PinFolderAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { foreach (string folderPath in folderPaths) await ContextMenu.InvokeVerb(ShellContextMenuVerbPinToHome, [folderPath]); @@ -112,7 +108,7 @@ public async Task PinFolderToSidebarAsync(string[] folderPaths, bool invokeQuick } /// - public async Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) + public async Task UnpinFolderAsync(string[] folderPaths, bool invokeQuickAccessChangedEvent = true) { // Get the shell application Program ID var shellAppType = Type.GetTypeFromProgID(ShellProgIDApplication)!; @@ -134,7 +130,7 @@ public async Task UnpinFolderFromSidebarAsync(string[] folderPaths, bool invokeQ if (folderPaths.Length == 0) { - folderPaths = (await GetPinnedFoldersAsync()) + folderPaths = (await GetFoldersAsync()) .Where(link => (bool?)link.Properties[ShellPropertyIsPinned] ?? false) .Select(link => link.FilePath).ToArray(); } @@ -182,20 +178,20 @@ public bool IsPinnedFolder(string folderPath) } /// - public async Task RefreshPinnedFolders(string[] items) + public async Task RefreshPinnedFolders(string[] folderPaths) { - if (Equals(items, PinnedFolderPaths.ToArray())) + if (Equals(folderPaths, PinnedFolderPaths.ToArray())) return; _quickAccessFolderWatcher.EnableRaisingEvents = false; // Unpin every item and pin new items in order - await UnpinFolderFromSidebarAsync([], false); - await PinFolderToSidebarAsync(items, false); + await UnpinFolderAsync([], false); + await PinFolderAsync(folderPaths, false); _quickAccessFolderWatcher.EnableRaisingEvents = true; - PinnedItemsChanged?.Invoke(this, new(items, true) { Reorder = true }); + PinnedItemsChanged?.Invoke(this, new(folderPaths, true) { Reorder = true }); } /// @@ -205,7 +201,7 @@ public async Task UpdatePinnedFolders() try { - PinnedFolderPaths = (await GetPinnedFoldersAsync()) + PinnedFolderPaths = (await GetFoldersAsync()) .Where(x => (bool?)x.Properties[ShellPropertyIsPinned] ?? false) .Select(x => x.FilePath).ToList(); diff --git a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs index 5d0c34ba47fc..f101e86d3f1d 100644 --- a/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/QuickAccessWidget.xaml.cs @@ -232,7 +232,7 @@ private async void QuickAccessWidget_Loaded(object sender, RoutedEventArgs e) { Loaded -= QuickAccessWidget_Loaded; - var itemsToAdd = await QuickAccessService.GetPinnedFoldersAsync(); + var itemsToAdd = await QuickAccessService.GetFoldersAsync(); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(itemsToAdd.ToArray(), false) { Reset = true @@ -305,11 +305,11 @@ private void OpenProperties(WidgetFolderCardItem item) public override async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinFolderToSidebarAsync([item.Path]); + await QuickAccessService.PinFolderAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); - var items = (await QuickAccessService.GetPinnedFoldersAsync()) + var items = (await QuickAccessService.GetFoldersAsync()) .Where(link => !((bool?)link.Properties["System.Home.IsPinned"] ?? false)); var recentItem = items.FirstOrDefault(x => !ItemsAdded.ToList().Select(y => y.Path).Contains(x.FilePath)); @@ -324,7 +324,7 @@ public override async Task PinToSidebarAsync(WidgetCardItem item) public override async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFolderFromSidebarAsync([item.Path]); + await QuickAccessService.UnpinFolderAsync([item.Path]); ModifyItemAsync(this, new ModifyQuickAccessEventArgs(new[] { item.Path }, false)); } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 9d18d2036a24..55f4f7d7abfa 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -841,12 +841,12 @@ private async Task OpenInNewWindowAsync() private void PinItem() { if (rightClickedItem is DriveItem) - _ = QuickAccessService.PinFolderToSidebarAsync(new[] { rightClickedItem.Path }); + _ = QuickAccessService.PinFolderAsync(new[] { rightClickedItem.Path }); } private void UnpinItem() { if (rightClickedItem.Section == SectionType.Pinned || rightClickedItem is DriveItem) - _ = QuickAccessService.UnpinFolderFromSidebarAsync([rightClickedItem.Path]); + _ = QuickAccessService.UnpinFolderAsync([rightClickedItem.Path]); } private void HideSection() @@ -1262,7 +1262,7 @@ private async Task HandleLocationItemDroppedAsync(LocationItem locationItem, Ite foreach (var item in storageItems) { if (item.ItemType == FilesystemItemType.Directory && !QuickAccessService.PinnedFolderPaths.Contains(item.Path)) - await QuickAccessService.PinFolderToSidebarAsync([item.Path]); + await QuickAccessService.PinFolderAsync([item.Path]); } } else diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 7f4df13444a0..1feaa3a0a1bb 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -103,12 +103,12 @@ public async Task OpenInNewWindowAsync(WidgetCardItem item) public virtual async Task PinToSidebarAsync(WidgetCardItem item) { - await QuickAccessService.PinFolderToSidebarAsync([item.Path]); + await QuickAccessService.PinFolderAsync([item.Path]); } public virtual async Task UnpinFromSidebarAsync(WidgetCardItem item) { - await QuickAccessService.UnpinFolderFromSidebarAsync([item.Path]); + await QuickAccessService.UnpinFolderAsync([item.Path]); } protected void OnRightClickedItemChanged(WidgetCardItem? item, CommandBarFlyout? flyout) From e06814b17fad64a565086808307ec705b92ffc75 Mon Sep 17 00:00:00 2001 From: 0x5BFA <62196528+0x5bfa@users.noreply.github.com> Date: Sat, 20 Apr 2024 13:47:51 +0900 Subject: [PATCH 14/14] fix --- src/Files.App/Data/Contexts/SideBar/SideBarContext.cs | 2 +- src/Files.App/ViewModels/UserControls/SidebarViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs index db84b2db6d6d..1ad2bccf541b 100644 --- a/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs +++ b/src/Files.App/Data/Contexts/SideBar/SideBarContext.cs @@ -10,7 +10,7 @@ internal sealed class SidebarContext : ObservableObject, ISidebarContext private int PinnedFolderItemIndex => IsItemRightClicked - ? QuickAccessService.IndexOfItem(_RightClickedItem!.Path) + ? QuickAccessService.IndexOf(_RightClickedItem!.Path) : -1; private INavigationControlItem? _RightClickedItem = null; diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 55f4f7d7abfa..c65ae527590e 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -935,7 +935,7 @@ private List GetLocationItemMenuItems(INavigatio { var options = item.MenuOptions; - var pinnedFolderIndex = QuickAccessService.IndexOfItem(item.Path); + var pinnedFolderIndex = QuickAccessService.IndexOf(item.Path); var pinnedFolderCount = QuickAccessService.PinnedFolderPaths.Count; var isPinnedItem = item.Section is SectionType.Pinned && pinnedFolderIndex is not -1;