龙腾猫跃 5 месяцев назад
Родитель
Сommit
560a149629
4 измененных файлов с 116 добавлено и 46 удалено
  1. 2 2
      Shared/General/MsDelta.cs
  2. 11 15
      Shared/Utils/DirectoryUtils.cs
  3. 93 26
      Shared/Utils/FileUtils.cs
  4. 10 3
      Shared/Utils/PathUtils.cs

+ 2 - 2
Shared/General/MsDelta.cs

@@ -17,7 +17,7 @@ public static class MsDelta {
         // fileTypeSet:https://learn.microsoft.com/en-us/previous-versions/bb417345(v=msdn.10)?redirectedfrom=MSDN#file-type-sets (15L: DELTA_FILE_TYPE_SET_EXECUTABLES)
         // 131072L: IgnoreFileSizeLimit, 32u: Crc32
         if (!CreateDelta(15L, 131072L, 0L, 
-            PathUtils.ToLongPath(oldFilePath), PathUtils.ToLongPath(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, PathUtils.ToLongPath(deltaFilePath)))
+            PathUtils.WithLongPath(oldFilePath), PathUtils.WithLongPath(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, PathUtils.WithLongPath(deltaFilePath)))
             throw new Win32Exception();
     }
 
@@ -31,7 +31,7 @@ public static class MsDelta {
         if (!FileUtils.Exists(deltaFilePath)) throw new FileNotFoundException($"补丁文件不存在:{deltaFilePath}", deltaFilePath);
         DirectoryUtils.Create(newFilePath, isFilePath: true);
         // 1L: AllowLegacy
-        if (!ApplyDelta(1L, PathUtils.ToLongPath(oldFilePath), PathUtils.ToLongPath(deltaFilePath), PathUtils.ToLongPath(newFilePath)))
+        if (!ApplyDelta(1L, PathUtils.WithLongPath(oldFilePath), PathUtils.WithLongPath(deltaFilePath), PathUtils.WithLongPath(newFilePath)))
             throw new Win32Exception();
     }
 

+ 11 - 15
Shared/Utils/DirectoryUtils.cs

