Kaynağa Gözat

路径检查

龙腾猫跃 5 ay önce
ebeveyn
işleme
f760befa26

+ 1 - 1
Shared/General/Logger.cs

@@ -221,7 +221,7 @@ public class BaseLogger {
             .Join("\r\n");
     }
     public static string GetLogPrefix(LogLevel level, string filePath)
-        => $"{DateTime.Now:HH':'mm':'ss'.'fff} {level.ToString().First()} {(Thread.CurrentThread.Name is null ? "" : $"<{Thread.CurrentThread.Name}> ")}[{Path.GetFileName(filePath).BeforeFirst(".")}] ";
+        => $"{DateTime.Now:HH':'mm':'ss'.'fff} {level.ToString().First()} {(Thread.CurrentThread.Name is null ? "" : $"<{Thread.CurrentThread.Name}> ")}[{PathUtils.GetLastPart(filePath).BeforeFirst(".")}] ";
 
     /// <summary>
     /// 输出格式化后的日志文本。

+ 1 - 1
Shared/Utils/DirectoryUtils.cs

@@ -110,7 +110,7 @@ public static class DirectoryUtils {
                 } catch (DirectoryNotFoundException ex) { // #4549,也可能已被其他线程删除
                     if (DirectoryUtils.Exists(folder)) {
                         Logger.Warn(ex, $"该文件夹可能为孤立的符号链接,尝试直接删除({folder})");
-                        Directory.Delete(folder);
+                        Directory.Delete(folder, true);
                     } else {
                         throw;
                     }

+ 2 - 2
Shared/Utils/FileUtils.cs

@@ -260,7 +260,7 @@ public static class FileUtils {
         DirectoryUtils.Create(outputDirectory);
         // 解压 gz(gz 不需要考虑编码)
         if (compressionFile.EndsWithF(".gz", true)) {
-            string outFilePath = Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(compressionFile));
+            string outFilePath = Path.Combine(outputDirectory, PathUtils.GetFileNameWithoutExtension(compressionFile));
             Logger.Trace($"解压 gz 文件:{compressionFile} → {outFilePath}");
             using var fileStream = FileUtils.ReadAsStream(compressionFile);
             using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
@@ -309,7 +309,7 @@ public static class FileUtils {
     public static void CreateZipFromFiles(string outputFullPath, params string[] sourceFiles) {
         var sources = new Dictionary<string, string>();
         foreach (var source in sourceFiles) {
-            string fileName = Path.GetFileName(source);
+            string fileName = PathUtils.GetLastPart(source);
             if (sources.ContainsKey(fileName)) throw new ArgumentException($"尝试将多个同文件名的文件放进压缩包中({fileName})", nameof(sourceFiles));
             sources.Add(fileName, source);
         }

+ 47 - 4
Shared/Utils/PathUtils.cs

@@ -33,7 +33,7 @@ public static class PathUtils {
         string pathToShorten = fullName;
         if (fullName.EndsWithF(".jar", true)) keepFileName = true; // jar 文件的文件名需要保留原样,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
         if (keepFileName && FileUtils.Exists(fullName)) {
-            pathToKeep = Path.GetFileName(fullName);
+            pathToKeep = PathUtils.GetLastPart(fullName);
             pathToShorten = PathUtils.RemoveLastPart(fullName);
         }
 
@@ -41,7 +41,7 @@ public static class PathUtils {
         while (!DirectoryUtils.Exists(pathToShorten) && !FileUtils.Exists(pathToShorten)) { // 如果路径不存在
             string parentPath = Path.GetDirectoryName(pathToShorten);
             if (string.IsNullOrEmpty(parentPath) || parentPath == pathToShorten) return fullName; // 已经到达根目录,全都不存在,直接返回
-            pathToKeep = Path.Combine(Path.GetFileName(pathToShorten), pathToKeep);
+            pathToKeep = Path.Combine(PathUtils.GetLastPart(pathToShorten), pathToKeep);
             pathToShorten = parentPath;
         }
         if (pathToShorten.Length <= 10) return fullName;
@@ -88,12 +88,12 @@ public static class PathUtils {
     /// 去除路径的最后一级,并移除末尾的分隔符。
     /// <code>
     /// "C:\foo\bar.txt" → "C:\foo"
-    /// "C:\foo\bar" → "C:\foo"
     /// "C:\foo\bar\" → "C:\foo"
+    /// "C:\foo\bar" → "C:\foo"
     /// "C:\foo\" → "C:"
     /// "https://foo.bar/file/pack.zip?arg=1" → "https://foo.bar/file"
     /// </code></summary>
-    public static string RemoveLastPart(string? path) {
+    public static string RemoveLastPart(string path) {
         if (path!.Contains("://")) { // 网络路径
             path = PathUtils.WithoutSeparator(path.BeforeFirst("#").BeforeFirst("?")); // 去除参数
             return PathUtils.WithoutSeparator(path).BeforeLast("/");
@@ -102,6 +102,49 @@ public static class PathUtils {
         }
     }
 
+    /// <summary>
+    /// 获取路径的最后一级,即文件名,或当前文件夹名。
+    /// <code>
+    /// "C:\foo\bar.txt" → "bar.txt"
+    /// "C:\foo\bar\" → "bar"
+    /// "C:\foo\bar" → "bar"
+    /// "https://foo.bar/file/pack.zip?arg=1" → "pack.zip"
+    /// </code></summary>
+    public static string GetLastPart(string path) {
+        if (path!.Contains("://")) { // 网络路径
+            path = PathUtils.WithoutSeparator(path.BeforeFirst("#").BeforeFirst("?")); // 去除参数
+            return path.AfterLast("/");
+        } else { // 文件路径
+            return PathUtils.WithoutSeparator(path).AfterLast("\\");
+        }
+    }
+
+    /// <summary>
+    /// 获取路径或文件名中,仅不包含最后一级扩展名的部分。
+    /// <code>
+    /// "C:\foo\bar.txt" → "bar"
+    /// "create.jar.disabled" → "create.jar"
+    /// "https://foo.bar/file/page.xaml.vb?arg=1" → "page.xaml"
+    /// </code>
+    /// </summary>
+    public static string GetFileNameWithoutExtension(string path) 
+        => GetLastPart(path).BeforeLast(".");
+
+    /// <summary>
+    /// 获取路径或文件名中的最后一级文件扩展名,不包含 <c>.</c>,固定小写。
+    /// 如果没有 <c>.</c> 则返回空字符串。
+    /// <code>
+    /// "C:\FOO\BAR.TXT" → "txt"
+    /// "create.jar.disabled" → "disabled"
+    /// "C:\foo\bar" → ""
+    /// "https://foo.bar/file/page.xaml.vb?arg=1" → "vb"
+    /// </code>
+    /// </summary>
+    public static string GetExtension(string path) {
+        var name = GetLastPart(path);
+        return name.Contains(".") ? name.AfterLast(".").Lower() : "";
+    }
+
     /// <summary>
     /// 将路径转换为标准格式:将短路径展开,将分隔符改为 \,去除前导的 <c>\\?\</c>,统一末尾是否包含分隔符。
     /// </summary>

+ 1 - 1
Tests/TestUtils.cs

@@ -25,7 +25,7 @@ public abstract class TestWithFolder {
     /// </summary>
     public string GetTestFile(string fileName) {
         var sourceFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", GetType().Name.Replace("Test", ""), fileName);
-        var distPath = Path.Combine(tempFolder, Path.GetFileName(sourceFilePath));
+        var distPath = Path.Combine(tempFolder, PathUtils.GetLastPart(sourceFilePath));
         FileUtils.Copy(sourceFilePath, distPath);
         return distPath;
     }

+ 1 - 1
Tests/Utils/FileUtilsTest.cs

@@ -24,7 +24,7 @@ public class FileUtilsTest : TestWithFolder {
         string output = Path.Combine(tempFolder, "Extracted");
         FileUtils.ExtractToDirectory(GetTestFile(testFile), output, p => progress = p);
         Assert.Equal(1, progress);
-        Assert.Contains(containsText, File.ReadAllText(PathUtils.WithLongPath(Path.Combine(output, Path.GetFileNameWithoutExtension(testFile)))));
+        Assert.Contains(containsText, File.ReadAllText(PathUtils.WithLongPath(Path.Combine(output, PathUtils.GetFileNameWithoutExtension(testFile)))));
     }
 
     [Theory]

+ 55 - 1
Tests/Utils/PathUtilsTest.cs

@@ -14,7 +14,61 @@ public class PathUtilsTests {
     [InlineData("https://foo.bar/file/", "https://foo.bar")]
     [InlineData("https://foo.bar/file/?arg=1", "https://foo.bar")]
     [InlineData("https://foo.bar/file?arg=1", "https://foo.bar")]
-    public void 路径处理_RemoveLastPart(string? input, string expected) 
+    public void 路径处理_RemoveLastPart(string input, string expected) 
         => Assert.Equal(expected, PathUtils.RemoveLastPart(input));
 
+    [Theory]
+    [InlineData(@"\\?\C:\foo\bar.txt", "bar.txt")]
+    [InlineData(@"C:\foo\bar.txt", "bar.txt")]
+    [InlineData(@"\\?\C:\foo\bar", "bar")]
+    [InlineData(@"C:\foo\bar", "bar")]
+    [InlineData(@"C:\foo\bar\", "bar")]
+    [InlineData(@"\\?\C:\foo\", "foo")]
+    [InlineData(@"C:\foo", "foo")]
+    [InlineData(@"C:\foo\", "foo")]
+    [InlineData("https://foo.bar/file/long/pack.zip?arg=1", "pack.zip")]
+    [InlineData("https://foo.bar/file/", "file")]
+    [InlineData("https://foo.bar/file/?arg=1", "file")]
+    [InlineData("https://foo.bar/file?arg=1", "file")]
+    public void 路径处理_GetLastPart(string input, string expected)
+        => Assert.Equal(expected, PathUtils.GetLastPart(input));
+
+    [Theory]
+    [InlineData(@"\\?\C:\foo\bar.txt", "bar")]
+    [InlineData(@"C:\foo\bar.2.txt", "bar.2")]
+    [InlineData(@"\\?\C:\foo\bar", "bar")]
+    [InlineData(@"C:\foo\bar", "bar")]
+    [InlineData(@"C:\foo.bar\how\", "how")]
+    [InlineData(@"\\?\C:\foo\", "foo")]
+    [InlineData(@"C:\foo", "foo")]
+    [InlineData(@"C:\foo\", "foo")]
+    [InlineData("create.jar.disabled", "create.jar")]
+    [InlineData("create.jar", "create")]
+    [InlineData("create", "create")]
+    [InlineData("https://foo.bar/file/page.xaml.vb?arg=1", "page.xaml")]
+    [InlineData("https://foo.bar/file/long/pack.zip?arg=1", "pack")]
+    [InlineData("https://foo.bar/some.how/file/", "file")]
+    [InlineData("https://foo.bar/file/?arg=1", "file")]
+    [InlineData("https://foo.bar/file?arg=1", "file")]
+    public void 路径处理_GetFileNameWithoutExtension(string input, string expected)
+        => Assert.Equal(expected, PathUtils.GetFileNameWithoutExtension(input));
+
+    [Theory]
+    [InlineData(@"\\?\C:\FOO\BAR.TXT", "txt")]
+    [InlineData(@"C:\foo\bar.txt", "txt")]
+    [InlineData(@"\\?\C:\foo\bar", "")]
+    [InlineData(@"C:\foo\bar", "")]
+    [InlineData(@"C:\foo\bar\", "")]
+    [InlineData(@"\\?\C:\foo\", "")]
+    [InlineData(@"C:\foo", "")]
+    [InlineData(@"C:\foo\", "")]
+    [InlineData("create.jar.diSAbled", "disabled")]
+    [InlineData("https://foo.bar/file/page.xaml.vb?arg=1", "vb")]
+    [InlineData("https://foo.bar/file/long/pack.ZIP?arg=1", "zip")]
+    [InlineData("https://foo.bar/file/", "")]
+    [InlineData("https://foo.bar/file/?arg=1", "")]
+    [InlineData("https://foo.bar/file?arg=1", "")]
+    public void 路径处理_GetExtension(string input, string expected)
+        => Assert.Equal(expected, PathUtils.GetExtension(input));
+
 }