locked
"Central Directory Corrupt" when creating new ZipArchive

    Question

  • When extracting a zip file, I am having an odd problem.  As described in:

    http://social.msdn.microsoft.com/Forums/en-US/35f3c75f-1e61-44c4-9cdb-be2a1f3a219d/unzipping-downloaded-zip-file-winrt?forum=winappswithcsharp

    I get a "central directory corrupt" error on the "new ZipArchive" line when extracting a downloaded zip file.  The oddity is that this never happens on the first zip file; only the second time I call the unzip function. The inner exception is: "Cannot seek to an absolute stream position that is negative."

    More details:

    • I am using the example code provided at the following link, as suggested in the above post: http://www.codeproject.com/Tips/515704/Archive-Multiple-Files-In-Zip-Extract-Zip-Archive
    • This happens on a number of different zip files, and only on the second (and subsequent) unzips.  A zip file which works for the first unzip will not work the second time.  I have tried many permutations here.
    • One time when I stepped through line by line in debug mode, this did _not_ happen -- I am wondering if it is a resource issue that ZipArchive doesn't free correctly, and only happens to work if the garbage collector is called before the next ZipArchive creation?  (wild theory, I know)  This also seems to be an anomaly, since I could not get this result by waiting a couple minutes between function calls. 
    • Perusing other reported issues, I have attempted the solution given in the following post, which says to copy the stream into a memory stream first: http://social.msdn.microsoft.com/Forums/en-US/d3265aa5-7a1d-47e6-ab1d-1e2db89b8a0e/issue-with-ziparchive-in-win-8-release-preview?forum=winappswithcsharp
    • When using the previous suggestion (copy to memory stream first), the inner exception changes to "An attempt was made to move the position before the beginning of the stream."
    • If it makes any difference, this is in a Windows store app and is called from a background thread (when the BackgroundDownloader completes a download)

    Any help would be greatly appreciated.  Thanks!

    I have included the function below, even though it is the same as in the above link:

    		public static async Task UnzipFile(IStorageFile file, IStorageFolder destination, CreationCollisionOption collisionOptions = CreationCollisionOption.ReplaceExisting)
    		{
                Stream zipMemoryStream = await file.OpenStreamForReadAsync();
                // Create zip archive to access compressed files in memory stream
                using (ZipArchive zipArchive = new ZipArchive(zipMemoryStream, ZipArchiveMode.Read))
                {
                    // For each compressed file...
                    foreach (ZipArchiveEntry entry in zipArchive.Entries)
                    {
                        // ... read its uncompressed contents
                        using (Stream entryStream = entry.Open())
                        {
                            byte[] buffer = new byte[entry.Length];
                            entryStream.Read(buffer, 0, buffer.Length);
                            try
                            {
                                //Create a file to store the contents
                                StorageFile uncompressedFile = await destination.CreateFileAsync
                                (entry.Name, CreationCollisionOption.ReplaceExisting);
                                // Store the contents
                                using (Windows.Storage.Streams.IRandomAccessStream uncompressedFileStream =
                                await uncompressedFile.OpenAsync(FileAccessMode.ReadWrite))
                                {
                                    using (Stream outstream = uncompressedFileStream.AsStreamForWrite())
                                    {
                                        outstream.Write(buffer, 0, buffer.Length);
                                        outstream.Flush();
                                    }
                                }
                            }
                            catch
                            {
                            }
                        }
                    }
                }
            }

    "An attempt was made to move the position before the beginning of the stream."
    "An attempt was made to move the position before the beginning of the stream."
    Wednesday, June 18, 2014 4:29 PM

Answers

  • It sounds like you're trying to access the file immediately once the download is completed( I guess the operating system needs some time to make that downloaded file as a valid user readable one) because of which ZipArchive thought that the zip file is corrupted and throws the error. Just by adding a minor delay before unzipping the files seems to be working:

    // Unzip and cleanup by removing the file
                    try
                    {
                        await Task.Delay(500);
                        await ZipFile.UnzipFile(download.ResultFile, targetFolder);
                        // Note: because this is in the app folder, the default delete would work, too
                        await download.ResultFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
                    }


    - Ram
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Edited by Ramprasath R Sunday, June 22, 2014 7:22 PM
    • Marked as answer by RoboMatt Monday, June 23, 2014 1:12 AM
    Sunday, June 22, 2014 7:20 PM
  • Ram -- thanks, you got me on (I think) the right track!

    So, your idea did indeed get it to work, but it bothers me -- it seems like a nasty race condition just waiting to happen!

    Here's my hypothesis about what is happening (it would be great if someone from microsoft could verify!).  I think the 'progress' callback is not supposed to be used for task completion purposes.  Basically, the download is still active, and the file hasn't been completely saved, even if progress is 1.0.  So, we have to wait to the download to release the resource before trying to unzip it.

    To guarantee this (without a potential race condition), we can put the unzip call _after_ the await download.StartAsync() call.

    var progress = new Progress<DownloadOperation>(ProgressCallback);
    await download.StartAsync().AsTask(progress);
    // Wait to complete download until operation has completed.
    CompleteDownload(download);
    In the case of my provided code sample, this would mean the progress function was effectively doing nothing.

    (Initially, it seemed very odd that this would happen on the second attempt, but not the first.  I think the compounding cause was that the dynamic generation of the resources needed for unzipping the first time slows things down just enough.)

    Thanks again, Ram, for getting me on (I think) the right track!



    • Marked as answer by RoboMatt Monday, June 23, 2014 1:11 AM
    Monday, June 23, 2014 1:11 AM

