Friday, June 26, 2009

Working with the 1.5 code base : part 4

In this part I will cover the FTP Source Control. At the base it is not that hard, since most of the stuff we already have :-). I added a class 'FtpSourceControl' that implements ISourceControl. This interface has the following definition:
public interface ISourceControl
{
Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to);

void LabelSourceControl(IIntegrationResult result);
void GetSource(IIntegrationResult result);

void Initialize(IProject project);
void Purge(IProject project);
}

The only item we do not have is the GetModifications in IFtpLib, so this needs an update : Modification[] ListNewOrUpdatedFilesAtFtpSite(string localFolder, string remoteFolder, bool recursive);
The implementation of this method, is also very easy, it is the same as the download, except that it does not need to retieve the file, only list it.
And that is it, easy. Ok, this code still needs some cleanup, but it shows how you can create new functionality for CCNet, step by step.
The part where you are waiting on :

The code

IFtpLib
using System;
namespace ThoughtWorks.CruiseControl.Core.Util
{
interface IFtpLib
{
/// <summary>
/// Logs into the specified server, with the userName and password
/// If activeConnectionMode is set to true, active connection is used,
/// otherwise passive connection.
/// </summary>
/// <param name="serverName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
/// <param name="activeConnectionMode"></param>
void LogIn(string serverName, string userName, string password, bool activeConnectionMode);

/// <summary>
/// Disconnects from the server
/// </summary>
void DisConnect();

/// <summary>
/// returns true if connected
/// </summary>
/// <returns></returns>
bool IsConnected();

/// <summary>
/// returns the current path of the server
/// </summary>
/// <returns></returns>
string CurrentWorkingFolder();

/// <summary>
/// downloads the remoter folder to the local folder, recursive if wanted
/// </summary>
/// <param name="localFolder"></param>
/// <param name="remoteFolder"></param>
/// <param name="recursive"></param>
void DownloadFolder(string localFolder, string remoteFolder, bool recursive);

/// <summary>
/// Uploads a local folder to the specified remotefolder, recursive if wanted
/// </summary>
/// <param name="remoteFolder"></param>
/// <param name="localFolder"></param>
/// <param name="recursive"></param>
void UploadFolder(string remoteFolder, string localFolder, bool recursive);

/// <summary>
/// Returns a list of new or updated files at the ftp site, compared to a local folder
/// </summary>
/// <param name="localFolder"></param>
/// <param name="remoteFolder"></param>
/// <param name="recursive"></param>
/// <returns></returns>
Modification[] ListNewOrUpdatedFilesAtFtpSite(string localFolder, string remoteFolder, bool recursive);
}
}
FtpLib
namespace ThoughtWorks.CruiseControl.Core.Util
{
public class FtpLib : IFtpLib
{
private EnterpriseDT.Net.Ftp.FTPConnection FtpServer;
private Tasks.TaskBase CallingTask;
private Util.BuildProgressInformation bpi;

public FtpLib(Tasks.TaskBase callingTask, Util.BuildProgressInformation buildProgressInformation)
{
CallingTask = callingTask;
bpi = buildProgressInformation;

this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;

this.FtpServer.Downloaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Downloaded);

this.FtpServer.Uploaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Uploaded);

}

public FtpLib(Util.BuildProgressInformation buildProgressInformation)
{
bpi = buildProgressInformation;

this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;
}

public FtpLib()
{
this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;

}

public void LogIn(string serverName, string userName, string password, bool activeConnectionMode)
{

Log.Info("Connecting to {0} ...", serverName);

{
this.FtpServer.ServerAddress = serverName;
this.FtpServer.UserName = userName;
this.FtpServer.Password = password;
this.FtpServer.Connect();

if (activeConnectionMode)
{
Log.Debug("Active mode enabled");
this.FtpServer.ConnectMode = EnterpriseDT.Net.Ftp.FTPConnectMode.ACTIVE;
}
else
{
Log.Debug("Passive mode enabled");
this.FtpServer.ConnectMode = EnterpriseDT.Net.Ftp.FTPConnectMode.PASV;
}

this.FtpServer.TransferType = EnterpriseDT.Net.Ftp.FTPTransferType.BINARY;
}
}

