I've been following Nant for some time now and just wanted to contribute 
some code
to the project.  It's something that I've been using at work to do part of our
mechanized builds (Tivoli packages) for two different projects.  Anyway, 
for those
with Tivoli experience, you simply can't put everything into a single 
fileset, you
really need to 'merge' a few different directories and prefix things.  This was
one of the reasons why Ant implemented zipfilesets.  So here it is, a basic 
implementation
of the zipfileset tag as specified in the Ant doc's
(http://jakarta.apache.org/ant/manual/CoreTasks/zip.html).  Implemented:
        * includes / excludes facility as implemented in filesets (simply wrapped)
        * property substitutions supported
        * prefix's supported
        * dir supported (similar to basedir but sports a different function)
        * networks paths supported

A common setup could be of the following:
        <zip zipfile="C:\package\dist_2002.zip" ziplevel="9">
                <fileset>
                        <includes basedir="d:\" name="*.build" />
                </fileset>
                        
                <zipfileset dir="${homeDir}" includes="*.log" prefix="info" />
                <zipfileset dir="C:\Inetpub\wwwroot\wsQuotingFacility\" includes="**" 
prefix="wsQuotingFacility" />
                <zipfileset dir="C:\Inetpub\wwwroot\StaticQuotePages" includes="**" 
prefix="StaticQuotePages" />
                <zipfileset dir="\\bbstip3\projects$\eCommerce\Project Documents" 
includes="**.doc" excludes="temp.doc" prefix="docs" />
        </zip>

The zip archive dist_2002.zip would look like (if run from build directory):
        build\test.build
        info\change.log
        info\history.log
        wsQuotingFacility\test.asmx
        wsQuotingFacility\web.config
        StaticQuotePages\entry.html
        StaticQuotePages\login.html
        StaticQuotePages\includes\formatting.js
        StaticQuotePages\includes\header.html
        StaticQuotePages\includes\footer.html
        docs\requirements.doc
        docs\revised\requirements.doc
        
The code basically was tested with the latest CVS snapshot as of 8/13/2002 
9:00PM
and it's backwards compatible with the current zip task (always a good 
thing).  Did
have to do some refactoring (to pull some commonly shared code with fileset 
& zipfileset).
Besides, ExecuteTask looks much cleaner now....  I'm still putting together 
a 'polished'
set of NUnit test cases that can be contributed, but I just wanted to get a 
feel for
what people thought.

One thing that I have noticed while working on this task is that the 
SharpZipLib
currently requires the "file to be compressed" to be loaded into memory 
first.  This is
okay for small files but would cause issues for larger files (i.e. system 
would be required
to page swap a lot more).  I'm wondering, as I haven't researched it, 
whether SharpZipLib
has plans to allow streamed input.  Since compression usually works on 64Kb 
'data' windows,
or 128Kb if you're using UltraCompressor - http://www.xs4all.nl/~aipnl/, 
you really don't
have to load the entire file into memory at once.

Enjoy!

-Wally


---------------------------------------------------------------------------------------------------
// 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 ([EMAIL PROTECTED])
// Gerry Shaw ([EMAIL PROTECTED])

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Xml;
using SourceForge.NAnt.Attributes;

using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;

namespace SourceForge.NAnt.Tasks {

     /// <summary>
     /// A task to create a zip file from a specified fileset.
     /// </summary>
     /// <remarks>
     ///   <para>Uses <a 
href="http://www.icsharpcode.net/OpenSource/NZipLib/";>NZipLib</a>, an open 
source Zip/GZip library written entirely in C#.</para>
     /// </remarks>
     /// <example>
     ///   <para>Zip all files in the subdirectory <c>build</c> to 
<c>backup.zip</c>.</para>
     ///   <code>
     ///     <![CDATA[
     /// <zip zipfile="backup.zip">
     ///     <fileset basedir="build">
     ///         <includes name="*.*"/>
     ///     </fileset>
     /// </zip>
     ///     ]]>
     ///   </code>
     /// </example>
     [TaskName("zip")]
     public class ZipTask : Task {
         string _zipfile = null;
         int _ziplevel = 6;
         FileSet _fileset = new FileSet();
         Crc32 crc = new Crc32();
                ArrayList _zipFileSetGroup=new ArrayList();