All replies

  • I don't see anything obvious.  If you can post a simplified repro to OneDrive someone can take a deeper look for you.

    Jeff Sanders (MSFT)

    @jsandersrocks - Windows Store Developer Solutions @WSDevSol
    Getting Started With Windows Azure Mobile Services development? Click here
    Getting Started With Windows Phone or Store app development? Click here
    My Team Blog: Windows Store & Phone Developer Solutions
    My Blog: Http Client Protocol Issues (and other fun stuff I support)

    Thursday, June 19, 2014 2:37 PM
    Moderator
  • Thanks for the quick reply. I'll try to boil down a small demonstrative example sometime today.
    Thursday, June 19, 2014 4:10 PM
  • Here is a link to a .zip file of a new solution I created that exhibits this problem.

    https://onedrive.live.com/redir?resid=1295F5B377097F89!4511&authkey=!AJ41J0OJYH1eWmg&ithint=file%2c.zip

    Steps to reproduce:

    1) Compile/deploy/run solution (I've been using 'Release' rather than 'Debug')

    2) Click on one of the two buttons to download the corresponding file.  Nothing is expected to happen (although in rare cases I have seen an opcode error pop up)

    3) Click on the other button to download the second file, and note the popup triggered from the catch statement around the call to the unzip function.  This is simply here to display the error message ("Central Directory Corrupt").

    4) Subsequent clicks (causing download/unzip actions) similarly cause the error.

    This may not be a minimal example allowing reproduction, but is a pretty simple demonstration of the problem I'm having.  If this doesn't cause the problem on your system, then I'll start looking into what could be going on with my system configuration.

    Hopefully this example will shed light into something else I'm doing wrongthat might be causing this issue.

    Thanks again,

    Matt

    Friday, June 20, 2014 3:14 AM
  • It sounds like you're trying to access the file immediately once the download is completed( I guess the operating system needs some time to make that downloaded file as a valid user readable one) because of which ZipArchive thought that the zip file is corrupted and throws the error. Just by adding a minor delay before unzipping the files seems to be working:

    // Unzip and cleanup by removing the file
                    try
                    {
                        await Task.Delay(500);
                        await ZipFile.UnzipFile(download.ResultFile, targetFolder);
                        // Note: because this is in the app folder, the default delete would work, too
                        await download.ResultFile.DeleteAsync(StorageDeleteOption.PermanentDelete);
                    }


    - Ram
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    • Edited by Ramprasath R Sunday, June 22, 2014 7:22 PM
    • Marked as answer by RoboMatt Monday, June 23, 2014 1:12 AM
    Sunday, June 22, 2014 7:20 PM
  • Ram -- thanks, you got me on (I think) the right track!

    So, your idea did indeed get it to work, but it bothers me -- it seems like a nasty race condition just waiting to happen!

    Here's my hypothesis about what is happening (it would be great if someone from microsoft could verify!).  I think the 'progress' callback is not supposed to be used for task completion purposes.  Basically, the download is still active, and the file hasn't been completely saved, even if progress is 1.0.  So, we have to wait to the download to release the resource before trying to unzip it.

    To guarantee this (without a potential race condition), we can put the unzip call _after_ the await download.StartAsync() call.

    var progress = new Progress<DownloadOperation>(ProgressCallback);
    await download.StartAsync().AsTask(progress);
    // Wait to complete download until operation has completed.
    CompleteDownload(download);
    In the case of my provided code sample, this would mean the progress function was effectively doing nothing.

    (Initially, it seemed very odd that this would happen on the second attempt, but not the first.  I think the compounding cause was that the dynamic generation of the resources needed for unzipping the first time slows things down just enough.)

    Thanks again, Ram, for getting me on (I think) the right track!



    • Marked as answer by RoboMatt Monday, June 23, 2014 1:11 AM
    Monday, June 23, 2014 1:11 AM