/// <summary>
        /// Treats this file as a zip file and extracts it to the specified target directory. This file and
        /// the target directory do NOT need to be in the same file system.
        /// If this file is not a valid zip archive, or any error occurs, then false is returned.
        /// </summary>
        public static bool ExtractZip(this FSFile zipFile, FSDir targetDir,
                                      ZipExtractionOptions options)
        {
            // First open the FSFile for reading
            Stream fsFileStream = zipFile.OpenReadOnly();

            // Create a zip archive (for reading) from the stream
            ZipArchive zipA = null;

            try
            {
                zipA = new ZipArchive(fsFileStream, ZipArchiveMode.Read);
            }
            catch (Exception)
            {
                fsFileStream.Dispose();
                return(false);
            }

            // Call the utility function to do the actual extraction
            ExtractZip(zipA, targetDir, options);

            // Clean up and return
            zipA.Dispose();
            fsFileStream.Dispose();
            return(true);
        }
        private static ZipExtractionStats ExtractZip(ZipArchive zipA, FSDir targetDirectory, ZipExtractionOptions options)
        {
            // Keep track of the number of entries encountered and extracted
            ZipExtractionStats stats = new ZipExtractionStats();

            // Loop through entries and extract
            foreach (ZipArchiveEntry entry in zipA.Entries)
            {
                stats.NumEntriesSeen++;
                bool  skipThisEntry = false;
                FSDir targetDir     = targetDirectory;

                // Split the full name on '/'
                string[] pieces = entry.FullName.Split('/');
                int      i;
                for (i = 0; i < pieces.Length - 1; i++)
                {
                    targetDir = targetDir.CreateDir(pieces[i]);
                    if (null == targetDir)
                    {
                        skipThisEntry = true;
                        break;
                    }
                }

                // Skip this entry if need be
                if (skipThisEntry)
                {
                    continue;
                }

                // At this point pieces[i] is the file we need to create in targetDir
                if (targetDir.GetFile(pieces[i]) != null && !options.Overwrite)
                {
                    // We can't overwrite the file
                    continue;
                }
                FSFile fileForEntry = targetDir.CreateFile(pieces[i]);
                if (null == fileForEntry)
                {
                    continue;
                }

                // Open the zip entry stream for reading and the FSFile for writing. Notice that despite
                // the possibility of failure to open the source stream from the zip file entry, we've
                // already created the FSFile in the target directory. This is intentional. If we fail
                // to extract a file, then a 0-byte placeholder is desired (for now, may change later).
                Stream src = entry.Open();
                if (src == null)
                {
                    continue;
                }
                Stream dst = fileForEntry.OpenReadWrite();
                if (dst == null)
                {
                    src.Dispose();
                    continue;
                }

                // Copy the contents
                byte[] buf = new byte[8192];
                while (true)
                {
                    int bytesRead = src.Read(buf, 0, buf.Length);
                    if (bytesRead <= 0)
                    {
                        break;
                    }
                    dst.Write(buf, 0, bytesRead);
                }

                // Clean up and increment number of files extracted
                src.Dispose();
                dst.Dispose();
                stats.NumEntriesExtracted++;
            }

            return(stats);
        }