         /// <summary>The zip file to create.</summary>
         [TaskAttribute("zipfile", Required=true)]
         public string ZipFileName { get { return 
Project.GetFullPath(_zipfile); } set {_zipfile = value; } }

         /// <summary>Desired level of compression (default is 6).</summary>
         /// <value>0 - 9 (0 - STORE only, 1-9 DEFLATE (1-lowest, 
9-highest))</value>
         [TaskAttribute("ziplevel", Required=false)]
         public int    ZipLevel    { get { return _ziplevel; } set 
{_ziplevel = value; } }

         /// <summary>The set of files to be included in the archive.</summary>
         [FileSet("fileset")]
         public FileSet ZipFileSet { get { return _fileset; } }

         protected override void ExecuteTask() {
             ZipOutputStream zOutstream = new 
ZipOutputStream(File.Create(ZipFileName));

                  // set the compression level
                  int zipLevel = ZipLevel;
                  if (zipLevel > 0) {
                     zOutstream.SetLevel(zipLevel);
                  }
                  else {
                     zOutstream.SetMethod(ZipOutputStream.STORED);
                  }

                  AppendFilesFromFileSetToZipArchive(ref zOutstream);
                  AppendFilesFromZipFileSetsToZipArchive(ref zOutstream);

                  zOutstream.Finish();
                  zOutstream.Close();
            }

            private String RemoveLeadingPath(string leadPath,string path) {
                    if(null==path) return null; // avoid 'null' scenarios up-front

                    if(path.StartsWith(leadPath)) {
                            path=path.Substring(leadPath.Length);
                    }

                    // we strip any leading seperator char's as they tend to arise
                    // inappropriately for network paths, not sure if this is a 
'feature'
                    // or a special case of the Path.GetPathRoot functionality
                    while(path[0]==Path.DirectorySeparatorChar) {
                            path=path.Substring(1);
                    }
                    return path;

            }
        
            /// <summary>
            /// Replace the leading part of the full file path with the prefix.
            /// we spend effort to remove leading directory seperators from the 
'fn' so that
            /// the zip archive doesn't contain any of them in the leading position
            /// For example, we want the archive to have
            ///    dir123\file.dat
            /// Not
            ///    \dir123\file.dat
            /// </summary>
            /// <param name="startDir"></param>
            /// <param name="prefix"></param>
            /// <param name="fn"></param>
            /// <returns></returns>
            private String ReplaceFullFilePathWithPrefix(string startDir, string 
prefix, string fn)
            {
                    string rootPath=Path.GetPathRoot(fn);

                    // strip rootPath from startDir & fn
                    startDir=RemoveLeadingPath(rootPath,startDir);
                    fn=RemoveLeadingPath(rootPath,fn);

                    if(0<prefix.Length) {
                            fn=RemoveLeadingPath(startDir,fn);          
                            return Path.Combine(prefix,fn);
                    }
                    return fn;
            }

            private void AppendFilesFromFileSetToZipArchive(ref ZipOutputStream 
zOutstream) {
                    if(Verbose) {
                            int fileCount=ZipFileSet.FileNames.Count;
                            string fileText=(1==fileCount) ? "file" : "files";
                            Log.WriteLine(LogPrefix + "Zipping {0} {1} to {2}", 
ZipFileSet.FileNames.Count, fileText, ZipFileName);
                    }

                    string basePath = 
Path.GetDirectoryName(Project.GetFullPath(ZipFileSet.BaseDirectory));

                    foreach (string file in ZipFileSet.FileNames) {
                            string entryName = file.Substring(basePath.Length + 1);
                            ZipFileUp(ref zOutstream, file, entryName);
                    }
            }

            private void AppendFilesFromZipFileSetsToZipArchive(ref 
ZipOutputStream zOutstream) {
                    foreach(ZipFileSetElement zfs in _zipFileSetGroup) {
                            string 
filePath=Path.GetDirectoryName(Project.GetFullPath(zfs.Dir+Path.DirectorySeparatorChar));

                            FileSet fs=new FileSet();
                            fs.BaseDirectory=filePath;
                            fs.Includes.Add(zfs.IncludesPattern);
                            fs.DefaultExcludes=false;
                            fs.Excludes.Add(zfs.ExcludesPattern);

                            if(Verbose) {
                                    int fileCount=fs.FileNames.Count;
                                    string fileText=(1==fileCount) ? "file" : "files";
                                    Log.WriteLine(LogPrefix + "Zipping {0} {1} to 
{2}", 
ZipFileSet.FileNames.Count, fileText, ZipFileName);
                            }

                            int i=0;
                            foreach(String fn in fs.FileNames) {
                                    string 
zipEntryName=ReplaceFullFilePathWithPrefix(fs.BaseDirectory,zfs.Prefix,fn);
                                    ZipFileUp(ref zOutstream, fn, zipEntryName);
                                        
                                    if(++i>5) break;
                            }
                    }
            }

            /// <summary>
            /// NOTE: Since we are reading in the entire file into memory and then 
