Просмотр исходного кода

增加Progress的IsFinished函数;修改解压ZIP的重试逻辑以及中途取消解压的参数

HerobrineXia 2 месяцев назад
Родитель
Сommit
ddad46e30d

+ 5 - 3
Shared/General/Progress.cs

@@ -159,16 +159,18 @@ public class ProgressProvider {
         }
     }
 
+    public bool IsFinished => GetRaw().actual + GetRaw().skiped > 0.9999999;
+
     private (double actual, double skiped) observedProgress = (0, 0);
     private double incrementProgress = 0;
     /// <summary>
     /// 获取一个在多次观测之间必定单调递增的进度值,范围为 [0, 1]。
     /// </summary>
-    public double GetIncrement() {
+    public double GetIncreasing() {
         lock (this) {
             (double actual, double skiped) current = GetRaw();
             if (current == observedProgress) return incrementProgress; // 未改变
-            if (current.actual + current.skiped > 0.9999999) { // 已完成
+            if (IsFinished) { // 已完成
                 incrementProgress = 1;
             } else if (current.actual >= observedProgress.actual) { // 进度增加
                 incrementProgress += (1 - incrementProgress) *
@@ -197,7 +199,7 @@ public class ProgressObserver : RateLimitedWorker {
     }
     private void StartObserver((double actual, double skiped) raw) => Start();
     private static void Observe(ProgressProvider provider, Action<double> updateAction) {
-        double progress = provider.GetIncrement();
+        double progress = provider.GetIncreasing();
         // TODO: 添加平缓过渡
         updateAction(progress);
     }

+ 21 - 17
Shared/Utils/FileUtils.cs

@@ -325,7 +325,7 @@ public static class FileUtils {
     /// 以只读模式打开压缩文件。
     /// 会先尝试 UTF8 编码,失败后换用 GB18030 编码。
     /// </summary>
-    public static ZipArchive OpenZip(string zipFilePath) {
+    public static ZipArchive OpenZip(string zipFilePath, CancellationToken c = default) {
         ZipArchive TryOpen(Encoding encoding) {
             Logger.Trace($"尝试以 {encoding.EncodingName} 编码打开压缩包:{zipFilePath}");
             var result = ZipFile.Open(PathUtils.ForApi(zipFilePath), ZipArchiveMode.Read, encoding);
@@ -337,23 +337,26 @@ public static class FileUtils {
                 throw;
             }
         }
-        try {
-            try { // 尝试两种编码
-                return TryOpen(new UTF8Encoding(false, true));
-            } catch (DecoderFallbackException) {
-                return TryOpen(Encoding.GetEncoding("GB18030"));
+        return Retrier.Attempt(delay: _ => TimeSpan.FromMilliseconds(200), maxAttempts:10, isRetryAllowed: ex => ex is IOException, cancellationToken: c, func: _ => {
+            try {
+                try { // 尝试两种编码
+                    return TryOpen(new UTF8Encoding(false, true));
+                } catch (DecoderFallbackException) {
+                    return TryOpen(Encoding.GetEncoding("GB18030"));
+                }
+            } catch (InvalidDataException ex) {
+                throw new InvalidDataException($"文件不是压缩包,或者文件已损坏({zipFilePath})", ex);
             }
-        } catch (InvalidDataException ex) {
-            throw new InvalidDataException($"文件不是压缩包,或者文件已损坏({zipFilePath})", ex);
-        }
+        });
+        
     }
 
     /// <summary>
     /// 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
     /// 会自动创建文件夹。会覆盖已有文件,但不会删除多余文件。
     /// </summary>
-    /// <param name="progressHandler">参数为已完成的总比例(0~1)。</param>
-    public static void ExtractToDirectory(string compressionFile, string outputDirectory, Action<double>? progressHandler = null) {
+    /// <param name="p">参数为已完成的总比例(0~1)。</param>
+    public static void ExtractToDirectory(string compressionFile, string outputDirectory, CancellationToken c = default, ProgressProvider? p = null) {
         compressionFile = PathUtils.ForApi(compressionFile);
         DirectoryUtils.Create(outputDirectory);
         // 解压 gz(gz 不需要考虑编码)
@@ -363,18 +366,19 @@ public static class FileUtils {
             using var fileStream = FileUtils.ReadAsStream(compressionFile);
             using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
             FileUtils.Write(outFilePath, gzipStream);
-            progressHandler?.Invoke(1);
+            p?.Finish();
             return;
         }
         // 解压 zip
-        using var archive = FileUtils.OpenZip(compressionFile);
-        int totalCount = archive.Entries.Count;
+        using var archive = FileUtils.OpenZip(compressionFile, c);
+        var entries = archive.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList(); // 跳过文件夹条目(ZipArchive 会将文件夹也作为一个 entry,但它们的 Name 为空)
+        int totalCount = entries.Count;
         int doneCount = 0;
         Logger.Trace($"解压 zip 文件:{compressionFile} → {outputDirectory}(共 {totalCount} 项)");
-        foreach (var entry in archive.Entries) {
+        foreach (var entry in entries) {
+            c.ThrowIfCancellationRequested();
             doneCount++;
-            if (totalCount > 0) progressHandler?.Invoke((double) doneCount / totalCount);
-            if (string.IsNullOrEmpty(entry.Name)) continue; // 跳过文件夹条目(ZipArchive 会将文件夹也作为一个 entry,但它们的 Name 为空)
+            if (totalCount > 0) p?.Set((double) doneCount / totalCount);
             // ZipSlip 修复
             string outputFilePath = Path.Combine(outputDirectory, entry.FullName);
             if (!PathUtils.IsParentOf(outputDirectory, outputFilePath))

+ 1 - 1
Tests/General/ProgressTest.cs

@@ -46,7 +46,7 @@ public class ProgressTest : TestBase {
         progress.Set(0.75, skiped: true);
 
         await Assert.That(changedCount).IsEqualTo(2);
-        await Assert.That(Math.Abs(progress.GetIncrement() - 2.0 / 3) < 0.000001).IsTrue();
+        await Assert.That(Math.Abs(progress.GetIncreasing() - 2.0 / 3) < 0.000001).IsTrue();
     }
 
     [Test]

+ 6 - 6
Tests/Utils/FileUtilsTest.cs

@@ -7,10 +7,10 @@ public class FileUtilsTest : TestWithFolder {
     [Arguments("GB Encoding.zip")]
     [Arguments("UTF8 Encoding.zip")]
     public async Task 解压_ReadZip(string testFile) {
-        double progress = 0;
         string output = Path.Combine(tempFolder, "Extracted");
-        FileUtils.ExtractToDirectory(GetTestFile(testFile), output, p => progress = p);
-        await Assert.That(progress).IsEqualTo(1);
+        var p = new ProgressProvider();
+        FileUtils.ExtractToDirectory(GetTestFile(testFile), output, p: p);
+        await Assert.That(p.IsFinished).IsTrue();
         await Assert.That(DirectoryUtils.Exists(Path.Combine(output, "文件夹"))).IsTrue();
         await Assert.That(DirectoryUtils.Exists(Path.Combine(output, "空文件夹"))).IsFalse();
         await Assert.That(File.ReadAllText(PathUtils.ToExtendedFormat(Path.Combine(output, "fabricloader.log")))).Contains("FabricLoader");
@@ -20,10 +20,10 @@ public class FileUtilsTest : TestWithFolder {
     [Test]
     [Arguments("GZ.gz", "LTCat")]
     public async Task 解压_ReadGz(string testFile, string containsText) {
-        double progress = 0;
         string output = Path.Combine(tempFolder, "Extracted");
-        FileUtils.ExtractToDirectory(GetTestFile(testFile), output, p => progress = p);
-        await Assert.That(progress).IsEqualTo(1);
+        var p = new ProgressProvider();
+        FileUtils.ExtractToDirectory(GetTestFile(testFile), output, p: p);
+        await Assert.That(p.IsFinished).IsTrue();
         await Assert.That(File.ReadAllText(PathUtils.ToExtendedFormat(Path.Combine(output, PathUtils.GetFileNameWithoutExtension(testFile))))).Contains(containsText);
     }
 

+ 2 - 2
Tests/Utils/TaskUtilsTest.cs

@@ -21,11 +21,11 @@ public class TaskUtilsTest : TestBase {
         ], progress);
 
         await Assert.That(ReferenceEquals(firstProgress, secondProgress)).IsFalse();
-        await Assert.That(progress.GetIncrement()).IsEqualTo(0.5);
+        await Assert.That(progress.GetIncreasing()).IsEqualTo(0.5);
         continueSecond.SetResult(null);
         var results = await whenAll;
         await Assert.That(string.Join(",", results)).IsEqualTo("1,2");
-        await Assert.That(progress.GetIncrement()).IsEqualTo(1);
+        await Assert.That(progress.GetIncreasing()).IsEqualTo(1);
     }
 
 }