Selaa lähdekoodia

fix: 长路径支持

龙腾猫跃 5 kuukautta sitten
vanhempi
sitoutus
70bec4025a

+ 16 - 0
Shared/Extensions/StringExtensions.cs

@@ -14,6 +14,14 @@ public static class StringExtensions {
         return value.StartsWith(prefix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
     }
     /// <summary>
+    /// 高速的 StartsWith。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool StartsWithF(this string? value, char prefix, bool ignoreCase = false) {
+        if (value is null) return false;
+        return value.StartsWith(prefix.ToString(), ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
+    /// <summary>
     /// 高速的 EndsWith。
     /// </summary>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -21,6 +29,14 @@ public static class StringExtensions {
         if (value is null) return false;
         return value.EndsWith(suffix, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
     }
+    /// <summary>
+    /// 高速的 EndsWith。
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool EndsWithF(this string? value, char suffix, bool ignoreCase = false) {
+        if (value is null) return false;
+        return value.EndsWith(suffix.ToString(), ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+    }
 
     /// <summary>
     /// 忽略大小写的 Contains。

+ 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, 
-            FileUtils.ShortenPath(oldFilePath), FileUtils.ShortenPath(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, FileUtils.ShortenPath(deltaFilePath)))
+            PathUtils.ToLongPath(oldFilePath), PathUtils.ToLongPath(newFilePath), null, null, new DeltaInput(), IntPtr.Zero, 32u, PathUtils.ToLongPath(deltaFilePath)))
             throw new Win32Exception();
     }
 
@@ -31,7 +31,7 @@ public static class MsDelta {
         if (!File.Exists(deltaFilePath)) throw new FileNotFoundException($"补丁文件不存在:{deltaFilePath}", deltaFilePath);
         DirectoryUtils.Create(newFilePath, isFilePath: true);
         // 1L: AllowLegacy
-        if (!ApplyDelta(1L, FileUtils.ShortenPath(oldFilePath), FileUtils.ShortenPath(deltaFilePath), FileUtils.ShortenPath(newFilePath)))
+        if (!ApplyDelta(1L, PathUtils.ToLongPath(oldFilePath), PathUtils.ToLongPath(deltaFilePath), PathUtils.ToLongPath(newFilePath)))
             throw new Win32Exception();
     }
 

+ 1 - 13
Shared/Utils/DirectoryUtils.cs

@@ -1,25 +1,13 @@
 namespace MeloongCore;
 public static class DirectoryUtils {
 
-    /// <summary>
-    /// 确保路径以 / 或 \ 结尾。
-    /// </summary>
-    public static string ToDirectoryFormat(string folder) => IsDirectoryFormat(folder) ? folder : folder + Path.DirectorySeparatorChar;
-
-    /// <summary>
-    /// 检查路径是否以 / 或 \ 结尾。
-    /// </summary>
-    public static bool IsDirectoryFormat(string path) => path.EndsWithF("/") || path.EndsWithF("\\");
-
     /// <summary>
     /// 创建文件夹,或文件所在的文件夹。
-    /// 支持长路径。
     /// 文件夹已存在时不会抛出异常。
     /// </summary>
     public static void Create(string path, bool isFilePath = false) {
-        path = FileUtils.ShortenPath(path);
         if (isFilePath) path = Path.GetDirectoryName(path);
-        Directory.CreateDirectory(path);
+        Directory.CreateDirectory(PathUtils.ToLongPath(path));
     }
 
 }

+ 3 - 43
Shared/Utils/FileUtils.cs

@@ -9,7 +9,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, string text, Encoding? encoding = null) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        File.WriteAllText(filePath, text, encoding ?? new UTF8Encoding());
+        File.WriteAllText(PathUtils.ToLongPath(filePath), text, encoding ?? new UTF8Encoding());
     }
 
     /// <summary>
@@ -18,7 +18,7 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, byte[] content) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        File.WriteAllBytes(filePath, content);
+        File.WriteAllBytes(PathUtils.ToLongPath(filePath), content);
     }
 
     /// <summary>
