如果调用两次保存,ZIP 文件会损坏

Corrupt ZIP file if calling Save twice

我在我的应用程序中使用 DotNetZip 1.9.6,它使用的文件结构类似于例如*.docx:包含 XML 个文件的 Zip 文件。
现在,应用程序的每个模块都可以将此类 XML 文件存储到我的自定义文件管理中,并在 "save" 上将它们序列化为流,然后通过 DotNetZip 将其保存到 Zip 文件中。
要更新条目,我使用 ZipFile.UpdateEntry(path, stream)。 这工作正常,我第一次通过调用 ZipFile.Save() 保存文件时一切正常。

但是如果我在同一个实例上第二次执行此操作(首先是一些 UpdateEntry 调用,然后是 Save)Zip 文件已损坏:文件结构和元数据(例如未压缩的大小每个文件的)仍然存在,但所有文件的压缩大小都是 0 字节。

如果我在保存一切后从刚保存的文件创建一个新实例,一切正常,但难道不能避免这种情况和 "reuse" 相同的实例吗?

以下示例(另见 https://dotnetfiddle.net/mHxEIy)可用于重现问题:

using System.IO;
using System.Text;

public class Program
{
    public static void Main()
    {
        var zipFile = new Ionic.Zip.ZipFile();

        var content1 = new MemoryStream(Encoding.Default.GetBytes("Content 1"));
        zipFile.UpdateEntry("test.txt", content1);

        zipFile.Save("test.zip"); // here the Zip file is correct
        //zipFile = new Ionic.Zip.ZipFile("test.zip"); // uncomment and it works too

        var content2 = new MemoryStream(Encoding.Default.GetBytes("Content 2"));
        zipFile.UpdateEntry("test.txt", content2);

        zipFile.Save();  // after that it is corrupt
    }
}

为了 运行 你需要添加 "DotNetZip 1.9.6" NuGet 包。

第一次保存后,这是你得到的:

第二次保存后:

这看起来像是库中的错误,与删除条目有关。如果您只是删除一个条目然后再次保存,它会正确删除文件。

但是,如果您删除一个条目然后添加另一个具有相同名称的条目 - 这是 UpdateEntry 在条目已存在时记录要做的事情 - 似乎使用旧条目。

您第二次以空文件结束的原因是原始 MemoryStream 正在被再次读取 - 但到目前为止,它位于数据的末尾,因此没有数据阅读。如果将位置重置为流的开头 (content1.Position = 0;),它将重写原始数据。如果您修改 content1 中的数据,您最终会得到无效的压缩数据。

我能立即想到的唯一解决方法是保留您自己的从文件名到 MemoryStream 的映射,并在您想要更新时替换每个 MemoryStream 的内容...或者只是根据您现有的解决方法,每次都加载文件。

尽管如此,围绕此提交错误绝对值得,因为据我所知,它应该 工作。

正如已经怀疑的那样,这是 DotNetZip 版本 1.9.6 中的错误。
我想我可以通过 THIS 更改来解决此问题,该更改刚刚在 NuGet 上作为版本 1.9.7 发布。至少对我来说,这个问题不会再发生了。

据我所知发生的一些背景:
当您调用 Save 时,该库会设置一个内部标志,该标志会记住 ZIP 文件刚刚保存,并且在第二次 Save 调用而不是 "recompressing" ZIP 文件中的所有条目时,它会从中复制它们刚刚保存的文件。
这适用于 adding/removing 条目,但当其中一个条目被更改时中断,因为它 "mixes" 旧条目和新条目并生成不一致的 ZIP 文件。
如果更改了条目,我的修复基本上会禁用 "copy from old file" 逻辑。