Search code examples
c#.netzipsystem.io.compression

Copy files from one Zip file to another


I am copying files from one zip file to another in certain circumstances. I am wondering if there is a better way to do it than what I came up with:

using FileStream sourceFileStream = new FileStream(source.FileName, FileMode.Open);
using FileStream targetFileStream = new FileStream(target.FileName, FileMode.Open, FileAccess.ReadWrite);
using ZipArchive sourceZip = new ZipArchive(sourceFileStream, ZipArchiveMode.Read);
using ZipArchive targetZip = new ZipArchive(targetFileStream, ZipArchiveMode.Update);
ZipArchiveEntry sourceEntry = sourceZip.GetEntry(filePathInArchive);
if (sourceEntry == null) 
    return;
ZipArchiveEntry targetEntry = targetZip.GetEntry(filePathInArchive);
if (targetEntry != null) 
    targetEntry.Delete();
targetZip.CreateEntry(filePathInArchive);
targetEntry = targetZip.GetEntry(filePathInArchive);
if (targetEntry != null)
{
    Stream writer = targetEntry.Open();
    Stream reader = sourceEntry.Open();

    int b;
    do
    {
        b = reader.ReadByte();
        writer.WriteByte((byte)b);
    } while (b != -1);


    writer.Close();
    reader.Close();
}

Tips and suggestions would be appreciated.


Solution

  • You can iterate each entry from source archive with opening its streams and using Stream.CopyTo write source entry content to target entry.

    From C# 8.0 it looks compact and works fine:

    static void CopyZipEntries(string sourceZipFile, string targetZipFile)
    {
        using FileStream sourceFS = new FileStream(sourceZipFile, FileMode.Open);
        using FileStream targetFS = new FileStream(targetZipFile, FileMode.Open);
    
        using ZipArchive sourceZIP = new ZipArchive(sourceFS, ZipArchiveMode.Read, false, Encoding.GetEncoding(1251));
        using ZipArchive targetZIP = new ZipArchive(targetFS, ZipArchiveMode.Update, false, Encoding.GetEncoding(1251));
    
        foreach (ZipArchiveEntry sourceEntry in sourceZIP.Entries)
        {
            // 'is' is replacement for 'null' check
            if (targetZIP.GetEntry(sourceEntry.FullName) is ZipArchiveEntry existingTargetEntry)
                existingTargetEntry.Delete();
    
            using (Stream targetEntryStream = targetZIP.CreateEntry(sourceEntry.FullName).Open())
            {
                sourceEntry.Open().CopyTo(targetEntryStream);
            }
        }
    }
    

    With earlier than C# 8.0 versions it works fine too, but more braces needed:

    static void CopyZipEntries(string sourceZipFile, string targetZipFile)
    {
        using (FileStream sourceFS = new FileStream(sourceZipFile, FileMode.Open))
        {
            using (FileStream targetFS = new FileStream(targetZipFile, FileMode.Open))
            {
                using (ZipArchive sourceZIP = new ZipArchive(sourceFS, ZipArchiveMode.Read, false, Encoding.GetEncoding(1251)))
                {
                    using (ZipArchive targetZIP = new ZipArchive(targetFS, ZipArchiveMode.Update, false, Encoding.GetEncoding(1251)))
                    {
                        foreach (ZipArchiveEntry sourceEntry in sourceZIP.Entries)
                        {
                            if (targetZIP.GetEntry(sourceEntry.FullName) is ZipArchiveEntry existingTargetEntry)
                            {
                                existingTargetEntry.Delete();
                            }
    
                            using (Stream target = targetZIP.CreateEntry(sourceEntry.FullName).Open())
                            {
                                sourceEntry.Open().CopyTo(target);
                            }
                        }
                    }
                }
            }
        }
    }
    

    For single specified file copy just replace bottom part from foreach loop to if condition:

    static void CopyZipEntry(string fileName, string sourceZipFile, string targetZipFile)
    {
        // ...
    
        // It means specified file exists in source ZIP-archive
        // and we can copy it to target ZIP-archive
        if (sourceZIP.GetEntry(fileName) is ZipArchiveEntry sourceEntry) 
        {
            if (targetZIP.GetEntry(sourceEntry.FullName) is ZipArchiveEntry existingTargetEntry)
                existingTargetEntry.Delete();
    
            using (Stream targetEntryStream = targetZIP.CreateEntry(sourceEntry.FullName).Open())
            {
                sourceEntry.Open().CopyTo(targetEntryStream);
            }
        }
        else
            MessageBox.Show("Source ZIP-archive doesn't contains file " + fileName);
    }