public void DownloadFolder(string localFolder, string remoteFolder, bool recursive)
{

this.FtpServer.ChangeWorkingDirectory(remoteFolder);

EnterpriseDT.Net.Ftp.FTPFile[] FtpServerFileInfo = this.FtpServer.GetFileInfos();

string LocalTargetFolder = null;
string FtpTargetFolder = null;
bool DownloadFile = false;
string LocalFile = null;
System.IO.FileInfo fi = default(System.IO.FileInfo);

if (!System.IO.Directory.Exists(localFolder))
{
Log.Debug("creating {0}", localFolder);
System.IO.Directory.CreateDirectory(localFolder);
}

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in FtpServerFileInfo)
{
if (recursive)
{
if (CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name != "." && CurrentFileOrDirectory.Name != "..")
{

LocalTargetFolder = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);
FtpTargetFolder = string.Format("{0}/{1}", remoteFolder, CurrentFileOrDirectory.Name);

if (!System.IO.Directory.Exists(LocalTargetFolder))
{
Log.Debug("creating {0}", LocalTargetFolder);
System.IO.Directory.CreateDirectory(LocalTargetFolder);
}

DownloadFolder(LocalTargetFolder, FtpTargetFolder, recursive);

//set the ftp working folder back to the correct value
this.FtpServer.ChangeWorkingDirectory(remoteFolder);
}
}

if (!CurrentFileOrDirectory.Dir)
{
DownloadFile = false;

LocalFile = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);


// check file existence
if (!System.IO.File.Exists(LocalFile))
{
DownloadFile = true;
}
else
{
//check file size
fi = new System.IO.FileInfo(LocalFile);
if (CurrentFileOrDirectory.Size != fi.Length)
{
DownloadFile = true;
System.IO.File.Delete(LocalFile);
}
else
{
//check modification time
if (CurrentFileOrDirectory.LastModified != fi.CreationTime)
{
DownloadFile = true;
System.IO.File.Delete(LocalFile);

}
}
}

if (DownloadFile)
{
Log.Debug("Downloading {0}", CurrentFileOrDirectory.Name);
this.FtpServer.DownloadFile(localFolder, CurrentFileOrDirectory.Name);

fi = new System.IO.FileInfo(LocalFile);
fi.CreationTime = CurrentFileOrDirectory.LastModified;
fi.LastAccessTime = CurrentFileOrDirectory.LastModified;
fi.LastWriteTime = CurrentFileOrDirectory.LastModified;
}

}

}
}

public void UploadFolder(string remoteFolder, string localFolder, bool recursive)
{
string[] LocalFiles = null;

LocalFiles = System.IO.Directory.GetFiles(localFolder, "*.*");
this.FtpServer.ChangeWorkingDirectory(remoteFolder);

// remove the local folder value, so we can work relative
for (int i = 0; i <= LocalFiles.Length - 1; i++)
{
LocalFiles[i] = LocalFiles[i].Remove(0, localFolder.Length + 1);
}

//upload files
//FtpServer.Exists throws an error, so we must do it ourselves
EnterpriseDT.Net.Ftp.FTPFile[] FtpServerFileInfo = this.FtpServer.GetFileInfos();

foreach (var LocalFile in LocalFiles)
{
if (!FileExistsAtFtp(FtpServerFileInfo, LocalFile))
{
this.FtpServer.UploadFile(System.IO.Path.Combine(localFolder, LocalFile), LocalFile);
}
else
{
if (FileIsDifferentAtFtp(FtpServerFileInfo, LocalFile, localFolder))
{
this.FtpServer.DeleteFile(LocalFile);
this.FtpServer.UploadFile(System.IO.Path.Combine(localFolder, LocalFile), LocalFile);
}
}
}

if (!recursive) return;

//upload folders
string[] Folders = null;

string LocalTargetFolder = null;
string FtpTargetFolder = null;

Folders = System.IO.Directory.GetDirectories(localFolder);

// remove the local folder value, so we can work relative
for (int i = 0; i <= Folders.Length - 1; i++)
{
Folders[i] = Folders[i].Remove(0, localFolder.Length + 1);
}

foreach (var Folder in Folders)
{
//explicit set the folder back, because of recursive calls
this.FtpServer.ChangeWorkingDirectory(remoteFolder);

if (!FolderExistsAtFtp(FtpServerFileInfo, Folder))
{
this.FtpServer.CreateDirectory(Folder);
}

LocalTargetFolder = System.IO.Path.Combine(localFolder, Folder);
FtpTargetFolder = string.Format("{0}/{1}", remoteFolder, Folder);

UploadFolder(FtpTargetFolder, LocalTargetFolder, recursive);
}
}

