diff --git a/src/Files.App/Helpers/DynamicDialogFactory.cs b/src/Files.App/Helpers/DynamicDialogFactory.cs index ce6c7b902096..03d341d66627 100644 --- a/src/Files.App/Helpers/DynamicDialogFactory.cs +++ b/src/Files.App/Helpers/DynamicDialogFactory.cs @@ -42,6 +42,19 @@ public static DynamicDialog GetFor_ConsentDialog() return dialog; } + public static DynamicDialog GetFor_ShortcutNotFound(string targetPath) + { + DynamicDialog dialog = new(new DynamicDialogViewModel + { + TitleText = "ShortcutCannotBeOpened".GetLocalizedResource(), + SubtitleText = string.Format("DeleteShortcutDescription".GetLocalizedResource(), targetPath), + PrimaryButtonText = "Delete".GetLocalizedResource(), + SecondaryButtonText = "No".GetLocalizedResource(), + DynamicButtons = DynamicDialogButtons.Primary | DynamicDialogButtons.Secondary + }); + return dialog; + } + public static DynamicDialog GetFor_RenameDialog() { DynamicDialog dialog = null; diff --git a/src/Files.App/Helpers/NavigationHelpers.cs b/src/Files.App/Helpers/NavigationHelpers.cs index 67c3114d70b8..16faeb786e40 100644 --- a/src/Files.App/Helpers/NavigationHelpers.cs +++ b/src/Files.App/Helpers/NavigationHelpers.cs @@ -12,10 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json; using System.Threading.Tasks; -using Windows.ApplicationModel.AppService; -using Windows.Foundation.Collections; using Windows.Storage; using Windows.Storage.Search; using Windows.System; @@ -135,38 +132,31 @@ public static async Task OpenPath(string path, IShellPage associatedInstan 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 = path.EndsWith(".lnk", StringComparison.Ordinal) || path.EndsWith(".url", StringComparison.Ordinal); + bool isShortcut = associatedInstance.SlimContentPage.SelectedItem is { IsShortcut: true }; FilesystemResult opened = (FilesystemResult)false; var shortcutInfo = new ShellLinkItem(); - if (itemType == null || IsShortcut || isHiddenItem || isReparsePoint) + if (itemType == null || isShortcut || isHiddenItem || isReparsePoint) { - if (IsShortcut) + if (isShortcut) { - var connection = await AppServiceConnectionHelper.Instance; + var shInfo = await Win32Shell.ParseLink(path); - if (connection == null) + if (shInfo == null) return false; - var (status, response) = await connection.SendMessageForResponseAsync(new ValueSet() - { - { "Arguments", "FileOperation" }, - { "fileop", "ParseLink" }, - { "filepath", path } - }); + itemType = shInfo.IsFolder ? FilesystemItemType.Directory : FilesystemItemType.File; - if (status == AppServiceResponseStatus.Success && response.ContainsKey("ShortcutInfo")) - { - var shInfo = JsonSerializer.Deserialize(response["ShortcutInfo"].GetString()); - if (shInfo != null) - { - shortcutInfo = shInfo; - } - itemType = shInfo != null && shInfo.IsFolder ? FilesystemItemType.Directory : FilesystemItemType.File; - } - else + shortcutInfo = shInfo; + + if (shortcutInfo.InvalidTarget) { - return false; + if (await DialogDisplayHelper.ShowDialogAsync(DynamicDialogFactory.GetFor_ShortcutNotFound(shortcutInfo.TargetPath)) != DynamicDialogResult.Primary) + return false; + + // Delete shortcut + var shortcutItem = StorageHelpers.FromPathAndType(path, FilesystemItemType.File); + await associatedInstance.FilesystemHelpers.DeleteItemAsync(shortcutItem, false, false, true); } } else if (isReparsePoint) diff --git a/src/Files.App/Shell/Win32Shell.cs b/src/Files.App/Shell/Win32Shell.cs index 62a35bd68f8e..5e68b63a9516 100644 --- a/src/Files.App/Shell/Win32Shell.cs +++ b/src/Files.App/Shell/Win32Shell.cs @@ -1,15 +1,14 @@ -using Files.Shared; -using Files.Shared.Extensions; +#nullable enable + +using Files.Shared; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using Vanara.PInvoke; using Vanara.Windows.Shell; -using Windows.Storage; namespace Files.App.Shell { @@ -90,5 +89,51 @@ public static (bool HasRecycleBin, long NumItems, long BinSize) QueryRecycleBin( return (false, 0, 0); } } + + public static async Task ParseLink(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + return null; + + string targetPath = string.Empty; + + try + { + if (filePath.EndsWith(".lnk", StringComparison.OrdinalIgnoreCase)) + { + using var link = new ShellLink(filePath, LinkResolution.NoUIWithMsgPump, null, TimeSpan.FromMilliseconds(100)); + targetPath = link.TargetPath; + return ShellFolderExtensions.GetShellLinkItem(link); + } + + if (filePath.EndsWith(".url", StringComparison.OrdinalIgnoreCase)) + { + targetPath = await Win32API.StartSTATask(() => + { + var ipf = new Url.IUniformResourceLocator(); + (ipf as System.Runtime.InteropServices.ComTypes.IPersistFile)?.Load(filePath, 0); + ipf.GetUrl(out var retVal); + return retVal; + }); + + return string.IsNullOrEmpty(targetPath) ? null : new ShellLinkItem { TargetPath = targetPath }; + } + } + catch (FileNotFoundException ex) // Could not parse shortcut + { + App.Logger?.Warn(ex, ex.Message); + // Return a item containing the invalid target path + return new ShellLinkItem + { + TargetPath = string.IsNullOrEmpty(targetPath) ? string.Empty : targetPath, + InvalidTarget = true + }; + } + catch (Exception ex) + { + App.Logger?.Warn(ex, ex.Message); + } + return null; + } } } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 0896930a1dbb..8b974e53f53a 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -2832,4 +2832,10 @@ Restore + + Shortcut cannot be opened + + + The target destination cannot be found {0}. Do you want to delete this shortcut? + \ No newline at end of file diff --git a/src/Files.Shared/ShellLinkItem.cs b/src/Files.Shared/ShellLinkItem.cs index 79600b8c4d88..bd8e17bdbcd8 100644 --- a/src/Files.Shared/ShellLinkItem.cs +++ b/src/Files.Shared/ShellLinkItem.cs @@ -6,6 +6,7 @@ public class ShellLinkItem : ShellFileItem public string Arguments { get; set; } public string WorkingDirectory { get; set; } public bool RunAsAdmin { get; set; } + public bool InvalidTarget { get; set; } public ShellLinkItem() {