compressing it,
            /// we will be incurring a memory penalty (especially in terms of 
memory allocation/
            /// garbage collection). What would be better is to pass the data 
stream to
            /// the NZipLib, however, currently that library doesn't handle 
this.  Their is also
            /// a performance limitation on larger files, which have to be loaded 
into memory all
            /// at once.
            /// NOTE: Would it make sense to research some sort of mechanism to 
zip-up files that
            /// are currently locked by applications?  For example, my need is to 
have the Visual
            /// Studio project to be archived, I don't want to quit my project to 
be able to zip
            /// things up.
            /// </summary>
            /// <param name="zOutstream"></param>
            /// <param name="fnOnDisk"></param>
            /// <param name="zipEntryName"></param>
            private void ZipFileUp(ref ZipOutputStream zOutstream, string 
fnOnDisk, string zipEntryName) {
                    if (!File.Exists(fnOnDisk)) {
                            throw new FileNotFoundException();
                    }
                
                    // read source file
                    FileStream fStream = File.OpenRead(fnOnDisk);

                    long   fileSize = fStream.Length;
                    byte[] buffer = new byte[fileSize];
                    fStream.Read(buffer, 0, buffer.Length);
                    fStream.Close();

                    // create ZIP entry
                    ZipEntry entry = new ZipEntry(zipEntryName);
                    if (Verbose) {
                            Log.WriteLine(LogPrefix + " Adding {0}", zipEntryName);
                    }

                    if (ZipLevel == 0) {
                            entry.Size = fileSize;

                            // calculate crc32 of current file
                            crc.Reset();
                            crc.Update(buffer);
                            entry.Crc  = crc.Value;
                    }

                    // write file to ZIP
                    zOutstream.PutNextEntry(entry);
                    zOutstream.Write(buffer, 0, buffer.Length);
            }

            protected override void InitializeElement(XmlNode elementNode) {
                    // The ZipFileSet class will initialize the marked xml attributes 
but
                    // not the unmarked sub-elements.  We don't have any for 
ZipFileSet yet,
                    // but they could go here.  Alternatively we could have built the
                    // group-of-ZipFileSet from an overridden InitializeTask.
                    foreach (XmlNode node in elementNode) {
                            if("zipfileset".Equals(node.Name)) {
                                    ZipFileSetElement zfs = new ZipFileSetElement();
                                    zfs.Project=Project;
                                    zfs.Initialize(node);

                                    _zipFileSetGroup.Add(zfs);
                            }
                    }
            }

            [ElementName("zipfileset")]
                    private class ZipFileSetElement : Element
            {
                    private string _dir="";
                    private string _prefix="";
                    private string _includesPattern="";
                    private string _excludesPattern="";

                    [TaskAttribute("prefix", Required=false)]
                    public string Prefix
                    {
                            get { return _prefix; }
                            set { _prefix=value;}
                    }

                    [TaskAttribute("dir", Required=false)]
                    public string Dir
                    {
                            get { return _dir; }
                            set { _dir=value; }
                    }

                    [TaskAttribute("includes", Required=false)]
                    public string IncludesPattern
                    {
                            get { return _includesPattern; }
                            set { _includesPattern=value;}
                    }

                    [TaskAttribute("excludes", Required=false)]
                    public string ExcludesPattern
                    {
                            get { return _excludesPattern; }
                            set { _excludesPattern=value;}
                    }
            }
     }
}



-------------------------------------------------------
This sf.net email is sponsored by: Dice - The leading online job board
for high-tech professionals. Search and apply for tech jobs today!
http://seeker.dice.com/seeker.epl?rel_code=31
_______________________________________________
Nant-developers mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/nant-developers

Reply via email to