public void DisConnect()
{
this.FtpServer.Close();
}

public bool IsConnected()
{
return this.FtpServer.IsConnected;
}

public string CurrentWorkingFolder()
{
return this.FtpServer.ServerDirectory;
}

public Modification[] ListNewOrUpdatedFilesAtFtpSite(string localFolder, string remoteFolder, bool recursive)
{
System.Collections.Generic.List<Modification> mods = new System.Collections.Generic.List<Modification>();

GetTheList(mods, localFolder, remoteFolder, recursive);

return mods.ToArray();
}

private void GetTheList(System.Collections.Generic.List<Modification> mods, string localFolder, string remoteFolder, bool recursive)
{
this.FtpServer.ChangeWorkingDirectory(remoteFolder);

EnterpriseDT.Net.Ftp.FTPFile[] FtpServerFileInfo = this.FtpServer.GetFileInfos();

string LocalTargetFolder = null;
string FtpTargetFolder = null;
bool DownloadFile = false;
string LocalFile = null;
System.IO.FileInfo fi = default(System.IO.FileInfo);

if (!System.IO.Directory.Exists(localFolder))
{
Log.Debug("creating {0}", localFolder);
System.IO.Directory.CreateDirectory(localFolder);
}

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in FtpServerFileInfo)
{
if (recursive)
{
if (CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name != "." && CurrentFileOrDirectory.Name != "..")
{

LocalTargetFolder = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);
FtpTargetFolder = string.Format("{0}/{1}", remoteFolder, CurrentFileOrDirectory.Name);

if (!System.IO.Directory.Exists(LocalTargetFolder))
{
Log.Debug("creating {0}", LocalTargetFolder);
System.IO.Directory.CreateDirectory(LocalTargetFolder);
}

GetTheList(mods, LocalTargetFolder, FtpTargetFolder, recursive);

//set the ftp working folder back to the correct value
this.FtpServer.ChangeWorkingDirectory(remoteFolder);
}
}

if (!CurrentFileOrDirectory.Dir)
{
DownloadFile = false;
Modification m = new Modification();

LocalFile = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);


// check file existence
if (!System.IO.File.Exists(LocalFile))
{
DownloadFile = true;
m.Type = "added";
}
else
{
//check file size
fi = new System.IO.FileInfo(LocalFile);
if (CurrentFileOrDirectory.Size != fi.Length)
{
DownloadFile = true;
m.Type = "Updated";
}
else
{
//check modification time
if (CurrentFileOrDirectory.LastModified != fi.CreationTime)
{
DownloadFile = true;
m.Type = "Updated";
}
}
}

if (DownloadFile)
{
m.FileName = CurrentFileOrDirectory.Name;
m.FolderName = remoteFolder;
m.ModifiedTime = CurrentFileOrDirectory.LastModified;

mods.Add(m);
}
}

}

}

private bool FileExistsAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFileName)
{

bool Found = false;

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (!CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name.ToLower() == localFileName.ToLower())
{
Found = true;
}
}

return Found;
}

private bool FolderExistsAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFileName)
{

bool Found = false;
string updatedFolderName = null;

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (CurrentFileOrDirectory.Name.EndsWith("/"))
{
updatedFolderName = CurrentFileOrDirectory.Name.Remove(CurrentFileOrDirectory.Name.Length - 1, 1);
}
else
{
updatedFolderName = CurrentFileOrDirectory.Name;
}

if (CurrentFileOrDirectory.Dir && updatedFolderName.ToLower() == localFileName.ToLower())
{
Found = true;
}
}

return Found;
}

private bool FileIsDifferentAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFile, string localFolder)
{
bool isDifferent = false;
System.IO.FileInfo fi = default(System.IO.FileInfo);

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (!CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name.ToLower() == localFile.ToLower())
{
fi = new System.IO.FileInfo(System.IO.Path.Combine(localFolder, localFile));

if (fi.Length != CurrentFileOrDirectory.Size || fi.LastWriteTime != CurrentFileOrDirectory.LastModified)
{
isDifferent = true;
}
}
}

return isDifferent;
}

private void HandleMessages(object sender, EnterpriseDT.Net.Ftp.FTPMessageEventArgs e)
{
bpi.AddTaskInformation(e.Message);

Log.Debug(e.Message);
}

private void FtpServer_Uploaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Uploaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Uploaded : ", e.RemoteDirectory, e.RemoteFile);

