/// <summary>
        ///   Create a ZipOutputStream that writes to a filesystem file.
        /// </summary>
        ///
        /// <remarks>
        ///   The <see cref="ZipFile"/> class is generally easier to use when creating
        ///   zip files. The ZipOutputStream offers a different metaphor for creating a
        ///   zip file, based on the <see cref="System.IO.Stream"/> class.
        /// </remarks>
        ///
        /// <param name="fileName">
        ///   The name of the zip file to create.
        /// </param>
        ///
        /// <example>
        ///
        ///   This example shows how to create a zip file, using the
        ///   ZipOutputStream class.
        ///
        /// <code lang="C#">
        /// private void Zipup()
        /// {
        ///     if (filesToZip.Count == 0)
        ///     {
        ///         System.Console.WriteLine("Nothing to do.");
        ///         return;
        ///     }
        ///
        ///     using (var output= new ZipOutputStream(outputFileName))
        ///     {
        ///         output.Password = "******";
        ///         output.Encryption = EncryptionAlgorithm.WinZipAes256;
        ///
        ///         foreach (string inputFileName in filesToZip)
        ///         {
        ///             System.Console.WriteLine("file: {0}", inputFileName);
        ///
        ///             output.PutNextEntry(inputFileName);
        ///             using (var input = File.Open(inputFileName, FileMode.Open, FileAccess.Read,
        ///                                          FileShare.Read | FileShare.Write ))
        ///             {
        ///                 byte[] buffer= new byte[2048];
        ///                 int n;
        ///                 while ((n= input.Read(buffer,0,buffer.Length)) > 0)
        ///                 {
        ///                     output.Write(buffer,0,n);
        ///                 }
        ///             }
        ///         }
        ///     }
        /// }
        /// </code>
        ///
        /// <code lang="VB">
        /// Private Sub Zipup()
        ///     Dim outputFileName As String = "XmlData.zip"
        ///     Dim filesToZip As String() = Directory.GetFiles(".", "*.xml")
        ///     If (filesToZip.Length = 0) Then
        ///         Console.WriteLine("Nothing to do.")
        ///     Else
        ///         Using output As ZipOutputStream = New ZipOutputStream(outputFileName)
        ///             output.Password = "******"
        ///             output.Encryption = EncryptionAlgorithm.WinZipAes256
        ///             Dim inputFileName As String
        ///             For Each inputFileName In filesToZip
        ///                 Console.WriteLine("file: {0}", inputFileName)
        ///                 output.PutNextEntry(inputFileName)
        ///                 Using input As FileStream = File.Open(inputFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
        ///                     Dim n As Integer
        ///                     Dim buffer As Byte() = New Byte(2048) {}
        ///                     Do While (n = input.Read(buffer, 0, buffer.Length) > 0)
        ///                         output.Write(buffer, 0, n)
        ///                     Loop
        ///                 End Using
        ///             Next
        ///         End Using
        ///     End If
        /// End Sub
        /// </code>
        /// </example>
        internal static ZipOutputStream Create(String fileName)
        {
            string fullPath = ZipFileExtensions.GetFullPath(fileName);

            var file = FileSystem.Current.GetFileFromPathAsync(fullPath).ExecuteSync();

            if (file == null)
            {
                var dirName = Path.GetDirectoryName(fullPath);
                var dir     = ZipEntryExtensions.CreateDirectory(dirName, true);
                file = dir.CreateFileAsync(Path.GetFileName(fileName), CreationCollisionOption.ReplaceExisting).ExecuteSync();
            }
            var stream = file.OpenAsync(FileAccess.ReadAndWrite).ExecuteSync();

            return(new ZipOutputStream(stream));
        }
        /// <summary>
        /// Save the file to a new zipfile, with the given name.
        /// </summary>
        ///
        /// <remarks>
        /// <para>
        /// This method allows the application to explicitly specify the name of the zip
        /// file when saving. Use this when creating a new zip file, or when
        /// updating a zip archive.
        /// </para>
        ///
        /// <para>
        /// An application can also save a zip archive in several places by calling this
        /// method multiple times in succession, with different filenames.
        /// </para>
        ///
        /// <para>
        /// The <c>ZipFile</c> instance is written to storage, typically a zip file in a
        /// filesystem, only when the caller calls <c>Save</c>.  The Save operation writes
        /// the zip content to a temporary file, and then renames the temporary file
        /// to the desired name. If necessary, this method will delete a pre-existing file
        /// before the rename.
        /// </para>
        ///
        /// </remarks>
        ///
        /// <exception cref="System.ArgumentException">
        /// Thrown if you specify a directory for the filename.
        /// </exception>
        ///
        /// <param name="fileName">
        /// The name of the zip archive to save to. Existing files will
        /// be overwritten with great prejudice.
        /// </param>
        ///
        /// <example>
        /// This example shows how to create and Save a zip file.
        /// <code>
        /// using (ZipFile zip = new ZipFile())
        /// {
        ///   zip.AddDirectory(@"c:\reports\January");
        ///   zip.Save("January.zip");
        /// }
        /// </code>
        ///
        /// <code lang="VB">
        /// Using zip As New ZipFile()
        ///   zip.AddDirectory("c:\reports\January")
        ///   zip.Save("January.zip")
        /// End Using
        /// </code>
        ///
        /// </example>
        ///
        /// <example>
        /// This example shows how to update a zip file.
        /// <code>
        /// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
        /// {
        ///   zip.AddFile("NewData.csv");
        ///   zip.Save("UpdatedArchive.zip");
        /// }
        /// </code>
        ///
        /// <code lang="VB">
        /// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip")
        ///   zip.AddFile("NewData.csv")
        ///   zip.Save("UpdatedArchive.zip")
        /// End Using
        /// </code>
        ///
        /// </example>
        public static void Save(this ZipFile zipFile, String fileName)
        {
            if (fileName == null)
            {
                throw new ArgumentNullException("fileName");
            }

            string fullPath = GetFullPath(fileName);

            if (FileSystem.Current.GetFolderFromPathAsync(fullPath).ExecuteSync() != null)
            {
                throw new ZipException("Bad Directory", new ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName"));
            }

            var fullDirectoryName = Path.GetDirectoryName(fullPath);
            var directoryName     = Path.GetDirectoryName(fileName);
            var folder            = FileSystem.Current.GetFolderFromPathAsync(fullDirectoryName).ExecuteSync();

            if (folder == null)
            {
                throw new ZipException("Bad Directory", new ArgumentException(string.Format("That folder ({0}) does not exist!", directoryName)));
            }

            // check up here so we may shortcut some IO operations in the future :)
            var fileExists = folder.CheckExistsAsync(fullPath).ExecuteSync() == ExistenceCheckResult.FileExists;

            // write to a temporary file so that we can read from the same zip when saving
            var tmpName = Path.GetRandomFileName();

            try
            {
                ZipSegmentedStreamManager segmentsManager = null;

                if (zipFile.MaxOutputSegmentSize != 0)
                {
                    // save segmented files using the segments manager
                    var manager = new FileSystemZipSegmentedStreamManager(fullPath);
                    segmentsManager = manager;
                    zipFile.Save(segmentsManager);
                    tmpName = manager.TemporaryName;
                }
                else
                {
                    // save
                    var tmpFile = folder.CreateFileAsync(tmpName, CreationCollisionOption.ReplaceExisting).ExecuteSync();
                    tmpName = tmpFile.Path;
                    using (var tmpStream = tmpFile.OpenAsync(FileAccess.ReadAndWrite).ExecuteSync())
                    {
                        zipFile.Save(tmpStream);
                    }
                }

                // if it wasn't canceled
                if (!zipFile.IsSaveOperationCanceled())
                {
                    // disconnect from the stream
                    zipFile.SetUnderlyingZipStream(null);
                    // move the temporary file into position
                    zipFile.OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
                    ZipEntryExtensions.MoveFileInPlace(fileExists, fullPath, tmpName);
                    zipFile.OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
                    // and now set the read stream to be that of the new file
                    var targetFile = FileSystem.Current.GetFileFromPathAsync(fullPath).ExecuteSync();
                    var fileStream = targetFile.OpenAsync(FileAccess.Read).ExecuteSync();
                    zipFile.SetUnderlyingZipSegmentedStreamManager(segmentsManager);
                    zipFile.SetUnderlyingZipStream(fileStream);
                    zipFile.SetShouldDisposeReadStream(true);
                    zipFile.Name = fullPath;

                    // so we can shortcut the existance checks
                    tmpName = null;
                }
            }
            finally
            {
                // An exception has occurred. If the file exists, check
                // to see if it existed before we tried extracting.  If
                // it did not, attempt to remove the target file. There
                // is a small possibility that the existing file has
                // been extracted successfully, overwriting a previously
                // existing file, and an exception was thrown after that
                // but before final completion (setting times, etc). In
                // that case the file will remain, even though some
                // error occurred.  Nothing to be done about it.
                if (tmpName != null)
                {
                    var tmpFile = folder.GetFileAsync(tmpName).ExecuteSync();
                    tmpFile.DeleteAsync();
                }
            }
        }