问题
Can anyone tell me why the following code doesnt work? I am using the SharpZipLib API for the Zip streams, latest version DL'ed today from their site. Im attempting to use this logic to merge the contents of one zip file into another, without having to perform IO on the disk, as the intended zip files may contain reserved file names for windows. I have tried this with multiple different source and destination zip files (those that contain reserved names and those that dont). The code does not throw any exception, and if you inspect the buffer prior to each write operation, you can see that it contains real data, yet after the entire operation finishes the size of the target zip file has not changed, and you can explore it to confirm that no new files (the ones the code is supposed to add) have actually been added to the destination file. :(
public static void CopyToZip(string inArchive, string outArchive)
{
ZipOutputStream outStream = null;
ZipInputStream inStream = null;
try
{
outStream = new ZipOutputStream(File.OpenWrite(outArchive));
outStream.IsStreamOwner = false;
inStream = new ZipInputStream(File.OpenRead(inArchive));
ZipEntry currentEntry = inStream.GetNextEntry();
while (currentEntry != null)
{
byte[] buffer = new byte[1024];
ZipEntry newEntry = new ZipEntry(currentEntry.Name);
newEntry.Size = currentEntry.Size;
newEntry.DateTime = currentEntry.DateTime;
outStream.PutNextEntry(newEntry);
int size = 0;
while ((size = inStream.Read(buffer, 0, buffer.Length)) > 0)
{
outStream.Write(buffer, 0, size);
}
outStream.CloseEntry();
currentEntry = inStream.GetNextEntry();
}
outStream.IsStreamOwner = true;
}
catch (Exception e)
{
throw e;
}
finally
{
try { outStream.Close(); }
catch (Exception ignore) { }
try { inStream.Close(); }
catch (Exception ignore) { }
}
}
回答1:
I ended up doing this using a different API. DotNet zip from http://dotnetzip.codeplex.com/. Here is the implementation:
public static void CopyToZip(string inArchive, string outArchive, string tempPath)
{
ZipFile inZip = null;
ZipFile outZip = null;
try
{
inZip = new ZipFile(inArchive);
outZip = new ZipFile(outArchive);
List<string> tempNames = new List<string>();
List<string> originalNames = new List<string>();
int I = 0;
foreach (ZipEntry entry in inZip)
{
if (!entry.IsDirectory)
{
string tempName = Path.Combine(tempPath, "tmp.tmp");
string oldName = entry.FileName;
byte[] buffer = new byte[4026];
Stream inStream = null;
FileStream stream = null;
try
{
inStream = entry.OpenReader();
stream = new FileStream(tempName, FileMode.Create, FileAccess.ReadWrite);
int size = 0;
while ((size = inStream.Read(buffer, 0, buffer.Length)) > 0)
{
stream.Write(buffer, 0, size);
}
inStream.Close();
stream.Flush();
stream.Close();
inStream = new FileStream(tempName, FileMode.Open, FileAccess.Read);
outZip.AddEntry(oldName, inStream);
outZip.Save();
}
catch (Exception exe)
{
throw exe;
}
finally
{
try { inStream.Close(); }
catch (Exception ignore) { }
try { stream.Close(); }
catch (Exception ignore) { }
}
}
}
}
catch (Exception e)
{
throw e;
}
}
回答2:
One issue that I see is that you are opening the output zip file using File.OpenWrite(), which will replace the existing output file rather than merging new entries into it.
There is an example on the SharpDevelop Wiki that gives an example of updating a zip file using memory streams. It can be found at http://wiki.sharpdevelop.net/SharpZipLib_Updating.ashx#Updating_a_zip_file_in_memory_1
回答3:
The following is some simpler code that will read from the input zip and write to the output zip, which potentially already exists. It does not require writing temporary data to the filesystem.
public static void CopyToZip(string inArchive, string outArchive)
{
using (inZip = new ZipFile(inArchive),
outZip = new ZipFile(outArchive))
{
Func<String,Func<String,Stream>> getInStreamReturner = (name) => {
return new Func<String,Stream>(a){ return inZip[a].OpenReader(); };
};
foreach (ZipEntry entry in inZip)
{
if (!entry.IsDirectory)
{
string zipEntryName = entry.FileName;
outZip.AddEntry(zipEntryName,
getInStreamReturner(zipEntryName),
(name, stream) => stream.Close() );
}
}
outZip.Save();
}
}
Notes:
This approach uses the
ZipFile.AddEntry
overload that accepts two delegates: an opener and a closer. These functions get called at the time ofZipFile.Save
. The former delegate needs to open and return the stream that contains the data to be zipped. The latter delegate needs to just close the stream.It is necessary to define the
getInStreamReturner
Func , in order to open the right stream at the time ofZipFile.Save
. Bear in mind that thezipEntryName
changes value each time through the loop. AlsoZipEntry.OpenReader()
opens a stream on the actual zip data, which reads-and-decompresses as it goes. You can have only one of those open, at any one time, per ZipFile.getInStreamReturner
creates a new function each time through the loop, thereby creating a closure to retain the value of thezipEntryName
for reference at the time ofZipFile.Save
.This approach will fail if there are name clashes between the inArchive and outArchive. To avoid that you'd need to check for that and somehow avoid it. Either contrive a new, unique name, or skip adding entries with duplicate names into the outarchive.
I haven't tested this.
While this approach does not write to the filesystem, it does decompress and recompress file data. There is an open request to provide a feature to DotNetZip to migrate entries without that decompress/recompress jump. I haven't implemented that yet.
来源:https://stackoverflow.com/questions/11839502/extract-zip-entries-to-another-zip-file