AddTaskStatusItem(file);
}

private void FtpServer_Downloaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Downloaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Downloaded : ", e.RemoteDirectory, e.RemoteFile);

AddTaskStatusItem(file);
}

private void AddTaskStatusItem(string information)
{
CallingTask.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(information));

if (CallingTask.CurrentStatus.ChildItems.Count > 10)
{
CallingTask.CurrentStatus.ChildItems.RemoveAt(0);
}

}
}
}
FtpSourceControl


using System;
using System.Collections.Generic;
using System.IO;
using Exortech.NetReflector;
using ThoughtWorks.CruiseControl.Core.Util;

namespace ThoughtWorks.CruiseControl.Core.Sourcecontrol
{
[ReflectorType("ftpSourceControl")]
public class FtpSourceControl : ISourceControl
{
private FtpLib ftp;

[ReflectorProperty("serverName", Required = true)]
public string ServerName = string.Empty;

[ReflectorProperty("userName", Required = true)]
public string UserName = string.Empty;

[ReflectorProperty("password", Required = true)]
public string Password = string.Empty;

[ReflectorProperty("useActiveConnectionMode", Required = false)]
public bool UseActiveConnectionMode = true;

[ReflectorProperty("ftpFolderName", Required = true)]
public string FtpFolderName = string.Empty;

[ReflectorProperty("localFolderName", Required = true)]
public string LocalFolderName = string.Empty;

[ReflectorProperty("recursiveCopy", Required = true)]
public bool RecursiveCopy = true;

#region ISourceControl Members

public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)
{
ftp = new FtpLib(to.BuildProgressInformation);
string remoteFolder = FtpFolderName;

ftp.LogIn(ServerName,UserName,Password,UseActiveConnectionMode);

if (!FtpFolderName.StartsWith("/"))
{
remoteFolder = System.IO.Path.Combine(ftp.CurrentWorkingFolder(), FtpFolderName);
}

Modification[] mods = ftp.ListNewOrUpdatedFilesAtFtpSite(LocalFolderName, remoteFolder, RecursiveCopy);

ftp.DisConnect();

return mods;
}

public void LabelSourceControl(IIntegrationResult result)
{
}

public void GetSource(IIntegrationResult result)
{
Util.Log.Info(result.HasModifications().ToString());

ftp = new FtpLib(result.BuildProgressInformation);
string remoteFolder = FtpFolderName;

ftp.LogIn(ServerName, UserName, Password, UseActiveConnectionMode);


if (!FtpFolderName.StartsWith("/"))
{
remoteFolder = System.IO.Path.Combine(ftp.CurrentWorkingFolder(), FtpFolderName);
}

ftp.DownloadFolder( LocalFolderName, remoteFolder, RecursiveCopy);

ftp.DisConnect();
}

public void Initialize(IProject project)
{
}

public void Purge(IProject project)
{
}

#endregion
}
}

Tuesday, June 23, 2009

Working with the 1.5 code base : part 3

Showing the build progress. This requires just a small update to the code:
Things updates :
° updated the constructor so it takes the BuildProgressInformation as an extra argument
° updated the creation of the ftp lib (duh)
° updated the HandleMessages inside the ftplib to visualize the buildprogress info.

The new constructor

private Tasks.TaskBase CallingTask;
private Util.BuildProgressInformation bpi;

public FtpLib(Tasks.TaskBase callingTask, Util.BuildProgressInformation buildProgressInformation)
{
CallingTask = callingTask;
bpi = buildProgressInformation;

this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;

this.FtpServer.Downloaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Downloaded);

this.FtpServer.Uploaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Uploaded);

}


Updating the handle message

private void HandleMessages(object sender, EnterpriseDT.Net.Ftp.FTPMessageEventArgs e)
{
bpi.AddTaskInformation(e.Message);

Log.Info(e.Message);
}

Monday, June 22, 2009

Working with the 1.5 code base : part 2

In this part I'll cover how to give more detailed information of the task being executed. Not in the build stage view, but in the task detail view (F4 in CCTray).
As Craig pointed out, this is a rather easy update, thanks Craig.
On updating the Ftp task, I also did some smaller updates :
° make some settings not mandatory : useActiveConnectionMode, action
° make a better standard task description
° create the local folder if it does not exist

Now, in order to have the task show 'sub items'., all you have to do is call the following : Task.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(""));
Off course, add something meaningful for the string value. I added the files being upload/downloaded to this overview, so you have an idea what the ftp task is doing.


