// 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 // // Gerry Shaw (gerry_shaw@yahoo.com) // Ian MacLean (imaclean@gmail.com) using System; using System.Collections; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Text; using System.Xml; using NAnt.Core.Attributes; using NAnt.Core.Types; using NAnt.Core.Util; using NAnt.Core.Filters; namespace NAnt.Core.Tasks { /// /// Copies a file or set of files to a new file or directory. /// /// /// /// Files are only copied if the source file is newer than the destination /// file, or if the destination file does not exist. However, you can /// explicitly overwrite files with the attribute. /// /// /// When a is used to select files to copy, the /// attribute must be set. Files that are /// located under the base directory of the will /// be copied to a directory under the destination directory matching the /// path relative to the base directory of the , /// unless the attribute is set to /// . /// /// /// Files that are not located under the the base directory of the /// will be copied directly under to the destination /// directory, regardless of the value of the /// attribute. /// ///

Encoding

/// /// Unless an encoding is specified, the encoding associated with the /// system's current ANSI code page is used. /// /// /// An UTF-8, little-endian Unicode, and big-endian Unicode encoded text /// file is automatically recognized, if the file starts with the /// appropriate byte order marks. /// /// /// If you employ filters in your copy operation, you should limit the copy /// to text files. Binary files will be corrupted by the copy operation. /// ///
/// /// /// Copy a single file while changing its encoding from "latin1" to /// "utf-8". /// /// /// /// ]]> /// /// /// /// Copy a set of files to a new directory. /// /// /// /// /// /// /// ]]> /// /// /// /// /// Copy a set of files to a directory, replacing @TITLE@ with /// "Foo Bar" in all files. /// /// /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// [TaskName("copy")] public class CopyTask : Task { #region Private Instance Fields private FileInfo _sourceFile; private FileInfo _toFile; private DirectoryInfo _toDirectory; private bool _overwrite; private bool _flatten; private FileSet _fileset = new FileSet(); private Hashtable _fileCopyMap; private bool _includeEmptyDirs = true; private FilterChain _filters; private Encoding _inputEncoding; private Encoding _outputEncoding; #endregion Private Instance Fields #region Public Instance Constructors /// /// Initialize new instance of the . /// public CopyTask() { if (PlatformHelper.IsUnix) { _fileCopyMap = new Hashtable(); } else { _fileCopyMap = CollectionsUtil.CreateCaseInsensitiveHashtable(); } } #endregion Public Instance Constructors #region Public Instance Properties /// /// The file to copy. /// [TaskAttribute("file")] public virtual FileInfo SourceFile { get { return _sourceFile; } set { _sourceFile = value; } } /// /// The file to copy to. /// [TaskAttribute("tofile")] public virtual FileInfo ToFile { get { return _toFile; } set { _toFile = value; } } /// /// The directory to copy to. /// [TaskAttribute("todir")] public virtual DirectoryInfo ToDirectory { get { return _toDirectory; } set { _toDirectory = value; } } /// /// Overwrite existing files even if the destination files are newer. /// The default is . /// [TaskAttribute("overwrite")] [BooleanValidator()] public bool Overwrite { get { return _overwrite; } set { _overwrite = value; } } /// /// Ignore directory structure of source directory, copy all files into /// a single directory, specified by the /// attribute. The default is . /// [TaskAttribute("flatten")] [BooleanValidator()] public virtual bool Flatten { get { return _flatten; } set { _flatten = value; } } /// /// Copy any empty directories included in the . /// The default is . /// [TaskAttribute("includeemptydirs")] [BooleanValidator()] public bool IncludeEmptyDirs { get { return _includeEmptyDirs; } set { _includeEmptyDirs = value; } } /// /// Used to select the files to copy. To use a , /// the attribute must be set. /// [BuildElement("fileset")] public virtual FileSet CopyFileSet { get { return _fileset; } set { _fileset = value; } } /// /// Chain of filters used to alter the file's content as it is copied. /// [BuildElement("filterchain")] public virtual FilterChain Filters { get { return _filters; } set { _filters = value; } } /// /// The encoding to use when reading files. The default is the system's /// current ANSI code page. /// [TaskAttribute("inputencoding")] public Encoding InputEncoding { get { return _inputEncoding; } set { _inputEncoding = value; } } /// /// The encoding to use when writing the files. The default is /// the encoding of the input file. /// [TaskAttribute("outputencoding")] public Encoding OutputEncoding { get { return _outputEncoding; } set { _outputEncoding = value; } } #endregion Public Instance Properties #region Protected Instance Properties /// /// The set of files to perform a file operation on. /// /// /// /// The key of the is the absolute path of /// the destination file and the value is a /// holding the path and last write time of the most recently updated /// source file that is selected to be copied or moved to the /// destination file. /// /// /// On Windows, the is case-insensitive. /// /// protected Hashtable FileCopyMap { get { return _fileCopyMap; } } #endregion Protected Instance Properties #region Override implementation of Task /// /// Checks whether the task is initialized with valid attributes. /// /// The used to initialize the task. protected override void InitializeTask(XmlNode taskNode) { if (Flatten && ToDirectory == null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "'flatten' attribute requires that 'todir' has been set."), Location); } if (ToDirectory == null && CopyFileSet != null && CopyFileSet.Includes.Count > 0) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "The 'todir' should be set when using the element" + " to specify the list of files to be copied."), Location); } if (SourceFile != null && CopyFileSet != null && CopyFileSet.Includes.Count > 0) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "The 'file' attribute and the element" + " cannot be combined."), Location); } if (ToFile == null && ToDirectory == null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Either the 'tofile' or 'todir' attribute should be set."), Location); } if (ToFile != null && ToDirectory != null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "The 'tofile' and 'todir' attribute cannot both be set."), Location); } } /// /// Executes the Copy task. /// /// A file that has to be copied does not exist or could not be copied. protected override void ExecuteTask() { // ensure base directory is set, even if fileset was not initialized // from XML if (CopyFileSet.BaseDirectory == null) { CopyFileSet.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } // Clear previous copied files _fileCopyMap = new Hashtable(); // copy a single file. if (SourceFile != null) { if (SourceFile.Exists) { FileInfo dstInfo = null; if (ToFile != null) { dstInfo = ToFile; } else { string dstFilePath = Path.Combine(ToDirectory.FullName, SourceFile.Name); dstInfo = new FileInfo(dstFilePath); } // do the outdated check bool outdated = (!dstInfo.Exists) || (SourceFile.LastWriteTime > dstInfo.LastWriteTime); if (Overwrite || outdated) { // add to a copy map of absolute verified paths FileCopyMap.Add(dstInfo.FullName, new FileDateInfo(SourceFile.FullName, SourceFile.LastWriteTime)); if (dstInfo.Exists && dstInfo.Attributes != FileAttributes.Normal) { File.SetAttributes(dstInfo.FullName, FileAttributes.Normal); } } } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Could not find file '{0}' to copy.", SourceFile.FullName), Location); } } else { // copy file set contents. // get the complete path of the base directory of the fileset, ie, c:\work\nant\src DirectoryInfo srcBaseInfo = CopyFileSet.BaseDirectory; // if source file not specified use fileset foreach (string pathname in CopyFileSet.FileNames) { FileInfo srcInfo = new FileInfo(pathname); if (srcInfo.Exists) { // will holds the full path to the destination file string dstFilePath; if (Flatten) { dstFilePath = Path.Combine(ToDirectory.FullName, srcInfo.Name); } else { // Gets the relative path and file info from the full // source filepath // pathname = C:\f2\f3\file1, srcBaseInfo=C:\f2, then // dstRelFilePath=f3\file1 string dstRelFilePath = ""; if (srcInfo.FullName.IndexOf(srcBaseInfo.FullName, 0) != -1) { dstRelFilePath = srcInfo.FullName.Substring( srcBaseInfo.FullName.Length); } else { dstRelFilePath = srcInfo.Name; } if (dstRelFilePath[0] == Path.DirectorySeparatorChar) { dstRelFilePath = dstRelFilePath.Substring(1); } // The full filepath to copy to. dstFilePath = Path.Combine(ToDirectory.FullName, dstRelFilePath); } // do the outdated check FileInfo dstInfo = new FileInfo(dstFilePath); bool outdated = (!dstInfo.Exists) || (srcInfo.LastWriteTime > dstInfo.LastWriteTime); if (Overwrite || outdated) { // construct FileDateInfo for current file FileDateInfo newFile = new FileDateInfo(srcInfo.FullName, srcInfo.LastWriteTime); // if multiple source files are selected to be copied // to the same destination file, then only the last // updated source should actually be copied FileDateInfo oldFile = (FileDateInfo) FileCopyMap[dstInfo.FullName]; if (oldFile != null) { // if current file was updated after scheduled file, // then replace it if (newFile.LastWriteTime > oldFile.LastWriteTime) { FileCopyMap[dstInfo.FullName] = newFile; } } else { FileCopyMap.Add(dstInfo.FullName, newFile); if (dstInfo.Exists && dstInfo.Attributes != FileAttributes.Normal) { File.SetAttributes(dstInfo.FullName, FileAttributes.Normal); } } } } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Could not find file '{0}' to copy.", srcInfo.FullName), Location); } } if (IncludeEmptyDirs && !Flatten) { // create any specified directories that weren't created during the copy (ie: empty directories) foreach (string pathname in CopyFileSet.DirectoryNames) { DirectoryInfo srcInfo = new DirectoryInfo(pathname); // skip directory if not relative to base dir of fileset if (srcInfo.FullName.IndexOf(srcBaseInfo.FullName) == -1) { continue; } string dstRelPath = srcInfo.FullName.Substring(srcBaseInfo.FullName.Length); if (dstRelPath.Length > 0 && dstRelPath[0] == Path.DirectorySeparatorChar) { dstRelPath = dstRelPath.Substring(1); } // The full filepath to copy to. string destinationDirectory = Path.Combine(ToDirectory.FullName, dstRelPath); if (!Directory.Exists(destinationDirectory)) { try { Directory.CreateDirectory(destinationDirectory); } catch (Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Failed to create directory '{0}'.", destinationDirectory ), Location, ex); } Log(Level.Verbose, "Created directory '{0}'.", destinationDirectory); } } } } // do all the actual copy operations now DoFileOperations(); } #endregion Override implementation of Task #region Protected Instance Methods /// /// Actually does the file copies. /// protected virtual void DoFileOperations() { int fileCount = FileCopyMap.Count; if (fileCount > 0 || Verbose) { if (ToFile != null) { Log(Level.Info, "Copying {0} file{1} to '{2}'.", fileCount, (fileCount != 1) ? "s" : "", ToFile); } else { Log(Level.Info, "Copying {0} file{1} to '{2}'.", fileCount, (fileCount != 1) ? "s" : "", ToDirectory); } // loop thru our file list foreach (DictionaryEntry fileEntry in FileCopyMap) { string destinationFile = (string) fileEntry.Key; string sourceFile = ((FileDateInfo) fileEntry.Value).Path; if (sourceFile == destinationFile) { Log(Level.Verbose, "Skipping self-copy of '{0}'.", sourceFile); continue; } try { Log(Level.Verbose, "Copying '{0}' to '{1}'.", sourceFile, destinationFile); // create directory if not present string destinationDirectory = Path.GetDirectoryName(destinationFile); if (!Directory.Exists(destinationDirectory)) { Directory.CreateDirectory(destinationDirectory); Log(Level.Verbose, "Created directory '{0}'.", destinationDirectory); } // copy the file with filters FileUtils.CopyFile(sourceFile, destinationFile, Filters, InputEncoding, OutputEncoding); } catch (Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Cannot copy '{0}' to '{1}'.", sourceFile, destinationFile), Location, ex); } } } } #endregion Protected Instance Methods /// /// Holds the absolute paths and last write time of a given file. /// protected class FileDateInfo { #region Public Instance Constructors /// /// Initializes a new instance of the /// class for the specified file and last write time. /// /// The absolute path of the file. /// The last write time of the file. public FileDateInfo(string path, DateTime lastWriteTime) { _path = path; _lastWriteTime = lastWriteTime; } #endregion Public Instance Constructors #region Public Instance Properties /// /// Gets the absolute path of the current file. /// /// /// The absolute path of the current file. /// public string Path { get { return _path; } } /// /// Gets the time when the current file was last written to. /// /// /// The time when the current file was last written to. /// public DateTime LastWriteTime { get { return _lastWriteTime; } } #endregion Public Instance Properties #region Private Instance Fields private DateTime _lastWriteTime; private string _path; #endregion Private Instance Fields } } }