// NAnt - A .NET build tool // Copyright (C) 2003 Scott Hernandez // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Scott Hernandez (ScottHernandez@hotmail.com) using System; using System.Globalization; using System.IO; using System.Text; using ICSharpCode.SharpZipLib; using ICSharpCode.SharpZipLib.Zip; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Util; namespace NAnt.Compression.Tasks { /// /// Extracts files from a zip file. /// /// /// Uses #ziplib (SharpZipLib), an open source Zip/GZip library written entirely in C#. /// /// /// Extracts all the file from the zip, preserving the directory structure. /// /// /// ]]> /// /// [TaskName("unzip")] public class UnZipTask : Task { #region Private Instance Fields private FileInfo _zipfile; private DirectoryInfo _toDir; private bool _overwrite = true; private Encoding _encoding; #endregion Private Instance Fields #region Public Instance Properties /// /// The archive file to expand. /// [TaskAttribute("zipfile", Required=true)] public FileInfo ZipFile { get { return _zipfile; } set { _zipfile = value; } } /// /// The directory where the expanded files should be stored. The /// default is the project base directory. /// [TaskAttribute("todir", Required=false)] public DirectoryInfo ToDirectory { get { if (_toDir == null) { return new DirectoryInfo(Project.BaseDirectory); } return _toDir; } set { _toDir = value; } } /// /// Overwrite files, even if they are newer than the corresponding /// entries in the archive. The default is . /// public bool Overwrite { get { return _overwrite; } set { _overwrite = value; } } /// /// The character encoding that has been used for filenames inside the /// zip file. The default is the system's OEM code page. /// [TaskAttribute("encoding")] public Encoding Encoding { get { if (_encoding == null) { _encoding = Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); } return _encoding; } set { _encoding = value; } } #endregion Public Instance Properties #region Override implementation of Task /// /// Extracts the files from the zip file. /// protected override void ExecuteTask() { try { // ensure zip file exists if (!ZipFile.Exists) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Zip file '{0}' does not exist.", ZipFile.FullName), Location); } // set encoding of filenames and comment ZipConstants.DefaultCodePage = Encoding.CodePage; using (ZipInputStream s = new ZipInputStream(ZipFile.OpenRead())) { Log(Level.Info, "Unzipping '{0}' to '{1}'.", ZipFile.FullName, ToDirectory.FullName); ZipEntry entry; // extract the file or directory entry while ((entry = s.GetNextEntry()) != null) { if (entry.IsDirectory) { ExtractDirectory(s, entry.Name, entry.DateTime); } else { ExtractFile(s, entry.Name, entry.DateTime, entry.Size); } } // close the zip stream s.Close(); } } catch (IOException ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Failed to extract '{0}' to '{1}'.", ZipFile.FullName, ToDirectory.FullName), Location, ex); } catch (ZipException ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Invalid zip file '{0}'.", ZipFile.FullName), Location, ex); } } #endregion Override implementation of Task #region Private Instance Methods /// /// Extracts a file entry from the specified stream. /// /// The containing the compressed entry. /// The name of the entry including directory information. /// The date of the entry. /// The uncompressed size of the entry. /// /// The destination directory for the entry could not be created. /// -or- /// The entry could not be extracted. /// /// /// We cannot rely on the fact that the directory entry of a given file /// is created before the file is extracted, so we should create the /// directory if it doesn't yet exist. /// protected void ExtractFile(Stream inputStream, string entryName, DateTime entryDate, long entrySize) { // determine destination file FileInfo destFile = new FileInfo(Path.Combine(ToDirectory.FullName, entryName)); // ensure destination directory exists if (!destFile.Directory.Exists) { try { destFile.Directory.Create(); destFile.Directory.LastWriteTime = entryDate; destFile.Directory.Refresh(); } catch (Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Directory '{0}' could not be created.", destFile.DirectoryName), Location, ex); } } // determine if entry actually needs to be extracted if (!Overwrite && destFile.Exists && destFile.LastWriteTime >= entryDate) { Log(Level.Debug, "Skipping '{0}' as it is up-to-date.", destFile.FullName); return; } Log(Level.Verbose, "Extracting '{0}' to '{1}'.", entryName, ToDirectory.FullName); try { // extract the entry using (FileStream sw = new FileStream(destFile.FullName, FileMode.Create, FileAccess.Write)) { int size = 2048; byte[] data = new byte[2048]; // workaround for SharpZipLib issue related to zero-length files: // http://community.sharpdevelop.net/forums/thread/10051.aspx if (entrySize > 0L) { while (true) { size = inputStream.Read(data, 0, data.Length); if (size > 0) { sw.Write(data, 0, size); } else { break; } } } sw.Close(); } } catch (Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Unable to expand '{0}' to '{1}'.", entryName, ToDirectory.FullName), Location, ex); } destFile.LastWriteTime = entryDate; } /// /// Extracts a directory entry from the specified stream. /// /// The containing the directory entry. /// The name of the directory entry. /// The date of the entry. /// /// The destination directory for the entry could not be created. /// protected void ExtractDirectory(Stream inputStream, string entryName, DateTime entryDate) { DirectoryInfo destDir = new DirectoryInfo(Path.Combine( ToDirectory.FullName, entryName)); if (!destDir.Exists) { try { destDir.Create(); destDir.LastWriteTime = entryDate; destDir.Refresh(); } catch (Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Directory '{0}' could not be created.", destDir.FullName), Location, ex); } } } #endregion Private Instance Methods } }