@@ -7,35 +7,35 @@ public static class DirectoryUtils {
     /// </summary>
     public static void Create(string path, bool isFilePath = false) {
         if (isFilePath) path = Path.GetDirectoryName(path);
-        Directory.CreateDirectory(PathUtils.ToLongPath(path));
+        Directory.CreateDirectory(PathUtils.WithLongPath(path));
     }
 
     /// <summary>
     /// 判断文件夹是否存在。
     /// </summary>
     public static bool Exists(string path) => 
-        Directory.Exists(PathUtils.ToLongPath(path));
+        Directory.Exists(PathUtils.WithLongPath(path));
 
     /// <summary>
     /// 创建 <see cref="DirectoryInfo"/> 对象。
     /// </summary>
     public static DirectoryInfo GetInfo(string path) => 
-        new(PathUtils.ToLongPath(path));
+        new(PathUtils.WithLongPath(path));
 
     /// <summary>
-    /// 返回指定路径下的所有文件。
+    /// 返回指定路径下的所有文件,不以 \\?\ 开头。
     /// </summary>
     public static IEnumerable<string> GetFiles(string path, bool topDirectoryOnly = false, string searchPattern = "*") {
         if (!Exists(path)) return [];
-        return Directory.EnumerateFiles(PathUtils.ToLongPath(path), searchPattern, topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
+        return Directory.EnumerateFiles(PathUtils.WithLongPath(path), searchPattern, topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories).Select(PathUtils.WithoutLongPath);
     }
 
     /// <summary>
-    /// 返回指定路径下的所有文件夹,不以分隔符结尾。
+    /// 返回指定路径下的所有文件夹,不以分隔符结尾,不以 \\?\ 开头。
     /// </summary>
     public static IEnumerable<string> GetDirectories(string path, bool topDirectoryOnly = false, string searchPattern = "*") {
         if (!Exists(path)) return [];
-        return Directory.EnumerateDirectories(PathUtils.ToLongPath(path), searchPattern, topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories);
+        return Directory.EnumerateDirectories(PathUtils.WithLongPath(path), searchPattern, topDirectoryOnly ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories).Select(PathUtils.WithoutLongPath);
     }
 
     /// <summary>
@@ -44,7 +44,7 @@ public static class DirectoryUtils {
     /// </summary>
     public static bool IsEmpty(string path) {
         if (!Exists(path)) return true;
-        return !Directory.EnumerateFileSystemEntries(PathUtils.ToLongPath(path)).Any();
+        return !Directory.EnumerateFileSystemEntries(PathUtils.WithLongPath(path)).Any();
     }
 
     /// <summary>
@@ -53,21 +53,17 @@ public static class DirectoryUtils {
     public static void Move(string sourceDirName, string destDirName) {
         if (!Exists(sourceDirName)) return;
         Create(destDirName);
-        Directory.Move(PathUtils.ToLongPath(sourceDirName), PathUtils.ToLongPath(destDirName));
+        Directory.Move(PathUtils.WithLongPath(sourceDirName), PathUtils.WithLongPath(destDirName));
     }
 
     /// <summary>
     /// 删除文件夹。
     /// </summary>
     public static void Delete(string dirPath, bool toRecycleBin = false) {
-        dirPath = PathUtils.ToLongPath(dirPath);
         if (toRecycleBin) {
-            Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(dirPath,
-                Microsoft.VisualBasic.FileIO.UIOption.AllDialogs, 
-                Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin, 
-                Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException);
+            if (Exists(dirPath)) FileUtils.DeleteToRecycleBin(dirPath);
         } else {
-            Directory.Delete(dirPath);
+            Directory.Delete(PathUtils.WithLongPath(dirPath));
         }
     }
 }

+ 93 - 26
Shared/Utils/FileUtils.cs

@@ -1,4 +1,6 @@
-namespace MeloongCore;
+using System.Runtime.ExceptionServices;
+
+namespace MeloongCore;
 public static class FileUtils {
 
     #region 写入
@@ -9,7 +11,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, string text, Encoding? encoding = null) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        File.WriteAllText(PathUtils.ToLongPath(filePath), text, encoding ?? new UTF8Encoding());
+        File.WriteAllText(PathUtils.WithLongPath(filePath), text, encoding ?? new UTF8Encoding());
     }
 
     /// <summary>
@@ -18,7 +20,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, byte[] content) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        File.WriteAllBytes(PathUtils.ToLongPath(filePath), content);
+        File.WriteAllBytes(PathUtils.WithLongPath(filePath), content);
     }
 
     /// <summary>
@@ -27,7 +29,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, IEnumerable<byte> content) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        File.WriteAllBytes(PathUtils.ToLongPath(filePath), [..content]);
+        File.WriteAllBytes(PathUtils.WithLongPath(filePath), [..content]);
     }
 
     /// <summary>
@@ -37,57 +39,122 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, Stream stream) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        using FileStream fileStream = OpenCreate(PathUtils.ToLongPath(filePath));
+        using FileStream fileStream = OpenCreate(PathUtils.WithLongPath(filePath));
         if (stream.CanSeek && stream.Position != 0) stream.Seek(0, SeekOrigin.Begin);
         stream.CopyTo(fileStream);
     }
 
     #endregion
 