Changes in the code : ftpLib

Add a new constructor, that takes in the calling task and add the addChild on the uploaded/downloaded events.
private Tasks.TaskBase CallingTask;

public FtpLib(Tasks.TaskBase callingTask)
{
CallingTask = callingTask;
this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();
this.FtpServer.ReplyReceived += HandleMessages;
this.FtpServer.CommandSent += HandleMessages;
this.FtpServer.Downloaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Downloaded);
this.FtpServer.Uploaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Uploaded);
}

void FtpServer_Uploaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Uploaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Uploaded : ", e.RemoteDirectory, e.RemoteFile);

CallingTask.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(file));
}

void FtpServer_Downloaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Downloaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Downloaded : ", e.RemoteDirectory, e.RemoteFile);

CallingTask.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(file));
}

Changes in the code : ftpTask

protected override bool Execute(IIntegrationResult result)
{
result.BuildProgressInformation.SignalStartRunTask(!string.IsNullOrEmpty(Description) ? Description : GetDescription());

string remoteFolder = FtpFolderName;
FtpLib ftp = new FtpLib(this);

...
}

private string GetDescription()
{
if (Action == FtpAction.DownloadFolder)
{
return string.Concat("Downloading ", FtpFolderName, " to ", LocalFolderName);
}

return string.Concat("Uploading ", LocalFolderName, " to ", FtpFolderName);
}


Full Code of ftpLib

namespace ThoughtWorks.CruiseControl.Core.Util
{
public class FtpLib : IFtpLib
{
private EnterpriseDT.Net.Ftp.FTPConnection FtpServer;
private Tasks.TaskBase CallingTask;

public FtpLib()
{
this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;

}

public FtpLib(Tasks.TaskBase callingTask)
{
CallingTask = callingTask;

this.FtpServer = new EnterpriseDT.Net.Ftp.FTPConnection();

this.FtpServer.ReplyReceived += HandleMessages;

this.FtpServer.CommandSent += HandleMessages;

this.FtpServer.Downloaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Downloaded);

this.FtpServer.Uploaded += new EnterpriseDT.Net.Ftp.FTPFileTransferEventHandler(FtpServer_Uploaded);

}


public void LogIn(string serverName, string userName, string password, bool activeConnectionMode)
{

Log.Info("Connecting to {0} ...", serverName);

{
this.FtpServer.ServerAddress = serverName;
this.FtpServer.UserName = userName;
this.FtpServer.Password = password;
this.FtpServer.Connect();

if (activeConnectionMode)
{
Log.Debug("Active mode enabled");
this.FtpServer.ConnectMode = EnterpriseDT.Net.Ftp.FTPConnectMode.ACTIVE;
}
else
{
Log.Debug("Passive mode enabled");
this.FtpServer.ConnectMode = EnterpriseDT.Net.Ftp.FTPConnectMode.PASV;
}

this.FtpServer.TransferType = EnterpriseDT.Net.Ftp.FTPTransferType.BINARY;
}
}


public void DownloadFolder(string localFolder, string remoteFolder, bool recursive)
{

this.FtpServer.ChangeWorkingDirectory(remoteFolder);

EnterpriseDT.Net.Ftp.FTPFile[] FtpServerFileInfo = this.FtpServer.GetFileInfos();

string LocalTargetFolder = null;
string FtpTargetFolder = null;
bool DownloadFile = false;
string LocalFile = null;
System.IO.FileInfo fi = default(System.IO.FileInfo);

if (!System.IO.Directory.Exists(localFolder))
{
Log.Debug("creating {0}", localFolder);
System.IO.Directory.CreateDirectory(localFolder);
}

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in FtpServerFileInfo)
{
if (recursive)
{
if (CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name != "." && CurrentFileOrDirectory.Name != "..")
{

LocalTargetFolder = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);
FtpTargetFolder = string.Format("{0}/{1}", remoteFolder, CurrentFileOrDirectory.Name);

if (!System.IO.Directory.Exists(LocalTargetFolder))
{
Log.Debug("creating {0}", LocalTargetFolder);
System.IO.Directory.CreateDirectory(LocalTargetFolder);
}

DownloadFolder(LocalTargetFolder, FtpTargetFolder, recursive);

//set the ftp working folder back to the correct value
this.FtpServer.ChangeWorkingDirectory(remoteFolder);
}
}

if (!CurrentFileOrDirectory.Dir)
{
DownloadFile = false;

LocalFile = System.IO.Path.Combine(localFolder, CurrentFileOrDirectory.Name);


// check file existence
if (!System.IO.File.Exists(LocalFile))
{
DownloadFile = true;
}
else
{
//check file size
fi = new System.IO.FileInfo(LocalFile);
if (CurrentFileOrDirectory.Size != fi.Length)
{
DownloadFile = true;
System.IO.File.Delete(LocalFile);
}
else
{
//check modification time
if (CurrentFileOrDirectory.LastModified != fi.CreationTime)
{
DownloadFile = true;
System.IO.File.Delete(LocalFile);

}
}
}


if (DownloadFile)
{
Log.Debug("Downloading {0}", CurrentFileOrDirectory.Name);
this.FtpServer.DownloadFile(localFolder, CurrentFileOrDirectory.Name);

fi = new System.IO.FileInfo(LocalFile);
fi.CreationTime = CurrentFileOrDirectory.LastModified;
fi.LastAccessTime = CurrentFileOrDirectory.LastModified;
fi.LastWriteTime = CurrentFileOrDirectory.LastModified;
}

}

}
}


