// NAnt - A .NET build tool
// Copyright (C) 2001 Gerry Shaw
//
// 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
//
// Mike Krueger (mike@icsharpcode.net)
// Gerry Shaw (gerry_shaw@yahoo.com)
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Text;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.Compression.Types;
namespace NAnt.Compression.Tasks {
///
/// Creates a zip file from the specified filesets.
///
///
/// Uses #ziplib (SharpZipLib), an open source Tar/Zip/GZip library written entirely in C#.
///
///
///
/// Zip all files in ${build.dir} and ${doc.dir} into a file
/// called "backup.zip".
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("zip")]
public class ZipTask : Task {
#region Private Instance Fields
private FileInfo _zipfile;
private int _ziplevel = 6;
private ZipFileSetCollection _filesets = new ZipFileSetCollection();
private DateTime _stampDateTime;
private string _comment;
private bool _includeEmptyDirs;
private DuplicateHandling _duplicateHandling = DuplicateHandling.Add;
private Encoding _encoding;
private Hashtable _addedDirs = new Hashtable();
private Hashtable _fileEntries = new Hashtable();
#endregion Private Instance Fields
#region Public Instance Properties
///
/// The zip file to create.
///
[TaskAttribute("zipfile", Required=true)]
public FileInfo ZipFile {
get { return _zipfile; }
set { _zipfile = value; }
}
///
/// The comment for the file.
///
[TaskAttribute("comment")]
public string Comment {
get { return _comment; }
set { _comment = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Date/time stamp for the files in the format MM/DD/YYYY HH:MM:SS.
///
[TaskAttribute("stampdatetime")]
[DateTimeValidator()]
public DateTime Stamp {
get { return _stampDateTime; }
set { _stampDateTime = value; }
}
///
/// Desired level of compression. Possible values are 0 (STORE only)
/// to 9 (highest). The default is 6.
///
[TaskAttribute("ziplevel")]
[Int32ValidatorAttribute(0, 9)]
public int ZipLevel {
get { return _ziplevel; }
set { _ziplevel = value; }
}
///
/// Include empty directories in the generated zip file. The default is
/// .
///
[TaskAttribute("includeemptydirs")]
[BooleanValidator()]
public bool IncludeEmptyDirs {
get { return _includeEmptyDirs; }
set { _includeEmptyDirs = value; }
}
///
/// The set of files to be included in the archive.
///
[BuildElementArray("fileset")]
public ZipFileSetCollection ZipFileSets {
get { return _filesets; }
set { _filesets = value; }
}
///
/// Specifies the behaviour when a duplicate file is found. The default
/// is .
///
[TaskAttribute("duplicate")]
public DuplicateHandling DuplicateHandling {
get { return _duplicateHandling; }
set { _duplicateHandling = value; }
}
///
/// The character encoding to use for filenames and comment 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
///
/// Creates the zip file.
///
protected override void ExecuteTask() {
ZipOutputStream zOutstream = null;
Log(Level.Info, "Zipping {0} files to '{1}'.",
ZipFileSets.FileCount, ZipFile.FullName);
try {
// set encoding to use for filenames and comment
ZipConstants.DefaultCodePage = Encoding.CodePage;
zOutstream = new ZipOutputStream(ZipFile.Create());
// set compression level
zOutstream.SetLevel(ZipLevel);
// set comment
if (!StringUtils.IsNullOrEmpty(Comment)) {
zOutstream.SetComment(Comment);
}
foreach (ZipFileSet fileset in ZipFileSets) {
string basePath = fileset.BaseDirectory.FullName;
if (Path.GetPathRoot(basePath) != basePath) {
basePath = Path.GetDirectoryName(basePath + Path.DirectorySeparatorChar);
}
// add files to zip
foreach (string file in fileset.FileNames) {
// ensure file exists (in case "asis" was used)
if (!File.Exists(file)) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"File '{0}' does not exist.", file), Location);
}
// the name of the zip entry
string entryName;
// determine name of the zip entry
if (file.StartsWith(basePath)) {
entryName = file.Substring(basePath.Length);
if (entryName.Length > 0 && entryName[0] == Path.DirectorySeparatorChar) {
entryName = entryName.Substring(1);
}
// remember that directory was added to zip file, so
// that we won't add it again later
string dir = Path.GetDirectoryName(file);
if (_addedDirs[dir] == null) {
_addedDirs[dir] = dir;
}
} else {
// flatten directory structure
entryName = Path.GetFileName(file);
}
// add prefix if specified
if (fileset.Prefix != null) {
entryName = fileset.Prefix + entryName;
}
// ensure directory separators are understood on linux
if (Path.DirectorySeparatorChar == '\\') {
entryName = entryName.Replace(@"\", "/");
}
// perform duplicate checking
if (_fileEntries.ContainsKey(entryName)) {
switch (DuplicateHandling) {
case DuplicateHandling.Add:
break;
case DuplicateHandling.Fail:
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
"Duplicate file '{0}' was found.",
entryName), Location.UnknownLocation);
case DuplicateHandling.Preserve:
// skip current entry
continue;
default:
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
"Duplicate value '{0}' is not supported.",
DuplicateHandling.ToString()),
Location.UnknownLocation);
}
}
// create zip entry
ZipEntry entry = new ZipEntry(entryName);
// store entry (to allow for duplicate checking)
_fileEntries[entryName] = null;
// set date/time stamp on zip entry
if (Stamp != DateTime.MinValue) {
entry.DateTime = Stamp;
} else {
entry.DateTime = File.GetLastWriteTime(file);
}
Log(Level.Verbose, "Adding {0}.", entryName);
// write file to zip file
zOutstream.PutNextEntry(entry);
// write file content to stream in small chuncks
using (FileStream fs = File.OpenRead(file)) {
byte[] buffer = new byte[50000];
while (true) {
int bytesRead = fs.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) {
break;
}
zOutstream.Write(buffer, 0, bytesRead);
}
}
}
// add (possibly empty) directories to zip
if (IncludeEmptyDirs) {
foreach (string directory in fileset.DirectoryNames) {
// skip directories that were already added when the
// files were added
if (_addedDirs[directory] != null) {
continue;
}
// skip directories that are not located beneath the base
// directory
if (!directory.StartsWith(basePath) || directory.Length <= basePath.Length) {
continue;
}
// determine zip entry name
string entryName = directory.Substring(basePath.Length + 1);
// add prefix if specified
if (fileset.Prefix != null) {
entryName = fileset.Prefix + entryName;
}
// ensure directory separators are understood on linux
if (Path.DirectorySeparatorChar == '\\') {
entryName = entryName.Replace(@"\", "/");
}
if (!entryName.EndsWith("/")) {
// trailing directory signals to #ziplib that we're
// dealing with directory entry
entryName += "/";
}
// create directory entry
ZipEntry entry = new ZipEntry(entryName);
// write directory to zip file
zOutstream.PutNextEntry(entry);
}
}
}
zOutstream.Close();
zOutstream.Finish();
} catch (Exception ex) {
// close the zip output stream
if (zOutstream != null) {
zOutstream.Close();
}
// delete the (possibly corrupt) zip file
if (ZipFile.Exists) {
ZipFile.Delete();
}
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Zip file '{0}' could not be created.", ZipFile.FullName),
Location, ex);
} finally {
CleanUp();
}
}
#endregion Override implementation of Task
#region Private Instance Methods
private void CleanUp() {
_addedDirs.Clear();
_fileEntries.Clear();
}
#endregion Private Instance Methods
}
}