+    #region 删除
+
+    /// <summary>
+    /// 删除文件。
+    /// </summary>
+    public static void Delete(string filePath, bool toRecycleBin = false) {
+        if (toRecycleBin) {
+            if (Exists(filePath)) DeleteToRecycleBin(filePath);
+        } else {
+            File.Delete(PathUtils.WithLongPath(filePath));
+        }
+    }
+
+    /// <summary>
+    /// 将文件或文件夹删除到回收站。
+    /// </summary>
+    internal static void DeleteToRecycleBin(string target) {
+        // 实际的删除方法
+        static void Run(string filePath) {
+            IShellItem? item = null;
+            IFileOperation? op = null;
+            try {
+                var iid = typeof(IShellItem).GUID;
+                Marshal.ThrowExceptionForHR(SHCreateItemFromParsingName(PathUtils.WithoutLongPath(filePath), IntPtr.Zero, ref iid, out item));
+                op = (IFileOperation) new FileOperation();
+                op.SetOperationFlags(0x0040 | 0x0010 | 0x0004); // FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT
+                op.DeleteItem(item, IntPtr.Zero);
+                op.PerformOperations();
+                op.GetAnyOperationsAborted(out bool aborted);
+                if (aborted) throw new OperationCanceledException("Delete operation was aborted.");
+            } finally {
+                if (op != null && Marshal.IsComObject(op)) Marshal.FinalReleaseComObject(op);
+                if (item != null && Marshal.IsComObject(item)) Marshal.FinalReleaseComObject(item);
+            }
+        }
+        // 在 STA 线程中执行删除方法
+        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) {
+            Run(target);
+        } else {
+            ExceptionDispatchInfo? internalEx = null; // 捕获内部异常
+            var thread = new Thread(() => { try { Run(target); } catch (Exception ex) { internalEx = ExceptionDispatchInfo.Capture(ex); } });
+            thread.SetApartmentState(ApartmentState.STA);
+            thread.Start();
+            thread.Join();
+            internalEx?.Throw();
+        }
+    }
+    // 用于删除到回收站的接口
+    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
+    static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
+    [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    interface IShellItem { }
+    [ComImport, Guid("3AD05575-8857-4850-9277-11B85BDB8E09")]
+    class FileOperation { }
+    [ComImport, Guid("947AAB5F-0A5C-4C13-B4D6-4BF7836FC9F8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    interface IFileOperation {
+        void Advise(IntPtr pfops, out uint pdwCookie);
+        void Unadvise(uint dwCookie);
+        void SetOperationFlags(uint dwOperationFlags);
+        void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage);
+        void SetProgressDialog(IntPtr popd);
+        void SetProperties(IntPtr pproparray);
+        void SetOwnerWindow(IntPtr hwndOwner);
+        void ApplyPropertiesToItem(IShellItem psiItem);
+        void ApplyPropertiesToItems(IntPtr punkItems);
+        void RenameItem(IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, IntPtr pfopsItem);
+        void RenameItems(IntPtr pUnkItems, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
+        void MoveItem(IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, IntPtr pfopsItem);
+        void MoveItems(IntPtr punkItems, IShellItem psiDestinationFolder);
+        void CopyItem(IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszCopyName, IntPtr pfopsItem);
+        void CopyItems(IntPtr punkItems, IShellItem psiDestinationFolder);
+        void DeleteItem(IShellItem psiItem, IntPtr pfopsItem);
+        void DeleteItems(IntPtr punkItems);
+        void NewItem(IShellItem psiDestinationFolder, uint dwFileAttributes, [MarshalAs(UnmanagedType.LPWStr)] string pszName, [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, IntPtr pfopsItem);
+        void PerformOperations();
+        void GetAnyOperationsAborted([MarshalAs(UnmanagedType.Bool)] out bool pfAnyOperationsAborted);
+    }
+
+    #endregion
+
     #region FileStream
 
     /// <summary>
     /// 打开该文件的只读 <see cref="FileStream"/>。
     /// </summary>
     public static FileStream OpenRead(string filePath) =>
-        new(PathUtils.ToLongPath(filePath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+        new(PathUtils.WithLongPath(filePath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
 
     /// <summary>
     /// 在指定路径创建文件,并打开 <see cref="FileStream"/>。
     /// </summary>
     public static FileStream OpenCreate(string filePath) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        return new(PathUtils.ToLongPath(filePath), FileMode.Create);
+        return new(PathUtils.WithLongPath(filePath), FileMode.Create);
     }
 
     #endregion
 
-    /// <summary>
-    /// 删除文件。
-    /// </summary>
-    public static void Delete(string filePath, bool toRecycleBin = false) {
-        filePath = PathUtils.ToLongPath(filePath);
-        if (toRecycleBin) {
-            Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(filePath,
-                Microsoft.VisualBasic.FileIO.UIOption.AllDialogs,
-                Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin,
-                Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException);
-        } else {
-            File.Delete(filePath);
-        }
-    }
-
     /// <summary>
     /// 确定指定的文件是否存在。
     /// </summary>
     public static bool Exists(string filePath) => 
-        File.Exists(PathUtils.ToLongPath(filePath));
+        File.Exists(PathUtils.WithLongPath(filePath));
 
     /// <summary>
     /// 创建 <see cref="FileInfo"/> 对象。
     /// </summary>
     public static FileInfo GetInfo(string path) => 
-        new(PathUtils.ToLongPath(path));
+        new(PathUtils.WithLongPath(path));
 
     /// <summary>
     /// 复制文件。
@@ -96,7 +163,7 @@ public static class FileUtils {
     public static void Copy(string sourceFilePath, string destFilePath) {
         if (sourceFilePath == destFilePath) return; // 如果复制同一个文件则跳过
         DirectoryUtils.Create(destFilePath, isFilePath: true);
-        File.Copy(PathUtils.ToLongPath(sourceFilePath), PathUtils.ToLongPath(destFilePath), true);
+        File.Copy(PathUtils.WithLongPath(sourceFilePath), PathUtils.WithLongPath(destFilePath), true);
     }
 
     /// <summary>
@@ -106,7 +173,7 @@ public static class FileUtils {
     public static void Move(string sourceFilePath, string destFilePath) {
         if (sourceFilePath == destFilePath) return; // 如果移动同一个文件则跳过
         DirectoryUtils.Create(destFilePath, isFilePath: true);
-        File.Move(PathUtils.ToLongPath(sourceFilePath), PathUtils.ToLongPath(destFilePath));
+        File.Move(PathUtils.WithLongPath(sourceFilePath), PathUtils.WithLongPath(destFilePath));
     }
 
 }

+ 10 - 3
Shared/Utils/PathUtils.cs

@@ -27,8 +27,9 @@ public static class PathUtils {
     /// 若输入的是文件夹路径,不保证其结尾是否有文件夹分隔符。
     /// </summary>
     public static string ToShortPath(string fullName, bool keepFileName = false) {
-        if (string.IsNullOrEmpty(fullName) || fullName.Length <= 200) return fullName;
-        fullName = fullName.Replace('/', '\\');
+        if (string.IsNullOrEmpty(fullName)) return fullName;
+        fullName = PathUtils.WithoutLongPath( fullName.Replace('/', '\\'));
+        if (fullName.Length <= 200) return fullName;
 
         // 保留文件名
         string pathToKeep = "";
@@ -62,13 +63,19 @@ public static class PathUtils {
     /// 将路径转换为以 \\?\ 开头的标准长路径格式。
     /// 这会去除路径末尾的分隔符,且将 / 替换为 \。
     /// </summary>
-    public static string ToLongPath(string path) {
+    public static string WithLongPath(string path) {
         if (string.IsNullOrWhiteSpace(path)) return path;
         if (path.StartsWithF(@"\\")) return path; // 已有长路径前缀
         path = WithoutSeparator(path).Replace('/', '\\'); // API 要求这个格式……
         return $@"\\?\{path}";
     }
 
+    /// <summary>
+    /// 去除路径开头的 \\?\。
+    /// </summary>
+    public static string WithoutLongPath(string path) =>
+        path.AfterLast(@"\\?\");
+
     #endregion
 
 }