// NAnt - A .NET build tool // Copyright (C) 2001-2004 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 // // Gert Driesen (gert.driesen@ardatis.com) using System; using System.Collections; using System.Globalization; using System.IO; using ICSharpCode.SharpZipLib.BZip2; using ICSharpCode.SharpZipLib.Checksums; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; 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 tar file from the specified filesets. /// /// /// Uses #ziplib (SharpZipLib), an open source Tar/Zip/GZip library written entirely in C#. /// /// /// /// Tar all files in ${build.dir} and ${doc.dir} into a file /// called "backup.tar.gz", and apply gzip compression to it. /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// [TaskName("tar")] public class TarTask : Task { #region Private Instance Fields private FileInfo _destFile; private TarFileSetCollection _filesets = new TarFileSetCollection(); private bool _includeEmptyDirs = false; private TarCompressionMethod _compressionMethod = TarCompressionMethod.None; private Hashtable _addedDirs = new Hashtable(); #endregion Private Instance Fields #region Public Instance Properties /// /// The tar file to create. /// [TaskAttribute("destfile", Required=true)] public FileInfo DestFile { get { return _destFile; } set { _destFile = value; } } /// /// Include empty directories in the generated tar file. The default is /// . /// [TaskAttribute("includeemptydirs", Required=false)] [BooleanValidator()] public bool IncludeEmptyDirs { get { return _includeEmptyDirs; } set { _includeEmptyDirs = value; } } /// /// The set of files to be included in the archive. /// [BuildElementArray("fileset")] public TarFileSetCollection TarFileSets { get { return _filesets; } } /// /// The compression method. The default is . /// [TaskAttribute("compression")] public TarCompressionMethod CompressionMethod { get { return _compressionMethod; } set { _compressionMethod = value; } } #endregion Public Instance Properties #region Override implementation of Task /// /// Creates the tar file. /// protected override void ExecuteTask() { TarArchive archive = null; Stream outstream = null; Log(Level.Info, "Tarring {0} files to '{1}'.", TarFileSets.FileCount, DestFile.FullName); try { outstream = File.Create(DestFile.FullName); // wrap outputstream with corresponding compression method switch (CompressionMethod) { case TarCompressionMethod.GZip: outstream = new GZipOutputStream(outstream); break; case TarCompressionMethod.BZip2: outstream = new BZip2OutputStream(outstream); break; } // create tar archive archive = TarArchive.CreateOutputTarArchive(outstream, TarBuffer.DefaultBlockFactor); // do not use convert line endings of text files to \n, as this // converts all content to ASCII archive.SetAsciiTranslation(false); // process all filesets foreach (TarFileSet fileset in TarFileSets) { string basePath = fileset.BaseDirectory.FullName; if (Path.GetPathRoot(basePath) != basePath) { basePath = Path.GetDirectoryName(basePath + Path.DirectorySeparatorChar); } // add files to tar 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 filename of the tar entry string entryFileName; // the directory of the tar entry string entryDirName = string.Empty; // determine name of the tar entry if (file.StartsWith(basePath)) { entryFileName = file.Substring(basePath.Length); if (entryFileName.Length > 0 && entryFileName[0] == Path.DirectorySeparatorChar) { entryFileName = entryFileName.Substring(1); } // get directory part of entry entryDirName = Path.GetDirectoryName(entryFileName); // ensure directory separators are understood on linux if (Path.DirectorySeparatorChar == '\\') { entryDirName = entryDirName.Replace(@"\", "/"); } // get filename part of entry entryFileName = Path.GetFileName(entryFileName); } else { entryFileName = Path.GetFileName(file); } // add prefix if specified if (fileset.Prefix != null) { entryDirName = fileset.Prefix + entryDirName; } // ensure directory has trailing slash if (entryDirName.Length != 0) { if (!entryDirName.EndsWith("/")) { entryDirName += '/'; } // create directory entry in archive CreateDirectoryEntry(archive, entryDirName, fileset); } TarEntry entry = TarEntry.CreateEntryFromFile(file); entry.Name = entryDirName + entryFileName; entry.GroupId = fileset.Gid; entry.GroupName = fileset.GroupName; entry.UserId = fileset.Uid; entry.UserName = fileset.UserName; entry.TarHeader.Mode = fileset.FileMode; // write file to tar file archive.WriteEntry(entry, true); } // add (possibly empty) directories to zip if (IncludeEmptyDirs) { // add (possibly empty) directories to tar foreach (string directory in fileset.DirectoryNames) { // skip directories that are not located beneath the base // directory if (!directory.StartsWith(basePath) || directory.Length <= basePath.Length) { continue; } // determine tar 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 in archive CreateDirectoryEntry(archive, entryName, fileset); } } } // close the tar archive archive.CloseArchive(); } catch (Exception ex) { // close the tar output stream if (outstream != null) { outstream.Close(); } // close the tar archive if (archive != null) { archive.CloseArchive(); } // delete the (possibly corrupt) tar file if (DestFile.Exists) { DestFile.Delete(); } throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Tar file '{0}' could not be created.", DestFile.FullName), Location, ex); } } #endregion Override implementation of Task #region Private Instance Methods private void CreateDirectoryEntry(TarArchive archive, string entryName, TarFileSet fileset) { // skip directories that were already added before if (_addedDirs.ContainsKey(entryName)) { return; } // create directory entry TarEntry entry = TarEntry.CreateTarEntry(entryName); entry.GroupId = fileset.Gid; entry.GroupName = fileset.GroupName; entry.UserId = fileset.Uid; entry.UserName = fileset.UserName; entry.TarHeader.Mode = fileset.DirMode; // write directory to tar file archive.WriteEntry(entry, false); // remember that directory entry was added _addedDirs[entryName] = null; } #endregion Private Instance Methods } /// /// Specifies the compression methods supported by . /// public enum TarCompressionMethod { /// /// No compression. /// None = 0, /// /// GZIP compression. /// GZip = 1, /// /// BZIP2 compression. /// BZip2 = 2 } }