// NAnt - A .NET build tool
// Copyright (C) 2001-2002 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 (ian_maclean@another.com)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Text;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
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.
///
///
/// A can be used to select files to copy. To use
/// a , the attribute
/// must be set.
///
///
///
/// Copy a single file.
///
///
/// ]]>
///
/// Copy a set of files to a new directory.
///
///
///
///
///
///
/// ]]>
///
///
[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 = new Hashtable();
private bool _includeEmptyDirs = true;
private string _encodingName;
private string _filterFile;
private Hashtable _filterMap = new Hashtable();
#endregion Private Instance Fields
#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; }
}
///
/// The encoding to assume when filter-copying the files.
///
[TaskAttribute("encoding")]
public string EncodingName {
get { return _encodingName; }
set { _encodingName = StringUtils.ConvertEmptyToNull(value); }
}
///
/// The file containing the filter used when copying files.
///
[TaskAttribute("filter")]
public string FilterFile {
get { return _filterFile; }
set { _filterFile = StringUtils.ConvertEmptyToNull(value); }
}
#endregion Public Instance Properties
#region Protected Instance Properties
///
/// Gets the encoding that will be used when filter-copying the files.
///
protected Encoding Encoding
{
get {
if (EncodingName != null) {
return System.Text.Encoding.GetEncoding(EncodingName);
}
return null;
}
}
protected Hashtable FileCopyMap {
get { return _fileCopyMap; }
}
#endregion Protected Instance Properties
#region Override implementation of Task
///
/// Checks whether the given encoding is supported on the current
/// platform.
///
/// The used to initialize the task.
protected override void InitializeTask(XmlNode taskNode) {
if (EncodingName != null) {
try {
System.Text.Encoding.GetEncoding(EncodingName);
} catch (ArgumentException) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"{0} is not a valid encoding.",
EncodingName), Location);
} catch (NotSupportedException) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"{0} encoding is not supported on the current platform.",
EncodingName), Location);
}
}
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' or 'todir' attribute cannot both be set."),
Location);
}
if (FilterFile != null) {
try {
FileStream Filters = new FileStream(FilterFile, FileMode.Open, FileAccess.Read);
StreamReader FilterReader = new StreamReader(Filters);
string FilterData = FilterReader.ReadToEnd();
Filters.Close();
System.Text.RegularExpressions.MatchCollection AllFilterPairs = System.Text.RegularExpressions.Regex.Matches(FilterData, @"(?.+)=(?.+)");
foreach (System.Text.RegularExpressions.Match FilterPair in AllFilterPairs)
_filterMap.Add(FilterPair.Groups["filter"].Value.Trim(), FilterPair.Groups["value"].Value.Trim());
}
catch (Exception exx) {
throw new BuildException(exx.Message, 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);
}
// NOTE: when working with file and directory names its useful to
// use the FileInfo an DirectoryInfo classes to normalize paths like:
// c:\work\nant\extras\buildserver\..\..\..\bin
// 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(SourceFile.FullName, dstInfo.FullName);
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) {
// 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.
string 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) {
FileCopyMap.Add(srcInfo.FullName, dstFilePath);
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)) {
Log(Level.Verbose, LogPrefix + "Created directory '{0}'.", destinationDirectory);
Directory.CreateDirectory(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.Keys.Count;
if (fileCount > 0 || Verbose) {
if (ToFile != null) {
Log(Level.Info, LogPrefix + "Copying {0} file{1} to '{2}'.", fileCount, (fileCount != 1) ? "s" : "", ToFile);
} else {
Log(Level.Info, LogPrefix + "Copying {0} file{1} to '{2}'.", fileCount, (fileCount != 1) ? "s" : "", ToDirectory);
}
// loop thru our file list
foreach (string sourceFile in FileCopyMap.Keys) {
string destinationFile = (string) FileCopyMap[sourceFile];
if (Flatten) {
destinationFile = Path.Combine(ToDirectory.FullName,
Path.GetFileName(destinationFile));
}
if (sourceFile == destinationFile) {
Log(Level.Verbose, LogPrefix + "Skipping self-copy of '{0}'.", sourceFile);
continue;
}
try {
Log(Level.Verbose, LogPrefix + "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, LogPrefix + "Created directory '{0}'.", destinationDirectory);
}
// actually copy the file
if (_filterMap.Count == 0)
File.Copy(sourceFile, destinationFile, true);
else {
FileStream SourceFileStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);
StreamReader SourceReader = new StreamReader(SourceFileStream);
string FileData = SourceReader.ReadToEnd();
SourceFileStream.Close();
foreach (System.Collections.DictionaryEntry Entry in _filterMap)
FileData = FileData.Replace("{" + (string)Entry.Key + "}", (string)Entry.Value);
FileStream DestinationFileStream = new FileStream(destinationFile, FileMode.Create, FileAccess.Write);
StreamWriter DestinationWriter = new StreamWriter(DestinationFileStream);
DestinationWriter.WriteLine(FileData);
DestinationWriter.Flush();
DestinationWriter.Close();
}
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Cannot copy '{0}' to '{1}'.", sourceFile, destinationFile),
Location, ex);
}
}
}
}
#endregion Protected Instance Methods
}
}