public void UploadFolder(string remoteFolder, string localFolder, bool recursive)
{

string[] LocalFiles = null;

LocalFiles = System.IO.Directory.GetFiles(localFolder, "*.*");
this.FtpServer.ChangeWorkingDirectory(remoteFolder);


// remove the local folder value, so we can work relative
for (int i = 0; i <= LocalFiles.Length - 1; i++)
{
LocalFiles[i] = LocalFiles[i].Remove(0, localFolder.Length + 1);
}


//upload files
//FtpServer.Exists throws an error, so we must do it ourselves
EnterpriseDT.Net.Ftp.FTPFile[] FtpServerFileInfo = this.FtpServer.GetFileInfos();


foreach (var LocalFile in LocalFiles)
{
if (!FileExistsAtFtp(FtpServerFileInfo, LocalFile))
{
this.FtpServer.UploadFile(System.IO.Path.Combine(localFolder, LocalFile), LocalFile);
}
else
{
if (FileIsDifferentAtFtp(FtpServerFileInfo, LocalFile, localFolder))
{
this.FtpServer.DeleteFile(LocalFile);
this.FtpServer.UploadFile(System.IO.Path.Combine(localFolder, LocalFile), LocalFile);
}

}
}


if (!recursive) return;

//upload folders
string[] Folders = null;

string LocalTargetFolder = null;
string FtpTargetFolder = null;


Folders = System.IO.Directory.GetDirectories(localFolder);

// remove the local folder value, so we can work relative
for (int i = 0; i <= Folders.Length - 1; i++)
{
Folders[i] = Folders[i].Remove(0, localFolder.Length + 1);
}


foreach (var Folder in Folders)
{
//explicit set the folder back, because of recursive calls
this.FtpServer.ChangeWorkingDirectory(remoteFolder);


if (!FolderExistsAtFtp(FtpServerFileInfo, Folder))
{
this.FtpServer.CreateDirectory(Folder);
}

LocalTargetFolder = System.IO.Path.Combine(localFolder, Folder);
FtpTargetFolder = string.Format("{0}/{1}", remoteFolder, Folder);

UploadFolder(FtpTargetFolder, LocalTargetFolder, recursive);
}
}

public void DisConnect()
{
this.FtpServer.Close();
}


public bool IsConnected()
{
return this.FtpServer.IsConnected;
}


public string CurrentWorkingFolder()
{
return this.FtpServer.ServerDirectory;
}

private bool FileExistsAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFileName)
{

bool Found = false;

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (!CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name.ToLower() == localFileName.ToLower())
{
Found = true;
}
}

return Found;
}

private bool FolderExistsAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFileName)
{

bool Found = false;
string updatedFolderName = null;

foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (CurrentFileOrDirectory.Name.EndsWith("/"))
{
updatedFolderName = CurrentFileOrDirectory.Name.Remove(CurrentFileOrDirectory.Name.Length - 1, 1);
}
else
{
updatedFolderName = CurrentFileOrDirectory.Name;
}

if (CurrentFileOrDirectory.Dir && updatedFolderName.ToLower() == localFileName.ToLower())
{
Found = true;
}
}

return Found;
}

