// ZipFile.Extract.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-31 14:45:18>
//
// ------------------------------------------------------------------
//
// This module defines the methods for Extract operations on zip files.
//
// ------------------------------------------------------------------
//


using System;
using System.IO;
using System.Collections.Generic;

namespace Ionic.Zip
{

    public partial class ZipFile
    {

        /// <summary>
        /// Extracts all of the items in the zip archive, to the specified path in the
        /// filesystem.  The path can be relative or fully-qualified.
        /// </summary>
        ///
        /// <remarks>
        /// <para>
        ///   This method will extract all entries in the <c>ZipFile</c> to the
        ///   specified path.
        /// </para>
        ///
        /// <para>
        ///   If an extraction of a file from the zip archive would overwrite an
        ///   existing file in the filesystem, the action taken is dictated by the
        ///   ExtractExistingFile property, which overrides any setting you may have
        ///   made on individual ZipEntry instances.  By default, if you have not
        ///   set that property on the <c>ZipFile</c> instance, the entry will not
        ///   be extracted, the existing file will not be overwritten and an
        ///   exception will be thrown. To change this, set the property, or use the
        ///   <see cref="ZipFile.ExtractAll(string,
        ///   Ionic.Zip.ExtractExistingFileAction)" /> overload that allows you to
        ///   specify an ExtractExistingFileAction parameter.
        /// </para>
        ///
        /// <para>
        ///   The action to take when an extract would overwrite an existing file
        ///   applies to all entries.  If you want to set this on a per-entry basis,
        ///   then you must use one of the <see
        ///   cref="ZipEntry.Extract()">ZipEntry.Extract</see> methods.
        /// </para>
        ///
        /// <para>
        ///   This method will send verbose output messages to the <see
        ///   cref="StatusMessageTextWriter"/>, if it is set on the <c>ZipFile</c>
        ///   instance.
        /// </para>
        ///
        /// <para>
        /// You may wish to take advantage of the <c>ExtractProgress</c> event.
        /// </para>
        ///
        /// <para>
        ///   About timestamps: When extracting a file entry from a zip archive, the
        ///   extracted file gets the last modified time of the entry as stored in
        ///   the archive. The archive may also store extended file timestamp
        ///   information, including last accessed and created times. If these are
        ///   present in the <c>ZipEntry</c>, then the extracted file will also get
        ///   these times.
        /// </para>
        ///
        /// <para>
        ///   A Directory entry is somewhat different. It will get the times as
        ///   described for a file entry, but, if there are file entries in the zip
        ///   archive that, when extracted, appear in the just-created directory,
        ///   then when those file entries are extracted, the last modified and last
        ///   accessed times of the directory will change, as a side effect.  The
        ///   result is that after an extraction of a directory and a number of
        ///   files within the directory, the last modified and last accessed
        ///   timestamps on the directory will reflect the time that the last file
        ///   was extracted into the directory, rather than the time stored in the
        ///   zip archive for the directory.
        /// </para>
        ///
        /// <para>
        ///   To compensate, when extracting an archive with <c>ExtractAll</c>,
        ///   DotNetZip will extract all the file and directory entries as described
        ///   above, but it will then make a second pass on the directories, and
        ///   reset the times on the directories to reflect what is stored in the
        ///   zip archive.
        /// </para>
        ///
        /// <para>
        ///   This compensation is performed only within the context of an
        ///   <c>ExtractAll</c>. If you call <c>ZipEntry.Extract</c> on a directory
        ///   entry, the timestamps on directory in the filesystem will reflect the
        ///   times stored in the zip.  If you then call <c>ZipEntry.Extract</c> on
        ///   a file entry, which is extracted into the directory, the timestamps on
        ///   the directory will be updated to the current time.
        /// </para>
        /// </remarks>
        ///
        /// <example>
        ///   This example extracts all the entries in a zip archive file, to the
        ///   specified target directory.  The extraction will overwrite any
        ///   existing files silently.
        ///
        /// <code>
        /// String TargetDirectory= "unpack";
        /// using(ZipFile zip= ZipFile.Read(ZipFileToExtract))
        /// {
        ///     zip.ExtractExistingFile= ExtractExistingFileAction.OverwriteSilently;
        ///     zip.ExtractAll(TargetDirectory);
        /// }
        /// </code>
        ///
        /// <code lang="VB">
        /// Dim TargetDirectory As String = "unpack"
        /// Using zip As ZipFile = ZipFile.Read(ZipFileToExtract)
        ///     zip.ExtractExistingFile= ExtractExistingFileAction.OverwriteSilently
        ///     zip.ExtractAll(TargetDirectory)
        /// End Using
        /// </code>
        /// </example>
        ///
        /// <seealso cref="Ionic.Zip.ZipFile.ExtractProgress"/>
        /// <seealso cref="Ionic.Zip.ZipFile.ExtractExistingFile"/>
        ///
        /// <param name="path">
        ///   The path to which the contents of the zipfile will be extracted.
        ///   The path can be relative or fully-qualified.
        /// </param>
        ///
        public void ExtractAll(string path)
        {
            _InternalExtractAll(path, true);
        }



