Unexpected end of archive on a ZIP file in c#

1.3k views Asked by At

I am trying to zip a text file and split it into chunks(byte[]) in the console application.

public List<byte[]> CreateChunks(string fileName, string txtFilePath)
    {
        long chunkSize = 100 * 1000;//100kb
        var fileChunks = new List<byte[]>();

        using (var memoryStream = new MemoryStream())
        {
            using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false))
            {
                zipArchive.CreateEntryFromFile($"{txtFilePath}", fileName, CompressionLevel.Optimal);

                memoryStream.Position = 0;

                var buffer = new byte[chunkSize];

                while (memoryStream.Read(buffer, 0, buffer.Length) > 0)
                {
                    fileChunks.Add(buffer);
                }
            }
        }
        return fileChunks;
    }

With those chunks, I am trying to create a zip file in an another application.

    public void JoinChunks(List<byte[]> fileChunks, string filePath)
    {
        var memory = new MemoryStream();
        using (var file = new FileStream(filePath + "\\temp.zip", FileMode.Create))
        {
            foreach (var item in fileChunks)
            {
                memory.Write(item, 0, item.Length);
            }
            memory.WriteTo(file);
            file.Close();
        }
    }

When viewing the created zip file, the error pops out and says Unexpected end of archive.

enter image description here

If I try to chunk a text file and join them back, then it's working fine. The problem is in ZIP. Any solutions are most welcome.

2

There are 2 answers

0
Peter Duniho On BEST ANSWER

You need to close the archive before you do anything else with the data, so that the archive object can flush the remaining data and finalize the archive. Your code should look like this:

using (var memoryStream = new MemoryStream())
{
    using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false))
    {
        zipArchive.CreateEntryFromFile($"{txtFilePath}", fileName, CompressionLevel.Optimal);
    }

    memoryStream.Position = 0;

    var buffer = new byte[chunkSize];

    while (memoryStream.Read(buffer, 0, buffer.Length) > 0)
    {
        fileChunks.Add(buffer);
    }
}

I.e. move the non-archive-related code out of the using statement for the zipArchive object.

0
jangix On

Beware if using instructions without curly brackets are used (C# >8.0):

static byte[] CompressAsZip(string fileName, Stream fileStram)
{
    using var memoryStream = new MemoryStream();
    using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false);
    using var entryStream = archive.CreateEntry(fileName).Open();

    fileStram.CopyTo(entryStream);

    archive.Dispose(); //<- Necessary to close the archive correctly 
    return memoryStream.ToArray();
}

Using the using statemant with curly brackets,

archive.Dispose()

will be implicitly invoked at the end of scope.

Also, since you are using C# >8.0, I recommend an asynchronous implementation of this type, capable of archiving multiple files with cancellation:

static async Task<byte[]> CompressAsZipAsync(
    IAsyncEnumerable<(string FileName, Stream FileStream)> files,
    bool disposeStreamsAfterCompression = false,
    CancellationToken cancellationToken = default)
{
    using var memoryStream = new MemoryStream();
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false))
    {
        await foreach (var (FileName, FileStream) in files.WithCancellation(cancellationToken))
        {
            using var entryStream = archive.CreateEntry(FileName).Open();
            await FileStream.CopyToAsync(entryStream, cancellationToken);

            if (disposeStreamsAfterCompression)
                await FileStream.DisposeAsync();
        }
    }
    return memoryStream.ToArray();
}