如果调用两次保存,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" 逻辑。
我在我的应用程序中使用 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" 逻辑。