Completed
Last Updated: 14 Mar 2024 08:59 by ADMIN
Release 2024.1.305 (2024 Q1)
Joe
Created on: 07 Feb 2024 12:34
Category: ZipLibrary
Type: Bug Report
1
ZipLibrary: System.IO.IOException: Cannot access file after ZipFile.CreateFromDirectory

The stream that is internally created by the CreateFromDirectory method remains opened and the user can't close it:

This is error message observed when using the below code snippet:

The process cannot access the file 'C:\Users\dyordano\OneDrive - Progress Software Corporation\MyProjects\1640071ZipFileCreateFromDirectory\myzip.zip' because it is being used by another process.

        static void Main(string[] args)
        {
            string archiveFileName = @"..\..\myzip.zip";
            string sourceDirectoryName = @"..\..\Folder";
            File.Delete(archiveFileName);
            Telerik.Windows.Zip.Extensions.ZipFile.CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);

            using (Stream stream = File.Open(archiveFileName, FileMode.Open))
            {

            }
        }

Workaround: 

        static void Main(string[] args)
        {
            string archiveFileName = @"..\..\myzip.zip";
            string sourceDirectoryName = @"..\..\Folder";
            File.Delete(archiveFileName);

          //  Telerik.Windows.Zip.Extensions.ZipFile.CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);
            CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);

            using (Stream stream = File.Open(archiveFileName, FileMode.Open))
            {

            }
        }

        private static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, Telerik.Windows.Zip.CompressionLevel compressionLevel, bool includeBaseDirectory)
        {
            Encoding entryNameEncoding = null;
            DeflateSettings compressionSettings = new DeflateSettings()
            {
                CompressionLevel = compressionLevel,
                HeaderType = CompressedStreamHeader.None
            };
            char[] directorySeparatorChar = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };

            sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
            destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName);
            FileStream fileStream = File.Open(destinationArchiveFileName, FileMode.CreateNew, FileAccess.Write, FileShare.None); ;
            using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create,false,  entryNameEncoding))
            {
                bool empty = true;

                DirectoryInfo directoryInfo = new DirectoryInfo(sourceDirectoryName);
                string fullName = directoryInfo.FullName;
                if (includeBaseDirectory && directoryInfo.Parent != null)
                {
                    fullName = directoryInfo.Parent.FullName;
                }

                foreach (FileSystemInfo fileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
                {
                    empty = false;

                    int length = fileSystemInfo.FullName.Length - fullName.Length;
                    string entryName = fileSystemInfo.FullName.Substring(fullName.Length, length);
                    entryName = entryName.TrimStart(directorySeparatorChar);

                    DirectoryInfo subfolder = fileSystemInfo as DirectoryInfo;
                    if (subfolder != null)
                    {
                        if (IsDirectoryEmpty(subfolder))
                        {
                            zipArchive.CreateEntry(string.Concat(entryName, Path.DirectorySeparatorChar));
                        }
                    }
                    else
                    {
                        ZipFile.CreateEntryFromFile(zipArchive, fileSystemInfo.FullName, entryName, compressionSettings);
                    }
                }

                if (includeBaseDirectory && empty)
                {
                    zipArchive.CreateEntry(string.Concat(directoryInfo.Name, Path.DirectorySeparatorChar));
                }
            }
        }
        private static bool IsDirectoryEmpty(DirectoryInfo directoryInfo)
        {
            bool empty = true;

            using (IEnumerator<FileSystemInfo> enumerator = directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories).GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    empty = false;
                }
            }

            return empty;
        }

Note: We should ensure that all of the obsolete API that used to use the "leave open" flag don't keep the stream locked.

 

3 comments
ADMIN
Martin
Posted on: 08 Mar 2024 07:49

Hi Joe,

I checked the code snippet from this ticket thread and I can confirm there is an error message when extacting a zip archive that contains an empty folder I logged an item on your behalf in order to investigate this unexpected behavior: ZipLibrary: 7zip reports errors while extracting archive containing empty folders. Please, make sure to subscribe to the task so we can notify you when its status changes.

On the other hand, with the sample project I created the archive could be successfully exported (even with the error message). The output can be seen in the attached "observed.gif"

As for setting the last modified date, you can check the ZipArchive entry`s LastWriteTime property. An example can be found in "Example 4: Set last entry's write time" in the ZipArchive Entry help article.

Regards,
Martin
Progress Telerik

A brand new ThemeBuilder course was just added to the Virtual Classroom. The training course was designed to help you get started with ThemeBuilder for styling Telerik and Kendo UI components for your applications. You can check it out at https://learn.telerik.com
Attached Files:
Joe
Posted on: 07 Mar 2024 11:42

The workaround code seems to have an issue: While empty folders do get added to the ZIP file, these do not get extracted when unzipping (neither with 7-ZIP nor with the Windows built-in ZIP extension). I also noted that folders added by this method do not have a "last modified" date in the ZIP file.

What needs to be done that empty folders added to a ZIP file also get extracted as empty folders?

ADMIN
Dess | Tech Support Engineer, Principal
Posted on: 13 Feb 2024 17:26

Hi,

I have added a temporary solution for handling the cases when opening an existing zip with the ZipFile.Open method for example:

       static void Main(string[] args)
       {
           string path = @"path-to-file.zip";
           using (ZipArchive zip = OpenAndClose(path, ZipArchiveMode.Read))
           {
               foreach (var entry in zip.Entries)
               {
                   if (entry.Name == "TemplateSample.docx")
                   {
                        entry.ExtractToFile(@"unzipped-file-name.docx", true);
                   }
               }
           }
           File.Delete(path);
       }

       public static ZipArchive OpenAndClose(string archiveFileName, ZipArchiveMode mode, Encoding entryNameEncoding=null)
       {
           FileMode fileMode;
           FileAccess fileAccess;
           FileShare fileShare;
           ZipArchive zipArchive;

           switch (mode)
           {
               case ZipArchiveMode.Read:
                   fileMode = FileMode.Open;
                   fileAccess = FileAccess.Read;
                   fileShare = FileShare.Read;
                   break;

               case ZipArchiveMode.Create:
                   fileMode = FileMode.CreateNew;
                   fileAccess = FileAccess.Write;
                   fileShare = FileShare.None;
                   break;

               case ZipArchiveMode.Update:
                   fileMode = FileMode.OpenOrCreate;
                   fileAccess = FileAccess.ReadWrite;
                   fileShare = FileShare.None;
                   break;

               default:
                   throw new ArgumentOutOfRangeException("mode");
           }

           FileStream fileStream = null;
           try
           {
               fileStream = File.Open(archiveFileName, fileMode, fileAccess, fileShare);

               bool leaveOpen = false;
               // Closing the stream is important because the created archive will remain locked 
               zipArchive = new ZipArchive(fileStream, mode, leaveOpen, entryNameEncoding);
           }
           catch
           {
               if (fileStream != null)
               {
                   fileStream.Dispose();
               }

               throw;
           }

           return zipArchive;
       }

Regards,
Dess | Tech Support Engineer, Principal
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.