@@ -28,51 +28,11 @@ public static class FileUtils {
     /// </summary>
     public static void Write(string filePath, Stream stream) {
         DirectoryUtils.Create(filePath, isFilePath: true);
-        using FileStream fileStream = new(filePath, FileMode.Create, FileAccess.Write);
+        using FileStream fileStream = new(PathUtils.ToLongPath(filePath), FileMode.Create, FileAccess.Write);
         if (stream.CanSeek && stream.Position != 0) stream.Seek(0, SeekOrigin.Begin);
         stream.CopyTo(fileStream);
     }
 
     #endregion
 
-    #region 短路径
-
-    /// <summary>
-    /// 若路径较长,则尽量将其转换为短路径。
-    /// 若输入的是文件夹路径,不保证其结尾是否有 \ 。
-    /// </summary>
-    public static string ShortenPath(string fullName, bool keepFileName = false) {
-        if (string.IsNullOrEmpty(fullName) || fullName.Length <= 200) return fullName;
-        fullName = fullName.Replace('/', '\\');
-
-        // 保留文件名
-        string pathToKeep = "";
-        string pathToShorten = fullName;
-        if (fullName.EndsWithF(".jar", true)) keepFileName = true; // jar 文件的文件名需要保留原样,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
-        if (keepFileName && File.Exists(fullName)) {
-            pathToKeep = Path.GetFileName(fullName);
-            pathToShorten = Path.GetDirectoryName(fullName);
-        }
-
-        // 逐级向上寻找已存在的文件夹,将不存在的部分挪到 suffix,不再缩短
-        while (!Directory.Exists(pathToShorten) && !File.Exists(pathToShorten)) { // 如果路径不存在
-            string parentPath = Path.GetDirectoryName(pathToShorten);
-            if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return fullName; // 已经到达根目录,全都不存在,直接返回
-            pathToKeep = Path.Combine(Path.GetFileName(pathToShorten), pathToKeep);
-            pathToShorten = parentPath;
-        }
-        if (pathToShorten.Length <= 10) return fullName;
-
-        // 缩短路径
-        char[] buffer = new char[260];
-        int result = GetShortPathNameW(pathToShorten, buffer, buffer.Length);
-        if (result == 0) return fullName;
-        string shortPath = new(buffer, 0, result);
-        return Path.Combine(shortPath, pathToKeep);
-    }
-    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
-    private static extern int GetShortPathNameW(string lpszLongPath, [Out] char[] buffer, int cchBuffer);
-
-    #endregion
-
 }

+ 73 - 0
Shared/Utils/PathUtils.cs

@@ -0,0 +1,73 @@
+namespace MeloongCore;
+public static class PathUtils {
+
+    #region 分隔符
+
+    /// <summary>
+    /// 检查路径是否以文件夹分隔符结尾。
+    /// </summary>
+    public static bool EndsWithSeparator(string path) => path.EndsWithF(Path.DirectorySeparatorChar) || path.EndsWithF(Path.AltDirectorySeparatorChar);
+
+    /// <summary>
+    /// 确保路径的结尾包含文件夹分隔符。
+    /// </summary>
+    public static string WithSeparator(string folder) => EndsWithSeparator(folder) ? folder : folder + Path.DirectorySeparatorChar;
+
+    /// <summary>
+    /// 确保路径的结尾不包含文件夹分隔符。
+    /// </summary>
+    public static string WithoutSeparator(string folder) => EndsWithSeparator(folder) ? folder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) : folder;
+
+    #endregion
+
+    #region 长路径
+
+    /// <summary>
+    /// 若路径较长,则尽量将其转换为短路径。
+    /// 若输入的是文件夹路径,不保证其结尾是否有文件夹分隔符。
+    /// </summary>
+    public static string Shorten(string fullName, bool keepFileName = false) {
+        if (string.IsNullOrEmpty(fullName) || fullName.Length <= 200) return fullName;
+        fullName = fullName.Replace('/', '\\');
+
+        // 保留文件名
+        string pathToKeep = "";
+        string pathToShorten = fullName;
+        if (fullName.EndsWithF(".jar", true)) keepFileName = true; // jar 文件的文件名需要保留原样,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
+        if (keepFileName && File.Exists(fullName)) {
+            pathToKeep = Path.GetFileName(fullName);
+            pathToShorten = Path.GetDirectoryName(fullName);
+        }
+
+        // 逐级向上寻找已存在的文件夹,将不存在的部分挪到 suffix,不再缩短
+        while (!Directory.Exists(pathToShorten) && !File.Exists(pathToShorten)) { // 如果路径不存在
+            string parentPath = Path.GetDirectoryName(pathToShorten);
+            if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return fullName; // 已经到达根目录,全都不存在,直接返回
+            pathToKeep = Path.Combine(Path.GetFileName(pathToShorten), pathToKeep);
+            pathToShorten = parentPath;
+        }
+        if (pathToShorten.Length <= 10) return fullName;
+
+        // 缩短路径
+        char[] buffer = new char[260];
+        int result = GetShortPathNameW(pathToShorten, buffer, buffer.Length);
+        if (result == 0) return fullName;
+        string shortPath = new(buffer, 0, result);
+        return Path.Combine(shortPath, pathToKeep);
+    }
+    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+    private static extern int GetShortPathNameW(string lpszLongPath, [Out] char[] buffer, int cchBuffer);
+
+    /// <summary>
+    /// 将路径转换为以 \\?\ 开头的长路径格式。
+    /// 这也会去除路径末尾的文件夹分隔符。
+    /// </summary>
+    public static string ToLongPath(string path) {
+        if (string.IsNullOrWhiteSpace(path)) return path;
+        if (path.StartsWithF(@"\\")) return path; // 已有长路径前缀
+        return $@"\\?\{WithoutSeparator(path)}"; // API 要求不包含分隔符
+    }
+
+    #endregion
+
+}