Browse Source

优化解压进度处理

龙腾猫跃 6 months ago
parent
commit
e74fe60198
2 changed files with 27 additions and 14 deletions
  1. 16 11
      Shared/Utils/FileUtils.cs
  2. 11 3
      Shared/Utils/PathUtils.cs

+ 16 - 11
Shared/Utils/FileUtils.cs

@@ -1,5 +1,4 @@
 using System.IO.Compression;
-using System.Text;
 
 namespace MeloongCore;
 public static class FileUtils {
@@ -162,10 +161,12 @@ public static class FileUtils {
                 throw;
             }
         }
-        try { // 尝试两种编码
-            return TryOpen(new UTF8Encoding(false, true));
-        } catch (DecoderFallbackException) {
-            return TryOpen(Encoding.GetEncoding("GB18030"));
+        try {
+            try { // 尝试两种编码
+                return TryOpen(new UTF8Encoding(false, true));
+            } catch (DecoderFallbackException) {
+                return TryOpen(Encoding.GetEncoding("GB18030"));
+            }
         } catch (InvalidDataException ex) {
             throw new InvalidDataException($"文件不是压缩包,或者文件已损坏({zipFilePath})", ex);
         }
@@ -175,24 +176,28 @@ public static class FileUtils {
     /// 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
     /// 会自动创建文件夹。会覆盖已有文件,但不会删除多余文件。
     /// </summary>
-    public static void ExtractToDirectory(string compressionFile, string outputDirectory, Action<double>? progressIncrementHandler = null) {
+    /// <param name="progressHandler">参数为已完成的总比例(0~1)。</param>
+    public static void ExtractToDirectory(string compressionFile, string outputDirectory, Action<double>? progressHandler = null) {
         compressionFile = PathUtils.WithLongPath(compressionFile);
         DirectoryUtils.Create(outputDirectory);
         // 解压 gz(gz 不需要考虑编码)
         if (compressionFile.EndsWithF(".gz", true)) {
-            using var stream = new GZipStream(FileUtils.ReadAsStream(compressionFile), CompressionMode.Decompress);
-            FileUtils.Write(Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(compressionFile)), stream);
+            using var fileStream = FileUtils.ReadAsStream(compressionFile);
+            using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
+            FileUtils.Write(Path.Combine(outputDirectory, Path.GetFileNameWithoutExtension(compressionFile)), gzipStream);
             return;
         }
         // 解压 zip
         using var archive = FileUtils.OpenZip(compressionFile);
-        int entryCount = archive.Entries.Count;
+        int totalCount = archive.Entries.Count;
+        int doneCount = 0;
         foreach (var entry in archive.Entries) {
-            if (progressIncrementHandler != null && entryCount > 0) progressIncrementHandler(1.0 / entryCount);
+            doneCount++;
+            if (progressHandler != null && totalCount > 0) progressHandler((double) doneCount / totalCount);
             if (string.IsNullOrEmpty(entry.Name)) continue; // 跳过文件夹条目(ZipArchive 会将文件夹也作为一个 entry,但它们的 Name 为空)
             // ZipSlip 修复
             string outputFilePath = PathUtils.WithLongPath(Path.GetFullPath(Path.Combine(outputDirectory, entry.FullName)));
-            if (!outputFilePath.StartsWithF(PathUtils.WithSeparator(PathUtils.WithLongPath(Path.GetFullPath(outputDirectory)))))
+            if (!outputFilePath.StartsWithF(PathUtils.WithSeparator(PathUtils.WithLongPath(Path.GetFullPath(outputDirectory))), ignoreCase:true))
                 throw new UnauthorizedAccessException($"Zip 文件项 {entry.FullName} 的路径在压缩包之外,这可能导致安全问题");
             // 实际的解压
             using var entryStream = entry.Open();

+ 11 - 3
Shared/Utils/PathUtils.cs

@@ -6,12 +6,16 @@ public static class PathUtils {
     /// <summary>
     /// 确保路径的结尾包含文件夹分隔符。
     /// </summary>
-    public static string WithSeparator(string folder) => folder.EndsWithF(Path.DirectorySeparatorChar) ? folder : folder + Path.DirectorySeparatorChar;
+    public static string WithSeparator(string folder) {
+        return folder.EndsWithF(Path.DirectorySeparatorChar) ? folder : folder + Path.DirectorySeparatorChar;
+    }
 
     /// <summary>
     /// 确保路径的结尾不包含文件夹分隔符。
     /// </summary>
-    public static string WithoutSeparator(string folder) => folder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+    public static string WithoutSeparator(string folder) {
+        return folder.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+    }
 
     #endregion
 
@@ -62,7 +66,11 @@ public static class PathUtils {
         if (string.IsNullOrWhiteSpace(path)) return path;
         if (path.StartsWithF(@"\\?\")) return path; // 已经是长路径
         path = WithoutSeparator(path).Replace('/', '\\'); // API 要求这个格式……
-        return $@"\\?\{path}";
+        if (path.StartsWithF(@"\\")) {
+            return $@"\\?\UNC\{path.Substring(2)}";
+        } else {
+            return $@"\\?\{path}";
+        }
     }
 
     /// <summary>