        /// <summary>
        /// Extracts all of the items in the zip archive, to the specified path in the
        /// filesystem, using the specified behavior when extraction would overwrite an
        /// existing file.
        /// </summary>
        ///
        /// <remarks>
        ///
        /// <para>
        /// This method will extract all entries in the <c>ZipFile</c> to the specified
        /// path.  For an extraction that would overwrite an existing file, the behavior
        /// is dictated by <paramref name="extractExistingFile"/>, which overrides any
        /// setting you may have made on individual ZipEntry instances.
        /// </para>
        ///
        /// <para>
        /// The action to take when an extract would overwrite an existing file
        /// applies to all entries.  If you want to set this on a per-entry basis,
        /// then you must use <see cref="ZipEntry.Extract(String,
        /// ExtractExistingFileAction)" /> or one of the similar methods.
        /// </para>
        ///
        /// <para>
        /// Calling this method is equivalent to setting the <see
        /// cref="ExtractExistingFile"/> property and then calling <see
        /// cref="ExtractAll(String)"/>.
        /// </para>
        ///
        /// <para>
        /// This method will send verbose output messages to the
        /// <see cref="StatusMessageTextWriter"/>, if it is set on the <c>ZipFile</c> instance.
        /// </para>
        /// </remarks>
        ///
        /// <example>
        /// This example extracts all the entries in a zip archive file, to the
        /// specified target directory.  It does not overwrite any existing files.
        /// <code>
        /// String TargetDirectory= "c:\\unpack";
        /// using(ZipFile zip= ZipFile.Read(ZipFileToExtract))
        /// {
        ///   zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.DontOverwrite);
        /// }
        /// </code>
        ///
        /// <code lang="VB">
        /// Dim TargetDirectory As String = "c:\unpack"
        /// Using zip As ZipFile = ZipFile.Read(ZipFileToExtract)
        ///     zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.DontOverwrite)
        /// End Using
        /// </code>
        /// </example>
        ///
        /// <param name="path">
        /// The path to which the contents of the zipfile will be extracted.
        /// The path can be relative or fully-qualified.
        /// </param>
        ///
        /// <param name="extractExistingFile">
        /// The action to take if extraction would overwrite an existing file.
        /// </param>
        /// <seealso cref="ExtractSelectedEntries(String,ExtractExistingFileAction)"/>
        public void ExtractAll(string path, ExtractExistingFileAction extractExistingFile)
        {
            ExtractExistingFile = extractExistingFile;
            _InternalExtractAll(path, true);
        }


        private void _InternalExtractAll(string path, bool overrideExtractExistingProperty)
        {
            bool header = Verbose;
            _inExtractAll = true;
            try
            {
                OnExtractAllStarted(path);

                int n = 0;
                foreach (ZipEntry e in _entries.Values)
                {
                    if (header)
                    {
                        StatusMessageTextWriter.WriteLine("\n{1,-22} {2,-8} {3,4}   {4,-8}  {0}",
                                  "Name", "Modified", "Size", "Ratio", "Packed");
                        StatusMessageTextWriter.WriteLine(new System.String('-', 72));
                        header = false;
                    }
                    if (Verbose)
                    {
                        StatusMessageTextWriter.WriteLine("{1,-22} {2,-8} {3,4:F0}%   {4,-8} {0}",
                                  e.FileName,
                                  e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"),
                                  e.UncompressedSize,
                                  e.CompressionRatio,
                                  e.CompressedSize);
                        if (!String.IsNullOrEmpty(e.Comment))
                            StatusMessageTextWriter.WriteLine("  Comment: {0}", e.Comment);
                    }
                    e.Password = _Password;  // this may be null
                    OnExtractEntry(n, true, e, path);
                    if (overrideExtractExistingProperty)
                        e.ExtractExistingFile = this.ExtractExistingFile;
                    e.Extract(path);
                    n++;
                    OnExtractEntry(n, false, e, path);
                    if (_extractOperationCanceled)
                        break;
                }

                if (!_extractOperationCanceled)
                {
                    // workitem 8264:
                    // now, set times on directory entries, again.
                    // The problem is, extracting a file changes the times on the parent
                    // directory.  So after all files have been extracted, we have to
                    // run through the directories again.
                    foreach (ZipEntry e in _entries.Values)
                    {
                        // check if it is a directory
                        if ((e.IsDirectory) || (e.FileName.EndsWith("/")))
                        {
                            string outputFile = (e.FileName.StartsWith("/"))
                                ? Path.Combine(path, e.FileName.Substring(1))
                                : Path.Combine(path, e.FileName);

                            e._SetTimes(outputFile, false);
                        }
                    }
                    OnExtractAllCompleted(path);
                }

            }
            finally
            {

                _inExtractAll = false;
            }
        }


    }
}