private bool FileIsDifferentAtFtp(EnterpriseDT.Net.Ftp.FTPFile[] ftpServerFileInfo, string localFile, string localFolder)
{
bool isDifferent = false;
System.IO.FileInfo fi = default(System.IO.FileInfo);


foreach (EnterpriseDT.Net.Ftp.FTPFile CurrentFileOrDirectory in ftpServerFileInfo)
{
if (!CurrentFileOrDirectory.Dir && CurrentFileOrDirectory.Name.ToLower() == localFile.ToLower())
{
fi = new System.IO.FileInfo(System.IO.Path.Combine(localFolder, localFile));

if (fi.Length != CurrentFileOrDirectory.Size || fi.LastWriteTime != CurrentFileOrDirectory.LastModified)
{
isDifferent = true;
}
}
}

return isDifferent;
}

private void HandleMessages(object sender, EnterpriseDT.Net.Ftp.FTPMessageEventArgs e)
{
Log.Info(e.Message);
}

void FtpServer_Uploaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Uploaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Uploaded : ", e.RemoteDirectory, e.RemoteFile);

CallingTask.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(file));
}

void FtpServer_Downloaded(object sender, EnterpriseDT.Net.Ftp.FTPFileTransferEventArgs e)
{
string file;
if (!e.RemoteDirectory.EndsWith("/"))
file = string.Concat("Downloaded : ", e.RemoteDirectory, "/", e.RemoteFile);
else
file = string.Concat("Downloaded : ", e.RemoteDirectory, e.RemoteFile);

CallingTask.CurrentStatus.AddChild(new ThoughtWorks.CruiseControl.Remote.ItemStatus(file));
}

}
}


Full code of ftpTask

using System;
using System.Collections.Generic;
using System.Text;
using Exortech.NetReflector;
using ThoughtWorks.CruiseControl.Core.Util;

namespace ThoughtWorks.CruiseControl.Core.Tasks
{
[ReflectorType("ftp")]
class FtpTask : TaskBase
{
public enum FtpAction
{
UploadFolder,
DownloadFolder
}

[ReflectorProperty("serverName", Required = true)]
public string ServerName = string.Empty;

[ReflectorProperty("userName", Required = true)]
public string UserName = string.Empty;

[ReflectorProperty("password", Required = true)]
public string Password = string.Empty;

[ReflectorProperty("useActiveConnectionMode", Required = false)]
public bool UseActiveConnectionMode = true;

[ReflectorProperty("action", Required = false)]
public FtpAction Action = FtpAction.DownloadFolder;

[ReflectorProperty("ftpFolderName", Required = true)]
public string FtpFolderName = string.Empty;

[ReflectorProperty("localFolderName", Required = true)]
public string LocalFolderName = string.Empty;

[ReflectorProperty("recursiveCopy", Required = true)]
public bool RecursiveCopy = true;

protected override bool Execute(IIntegrationResult result)
{
result.BuildProgressInformation.SignalStartRunTask(!string.IsNullOrEmpty(Description) ? Description : GetDescription());

string remoteFolder = FtpFolderName;
FtpLib ftp = new FtpLib(this);


try
{
ftp.LogIn(ServerName, UserName, Password, UseActiveConnectionMode);

if (!FtpFolderName.StartsWith("/"))
{
remoteFolder = ftp.CurrentWorkingFolder() + "/" + FtpFolderName;
}

if (Action == FtpAction.UploadFolder)
{
Log.Debug("Uploading {0} to {1}, recursive : {2}", LocalFolderName, remoteFolder, RecursiveCopy);
ftp.UploadFolder(remoteFolder, LocalFolderName, RecursiveCopy);
}

if (Action == FtpAction.DownloadFolder)
{
Log.Debug("Downloading {0} to {1}, recursive : {2}", remoteFolder, LocalFolderName, RecursiveCopy);
ftp.DownloadFolder(LocalFolderName, remoteFolder, RecursiveCopy);
}

}
catch (Exception ex)
{
// try to disconnect in a proper way on getting an error
if (ftp != null)
{
try
{ // swallow exception on disconnect to keep the original error
if (ftp.IsConnected()) ftp.DisConnect();
}
catch { }
}
throw ex;
}

return true;
}

private string GetDescription()
{
if (Action == FtpAction.DownloadFolder)
{
return string.Concat("Downloading ", FtpFolderName, " to ", LocalFolderName);
}

return string.Concat("Uploading ", LocalFolderName, " to ", FtpFolderName);
}
}
}