diff --git a/src/Files.App/Data/Contracts/INetworkDrivesService.cs b/src/Files.App/Data/Contracts/INetworkDrivesService.cs index fc3e2a500847..39aed55b8522 100644 --- a/src/Files.App/Data/Contracts/INetworkDrivesService.cs +++ b/src/Files.App/Data/Contracts/INetworkDrivesService.cs @@ -8,11 +8,22 @@ namespace Files.App.Data.Contracts public interface INetworkDrivesService { /// - /// Enumerates network storage devices + /// Gets enumerated network storage drives. + /// + ObservableCollection Drives { get; } + + /// + /// Enumerates network storage drives. /// /// A collection of network storage devices IAsyncEnumerable GetDrivesAsync(); + /// + /// Updates network storage drives to up-to-date. + /// + /// + Task UpdateDrivesAsync(); + /// /// Displays the operating system dialog for connecting to a network storage device /// @@ -25,5 +36,12 @@ public interface INetworkDrivesService /// An item representing the network storage device to disconnect from /// True or false to indicate status bool DisconnectNetworkDrive(ILocatableFolder drive); + + /// + /// Authenticates the specified network share point. + /// + /// A path to the network share point. + /// True If succeeds; otherwise, false. + Task AuthenticateNetworkShare(string path); } } diff --git a/src/Files.App/Data/Models/ItemViewModel.cs b/src/Files.App/Data/Models/ItemViewModel.cs index cc53e8114c4a..9e8464e922c4 100644 --- a/src/Files.App/Data/Models/ItemViewModel.cs +++ b/src/Files.App/Data/Models/ItemViewModel.cs @@ -49,6 +49,7 @@ 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(); @@ -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; } diff --git a/src/Files.App/Data/Models/NetworkConnectionDialog.cs b/src/Files.App/Data/Models/NetworkConnectionDialog.cs new file mode 100644 index 000000000000..e9d3c35ee553 --- /dev/null +++ b/src/Files.App/Data/Models/NetworkConnectionDialog.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Vanara.Extensions; +using Vanara.InteropServices; +using static Vanara.PInvoke.Mpr; + +namespace Files.App.Data.Models +{ + /// + /// A dialog box that allows the user to browse and connect to network resources. + /// + /// + /// Forked from Vanara. + /// + public sealed class NetworkConnectionDialog : CommonDialog + { + private readonly NETRESOURCE netRes = new(); + private CONNECTDLGSTRUCT dialogOptions; + + /// Initializes a new instance of the class. + public NetworkConnectionDialog() + { + dialogOptions.cbStructure = (uint)Marshal.SizeOf(typeof(CONNECTDLGSTRUCT)); + netRes.dwType = NETRESOURCEType.RESOURCETYPE_DISK; + } + + /// Gets the connected device number. This value is only valid after successfully running the dialog. + /// The connected device number. The value is 1 for A:, 2 for B:, 3 for C:, and so on. If the user made a deviceless connection, the value is –1. + [Browsable(false)] + public int ConnectedDeviceNumber => dialogOptions.dwDevNum; + + /// Gets or sets a value indicating whether to hide the check box allowing the user to restore the connection at logon. + /// true if hiding restore connection check box; otherwise, false. + [DefaultValue(false), Category("Appearance"), Description("Hide the check box allowing the user to restore the connection at logon.")] + public bool HideRestoreConnectionCheckBox + { + get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_HIDE_BOX); + set => dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_HIDE_BOX, value); + } + + /// Gets or sets a value indicating whether restore the connection at logon. + /// true to restore connection at logon; otherwise, false. + [DefaultValue(false), Category("Behavior"), Description("Restore the connection at logon.")] + public bool PersistConnectionAtLogon + { + get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_PERSIST); + set + { + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_PERSIST, value); + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_NOT_PERSIST, !value); + } + } + + /// + /// Gets or sets a value indicating whether to display a read-only path instead of allowing the user to type in a path. This is only + /// valid if is not . + /// + /// true to display a read only path; otherwise, false. + [DefaultValue(false), Category("Appearance"), Description("Display a read-only path instead of allowing the user to type in a path.")] + public bool ReadOnlyPath { get; set; } + + /// Gets or sets the name of the remote network. + /// The name of the remote network. + [DefaultValue(null), Category("Behavior"), Description("The value displayed in the path field.")] + public string RemoteNetworkName { get => netRes.lpRemoteName; set => netRes.lpRemoteName = value; } + + /// Gets or sets a value indicating whether to enter the most recently used paths into the combination box. + /// true to use MRU path; otherwise, false. + /// UseMostRecentPath + [DefaultValue(false), Category("Behavior"), Description("Enter the most recently used paths into the combination box.")] + public bool UseMostRecentPath + { + get => dialogOptions.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_USE_MRU); + set + { + if (value && !string.IsNullOrEmpty(RemoteNetworkName)) + throw new InvalidOperationException($"{nameof(UseMostRecentPath)} cannot be set to true if {nameof(RemoteNetworkName)} has a value."); + + dialogOptions.dwFlags = dialogOptions.dwFlags.SetFlags(CONN_DLG.CONNDLG_USE_MRU, value); + } + } + + /// + public override void Reset() + { + dialogOptions.dwDevNum = -1; + dialogOptions.dwFlags = 0; + dialogOptions.lpConnRes = IntPtr.Zero; + ReadOnlyPath = false; + } + + /// + protected override bool RunDialog(IntPtr hwndOwner) + { + using var lpNetResource = SafeCoTaskMemHandle.CreateFromStructure(netRes); + + dialogOptions.hwndOwner = hwndOwner; + dialogOptions.lpConnRes = lpNetResource.DangerousGetHandle(); + + if (ReadOnlyPath && !string.IsNullOrEmpty(netRes.lpRemoteName)) + dialogOptions.dwFlags |= CONN_DLG.CONNDLG_RO_PATH; + + var result = WNetConnectionDialog1(dialogOptions); + + dialogOptions.lpConnRes = IntPtr.Zero; + + if (result == unchecked((uint)-1)) + return false; + + result.ThrowIfFailed(); + + return true; + } + } +} diff --git a/src/Files.App/Data/Models/NetworkDrivesViewModel.cs b/src/Files.App/Data/Models/NetworkDrivesViewModel.cs deleted file mode 100644 index 30c6e6a99e5e..000000000000 --- a/src/Files.App/Data/Models/NetworkDrivesViewModel.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using Files.App.Data.Items; -using Files.App.Services; -using Files.Core.Storage.LocatableStorage; - -namespace Files.App.Data.Models -{ - public sealed class NetworkDrivesViewModel : ObservableObject - { - public ObservableCollection Drives - { - get => drives; - private set => SetProperty(ref drives, value); - } - - private ObservableCollection drives; - private readonly INetworkDrivesService networkDrivesService; - - public NetworkDrivesViewModel(INetworkDrivesService networkDrivesService) - { - this.networkDrivesService = networkDrivesService; - drives = []; - - var networkItem = new DriveItem - { - DeviceID = "network-folder", - Text = "Network".GetLocalizedResource(), - Path = Constants.UserEnvironmentPaths.NetworkFolderPath, - Type = DriveType.Network, - ItemType = NavigationControlItemType.Drive, - }; - networkItem.MenuOptions = new ContextMenuOptions - { - IsLocationItem = true, - ShowShellItems = true, - ShowEjectDevice = networkItem.IsRemovable, - ShowProperties = true - }; - - lock (drives) - { - drives.Add(networkItem); - } - } - - public async Task UpdateDrivesAsync() - { - var unsortedDrives = new List - { - drives.Single(x => x is DriveItem o && o.DeviceID == "network-folder") - }; - await foreach (ILocatableFolder item in networkDrivesService.GetDrivesAsync()) - { - unsortedDrives.Add(item); - } - - var orderedDrives = unsortedDrives.Cast() - .OrderByDescending(o => o.DeviceID == "network-folder") - .ThenBy(o => o.Text); - - Drives.Clear(); - foreach (ILocatableFolder item in orderedDrives) - { - Drives.AddIfNotPresent(item); - } - } - - public void DisconnectNetworkDrive(ILocatableFolder drive) - => networkDrivesService.DisconnectNetworkDrive(drive); - - public Task OpenMapNetworkDriveDialogAsync() - => networkDrivesService.OpenMapNetworkDriveDialogAsync(); - } -} diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs index a1eb4b0203b5..27571fb9c3c5 100644 --- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs +++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs @@ -196,7 +196,6 @@ public static IHost ConfigureHost() .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() .AddSingleton() .AddSingleton() .AddTransient() diff --git a/src/Files.App/Helpers/Navigation/NavigationHelpers.cs b/src/Files.App/Helpers/Navigation/NavigationHelpers.cs index b6c369daad1e..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; } diff --git a/src/Files.App/Services/NetworkDrivesService.cs b/src/Files.App/Services/NetworkDrivesService.cs index 6fa1ccbca846..00729e557136 100644 --- a/src/Files.App/Services/NetworkDrivesService.cs +++ b/src/Files.App/Services/NetworkDrivesService.cs @@ -1,28 +1,62 @@ -// Copyright (c) 2024 Files Community +// Copyright (c) 2024 Files Community // Licensed under the MIT License. See the LICENSE. -using Files.App.Utils.Shell; -using Files.Core.Storage.LocatableStorage; +using System.Runtime.InteropServices; +using System.Text; +using Vanara.InteropServices; using Vanara.PInvoke; using Vanara.Windows.Shell; +using static Vanara.PInvoke.AdvApi32; +using static Vanara.PInvoke.Mpr; namespace Files.App.Services { - public sealed class NetworkDrivesService : INetworkDrivesService + public sealed class NetworkDrivesService : ObservableObject, INetworkDrivesService { - public bool DisconnectNetworkDrive(ILocatableFolder drive) + private ObservableCollection _Drives; + /// + public ObservableCollection Drives { - return NetworkDrivesAPI.DisconnectNetworkDrive(drive.Path); + get => _Drives; + private set => SetProperty(ref _Drives, value); } + /// + /// Initializes an instance of . + /// + public NetworkDrivesService() + { + _Drives = []; + + var networkItem = new DriveItem() + { + DeviceID = "network-folder", + Text = "Network".GetLocalizedResource(), + Path = Constants.UserEnvironmentPaths.NetworkFolderPath, + Type = DriveType.Network, + ItemType = NavigationControlItemType.Drive, + }; + + networkItem.MenuOptions = new ContextMenuOptions() + { + IsLocationItem = true, + ShowEjectDevice = networkItem.IsRemovable, + ShowShellItems = true, + ShowProperties = true, + }; + lock (_Drives) + _Drives.Add(networkItem); + } + + /// public async IAsyncEnumerable GetDrivesAsync() { var networkLocations = await Win32Helper.StartSTATask(() => { var locations = new List(); - using (var nethood = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_NetHood)) + using (var netHood = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_NetHood)) { - foreach (var item in nethood) + foreach (var item in netHood) { if (item is ShellLink link) { @@ -30,44 +64,140 @@ public async IAsyncEnumerable GetDrivesAsync() } else { - var linkPath = (string)item.Properties["System.Link.TargetParsingPath"]; + var linkPath = (string?)item?.Properties["System.Link.TargetParsingPath"]; if (linkPath is not null) { var linkItem = ShellFolderExtensions.GetShellFileItem(item); - locations.Add(new ShellLinkItem(linkItem) { TargetPath = linkPath }); + locations.Add(new(linkItem) { TargetPath = linkPath }); } } } } + return locations; }); foreach (var item in networkLocations ?? Enumerable.Empty()) { - var networkItem = new DriveItem + var networkItem = new DriveItem() { - Text = System.IO.Path.GetFileNameWithoutExtension(item.FileName), + Text = SystemIO.Path.GetFileNameWithoutExtension(item.FileName), Path = item.TargetPath, DeviceID = item.FilePath, Type = DriveType.Network, ItemType = NavigationControlItemType.Drive, }; - networkItem.MenuOptions = new ContextMenuOptions + + networkItem.MenuOptions = new ContextMenuOptions() { IsLocationItem = true, ShowEjectDevice = networkItem.IsRemovable, ShowShellItems = true, ShowProperties = true, }; - yield return networkItem; } } + /// + public async Task UpdateDrivesAsync() + { + var unsortedDrives = new List() + { + _Drives.Single(x => x is DriveItem o && o.DeviceID == "network-folder") + }; + + await foreach (ILocatableFolder item in GetDrivesAsync()) + unsortedDrives.Add(item); + + var orderedDrives = + unsortedDrives.Cast() + .OrderByDescending(o => o.DeviceID == "network-folder") + .ThenBy(o => o.Text); + + Drives.Clear(); + + foreach (ILocatableFolder item in orderedDrives) + Drives.AddIfNotPresent(item); + } + + /// + public bool DisconnectNetworkDrive(ILocatableFolder drive) + { + return WNetCancelConnection2(drive.Path.TrimEnd('\\'), CONNECT.CONNECT_UPDATE_PROFILE, true).Succeeded; + } + + /// public Task OpenMapNetworkDriveDialogAsync() { - var handle = MainWindow.Instance.WindowHandle.ToInt64(); - return NetworkDrivesAPI.OpenMapNetworkDriveDialog(handle); + var hWnd = MainWindow.Instance.WindowHandle.ToInt64(); + + return Win32Helper.StartSTATask(() => + { + using var ncd = new NetworkConnectionDialog + { + UseMostRecentPath = true, + HideRestoreConnectionCheckBox = false + }; + + return ncd.ShowDialog(Win32Helper.Win32Window.FromLong(hWnd)) == System.Windows.Forms.DialogResult.OK; + }); + } + + /// + public async Task AuthenticateNetworkShare(string path) + { + var netRes = new NETRESOURCE() + { + dwType = NETRESOURCEType.RESOURCETYPE_DISK, + lpRemoteName = path + }; + + // If credentials are saved, this will return NO_ERROR + Win32Error connectionError = WNetAddConnection3(HWND.NULL, netRes, null, null, 0); + + if (connectionError == Win32Error.ERROR_LOGON_FAILURE || connectionError == Win32Error.ERROR_ACCESS_DENIED) + { + var dialog = DynamicDialogFactory.GetFor_CredentialEntryDialog(path); + await dialog.ShowAsync(); + var credentialsReturned = dialog.ViewModel.AdditionalData as string[]; + + if (credentialsReturned is not null && credentialsReturned[1] != null) + { + connectionError = WNetAddConnection3(HWND.NULL, netRes, credentialsReturned[1], credentialsReturned[0], 0); + if (credentialsReturned[2] == "y" && connectionError == Win32Error.NO_ERROR) + { + var creds = new CREDENTIAL + { + TargetName = new StrPtrAuto(path.Substring(2)), + UserName = new StrPtrAuto(credentialsReturned[0]), + Type = CRED_TYPE.CRED_TYPE_DOMAIN_PASSWORD, + AttributeCount = 0, + Persist = CRED_PERSIST.CRED_PERSIST_ENTERPRISE + }; + + byte[] bPassword = Encoding.Unicode.GetBytes(credentialsReturned[1]); + creds.CredentialBlobSize = (uint)bPassword.Length; + creds.CredentialBlob = Marshal.StringToCoTaskMemUni(credentialsReturned[1]); + CredWrite(creds, 0); + } + } + else + { + return false; + } + } + + if (connectionError == Win32Error.NO_ERROR) + { + return true; + } + else + { + await DialogDisplayHelper.ShowDialogAsync("NetworkFolderErrorDialogTitle".GetLocalizedResource(), connectionError.ToString().Split(":")[1].Trim()); + + return false; + } } } } diff --git a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs index 56cce2566a8f..c5e5227e2f99 100644 --- a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs @@ -22,8 +22,8 @@ public sealed partial class DrivesWidget : BaseWidgetViewModel, IWidgetViewModel public IUserSettingsService userSettingsService { get; } = Ioc.Default.GetRequiredService(); private IHomePageContext HomePageContext { get; } = Ioc.Default.GetRequiredService(); - private DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); - private NetworkDrivesViewModel networkDrivesViewModel = Ioc.Default.GetRequiredService(); + private readonly DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); + private readonly INetworkDrivesService NetworkDrivesService = Ioc.Default.GetRequiredService(); public delegate void DrivesWidgetInvokedEventHandler(object sender, DrivesWidgetInvokedEventArgs e); public event DrivesWidgetInvokedEventHandler DrivesWidgetInvoked; @@ -222,7 +222,7 @@ public override List GetItemMenuItems(WidgetCard private Task DoNetworkMapDriveAsync() { - return networkDrivesViewModel.OpenMapNetworkDriveDialogAsync(); + return NetworkDrivesService.OpenMapNetworkDriveDialogAsync(); } private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") @@ -314,7 +314,7 @@ private void MenuFlyout_Opening(object sender, object e) private void DisconnectNetworkDrive(WidgetDriveCardItem item) { - networkDrivesViewModel.DisconnectNetworkDrive(item.Item); + NetworkDrivesService.DisconnectNetworkDrive(item.Item); } private void GoToStorageSense_Click(object sender, RoutedEventArgs e) diff --git a/src/Files.App/Utils/Global/NetworkDrivesAPI.cs b/src/Files.App/Utils/Global/NetworkDrivesAPI.cs deleted file mode 100644 index d391e30a33b0..000000000000 --- a/src/Files.App/Utils/Global/NetworkDrivesAPI.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2024 Files Community -// Licensed under the MIT License. See the LICENSE. - -using Files.App.Extensions; -using Files.App.Helpers; -using Files.App.Utils.Shell; -using System; -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; -using Vanara.Extensions; -using Vanara.InteropServices; -using static Vanara.PInvoke.AdvApi32; -using Vanara.PInvoke; -using static Vanara.PInvoke.Mpr; - -namespace Files.App.Utils -{ - public sealed class NetworkDrivesAPI - { - /// - /// A dialog box that allows the user to browse and connect to network resources. - /// - public sealed class NetworkConnectionDialog : CommonDialog - { - private readonly NETRESOURCE nres = new NETRESOURCE(); - private CONNECTDLGSTRUCT opts; - - /// Initializes a new instance of the class. - public NetworkConnectionDialog() - { - opts.cbStructure = (uint)Marshal.SizeOf(typeof(CONNECTDLGSTRUCT)); - nres.dwType = NETRESOURCEType.RESOURCETYPE_DISK; - } - - /// Gets the connected device number. This value is only valid after successfully running the dialog. - /// The connected device number. The value is 1 for A:, 2 for B:, 3 for C:, and so on. If the user made a deviceless connection, the value is –1. - [Browsable(false)] - public int ConnectedDeviceNumber => opts.dwDevNum; - - /// Gets or sets a value indicating whether to hide the check box allowing the user to restore the connection at logon. - /// true if hiding restore connection check box; otherwise, false. - [DefaultValue(false), Category("Appearance"), Description("Hide the check box allowing the user to restore the connection at logon.")] - public bool HideRestoreConnectionCheckBox - { - get => opts.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_HIDE_BOX); - set => opts.dwFlags = opts.dwFlags.SetFlags(CONN_DLG.CONNDLG_HIDE_BOX, value); - } - - /// Gets or sets a value indicating whether restore the connection at logon. - /// true to restore connection at logon; otherwise, false. - [DefaultValue(false), Category("Behavior"), Description("Restore the connection at logon.")] - public bool PersistConnectionAtLogon - { - get => opts.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_PERSIST); - set - { - opts.dwFlags = opts.dwFlags.SetFlags(CONN_DLG.CONNDLG_PERSIST, value); - opts.dwFlags = opts.dwFlags.SetFlags(CONN_DLG.CONNDLG_NOT_PERSIST, !value); - } - } - - /// - /// Gets or sets a value indicating whether to display a read-only path instead of allowing the user to type in a path. This is only - /// valid if is not . - /// - /// true to display a read only path; otherwise, false. - [DefaultValue(false), Category("Appearance"), Description("Display a read-only path instead of allowing the user to type in a path.")] - public bool ReadOnlyPath { get; set; } - - /// Gets or sets the name of the remote network. - /// The name of the remote network. - [DefaultValue(null), Category("Behavior"), Description("The value displayed in the path field.")] - public string RemoteNetworkName { get => nres.lpRemoteName; set => nres.lpRemoteName = value; } - - /// Gets or sets a value indicating whether to enter the most recently used paths into the combination box. - /// true to use MRU path; otherwise, false. - /// UseMostRecentPath - [DefaultValue(false), Category("Behavior"), Description("Enter the most recently used paths into the combination box.")] - public bool UseMostRecentPath - { - get => opts.dwFlags.IsFlagSet(CONN_DLG.CONNDLG_USE_MRU); - set - { - if (value && !string.IsNullOrEmpty(RemoteNetworkName)) - throw new InvalidOperationException($"{nameof(UseMostRecentPath)} cannot be set to true if {nameof(RemoteNetworkName)} has a value."); - opts.dwFlags = opts.dwFlags.SetFlags(CONN_DLG.CONNDLG_USE_MRU, value); - } - } - - /// - public override void Reset() - { - opts.dwDevNum = -1; - opts.dwFlags = 0; - opts.lpConnRes = IntPtr.Zero; - ReadOnlyPath = false; - } - - /// - protected override bool RunDialog(IntPtr hwndOwner) - { - using var lpnres = SafeCoTaskMemHandle.CreateFromStructure(nres); - opts.hwndOwner = hwndOwner; - opts.lpConnRes = lpnres.DangerousGetHandle(); - if (ReadOnlyPath && !string.IsNullOrEmpty(nres.lpRemoteName)) - opts.dwFlags |= CONN_DLG.CONNDLG_RO_PATH; - var ret = WNetConnectionDialog1(opts); - opts.lpConnRes = IntPtr.Zero; - if (ret == unchecked((uint)-1)) return false; - ret.ThrowIfFailed(); - return true; - } - } - - public static Task OpenMapNetworkDriveDialog(long hwnd) - { - return Win32Helper.StartSTATask(() => - { - using var ncd = new NetworkConnectionDialog { UseMostRecentPath = true }; - ncd.HideRestoreConnectionCheckBox = false; - return ncd.ShowDialog(Win32Helper.Win32Window.FromLong(hwnd)) == System.Windows.Forms.DialogResult.OK; - }); - } - - public static async Task AuthenticateNetworkShare(string path) - { - NETRESOURCE nr = new NETRESOURCE - { - dwType = NETRESOURCEType.RESOURCETYPE_DISK, - lpRemoteName = path - }; - - Win32Error connectionError = WNetAddConnection3(HWND.NULL, nr, null, null, 0); // if creds are saved, this will return NO_ERROR - - if (connectionError == Win32Error.ERROR_LOGON_FAILURE || connectionError == Win32Error.ERROR_ACCESS_DENIED) - { - var dialog = DynamicDialogFactory.GetFor_CredentialEntryDialog(path); - await dialog.ShowAsync(); - var credentialsReturned = dialog.ViewModel.AdditionalData as string[]; - - if (credentialsReturned is string[] && credentialsReturned[1] != null) - { - connectionError = WNetAddConnection3(HWND.NULL, nr, credentialsReturned[1], credentialsReturned[0], 0); - if (credentialsReturned[2] == "y" && connectionError == Win32Error.NO_ERROR) - { - CREDENTIAL creds = new CREDENTIAL(); - creds.TargetName = new StrPtrAuto(path.Substring(2)); - creds.UserName = new StrPtrAuto(credentialsReturned[0]); - creds.Type = CRED_TYPE.CRED_TYPE_DOMAIN_PASSWORD; - creds.AttributeCount = 0; - creds.Persist = CRED_PERSIST.CRED_PERSIST_ENTERPRISE; - byte[] bpassword = Encoding.Unicode.GetBytes(credentialsReturned[1]); - creds.CredentialBlobSize = (UInt32)bpassword.Length; - creds.CredentialBlob = Marshal.StringToCoTaskMemUni(credentialsReturned[1]); - CredWrite(creds, 0); - } - } - else - return false; - } - - if (connectionError == Win32Error.NO_ERROR) - return true; - else - { - await DialogDisplayHelper.ShowDialogAsync("NetworkFolderErrorDialogTitle".GetLocalizedResource(), connectionError.ToString().Split(":")[1].Trim()); - return false; - } - } - - public static bool DisconnectNetworkDrive(string drive) - { - return WNetCancelConnection2(drive.TrimEnd('\\'), CONNECT.CONNECT_UPDATE_PROFILE, true).Succeeded; - } - } -} diff --git a/src/Files.App/ViewModels/MainPageViewModel.cs b/src/Files.App/ViewModels/MainPageViewModel.cs index 6277edaab594..6f15460f5704 100644 --- a/src/Files.App/ViewModels/MainPageViewModel.cs +++ b/src/Files.App/ViewModels/MainPageViewModel.cs @@ -16,7 +16,7 @@ public sealed class MainPageViewModel : ObservableObject // Dependency injections private IAppearanceSettingsService AppearanceSettingsService { get; } = Ioc.Default.GetRequiredService(); - private NetworkDrivesViewModel NetworkDrivesViewModel { get; } = Ioc.Default.GetRequiredService(); + private INetworkDrivesService NetworkDrivesService { get; } = Ioc.Default.GetRequiredService(); private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); private IResourcesService ResourcesService { get; } = Ioc.Default.GetRequiredService(); private DrivesViewModel DrivesViewModel { get; } = Ioc.Default.GetRequiredService(); @@ -179,7 +179,7 @@ UserSettingsService.GeneralSettingsService.LastSessionTabList is not null && await Task.WhenAll( DrivesViewModel.UpdateDrivesAsync(), - NetworkDrivesViewModel.UpdateDrivesAsync()); + NetworkDrivesService.UpdateDrivesAsync()); } // Command methods diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 53e42b94ced2..ac792a8b34b9 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -24,13 +24,12 @@ namespace Files.App.ViewModels.UserControls { public sealed class SidebarViewModel : ObservableObject, IDisposable, ISidebarViewModel { + private INetworkDrivesService NetworkDrivesService { get; } = Ioc.Default.GetRequiredService(); private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); private ICommandManager Commands { get; } = Ioc.Default.GetRequiredService(); private readonly DrivesViewModel drivesViewModel = Ioc.Default.GetRequiredService(); private readonly IFileTagsService fileTagsService; - private readonly NetworkDrivesViewModel networkDrivesViewModel = Ioc.Default.GetRequiredService(); - private IPaneHolder paneHolder; public IPaneHolder PaneHolder { @@ -250,7 +249,7 @@ public SidebarViewModel() App.LibraryManager.DataChanged += Manager_DataChanged; drivesViewModel.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged += Manager_DataChanged; - networkDrivesViewModel.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Network, args); + NetworkDrivesService.Drives.CollectionChanged += (x, args) => Manager_DataChanged(SectionType.Network, args); WSLDistroManager.DataChanged += Manager_DataChanged; App.FileTagsManager.DataChanged += Manager_DataChanged; SidebarDisplayMode = UserSettingsService.AppearanceSettingsService.IsSidebarOpen ? SidebarDisplayMode.Expanded : SidebarDisplayMode.Compact; @@ -283,7 +282,7 @@ await dispatcherQueue.EnqueueOrInvokeAsync(async () => SectionType.Pinned => App.QuickAccessManager.Model.PinnedFolderItems, SectionType.CloudDrives => CloudDrivesManager.Drives, SectionType.Drives => drivesViewModel.Drives.Cast().ToList().AsReadOnly(), - SectionType.Network => networkDrivesViewModel.Drives.Cast().ToList().AsReadOnly(), + SectionType.Network => NetworkDrivesService.Drives.Cast().ToList().AsReadOnly(), SectionType.WSL => WSLDistroManager.Distros, SectionType.Library => App.LibraryManager.Libraries, SectionType.FileTag => App.FileTagsManager.FileTags, @@ -578,7 +577,7 @@ public async Task UpdateSectionVisibilityAsync(SectionType sectionType, bool sho { SectionType.CloudDrives when generalSettingsService.ShowCloudDrivesSection => CloudDrivesManager.UpdateDrivesAsync, SectionType.Drives => drivesViewModel.UpdateDrivesAsync, - SectionType.Network when generalSettingsService.ShowNetworkDrivesSection => networkDrivesViewModel.UpdateDrivesAsync, + SectionType.Network when generalSettingsService.ShowNetworkDrivesSection => NetworkDrivesService.UpdateDrivesAsync, SectionType.WSL when generalSettingsService.ShowWslSection => WSLDistroManager.UpdateDrivesAsync, SectionType.FileTag when generalSettingsService.ShowFileTagsSection => App.FileTagsManager.UpdateFileTagsAsync, SectionType.Library => App.LibraryManager.UpdateLibrariesAsync, @@ -645,7 +644,7 @@ public void Dispose() App.LibraryManager.DataChanged -= Manager_DataChanged; drivesViewModel.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Drives, args); CloudDrivesManager.DataChanged -= Manager_DataChanged; - networkDrivesViewModel.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Network, args); + NetworkDrivesService.Drives.CollectionChanged -= (x, args) => Manager_DataChanged(SectionType.Network, args); WSLDistroManager.DataChanged -= Manager_DataChanged; App.FileTagsManager.DataChanged -= Manager_DataChanged; }