Asynchronous File I/O with NAnt

posted @ Thursday, May 25, 2006 11:43 AM

 

I recently needed to recursively iterate over a directory and change the content of files based on a search term. While there are certain NAnt tasks that have been defined to perform similar operations such as the foreach task used in conjunction with a loadfile and its associated filterchain and replacestring, I didn't see an easy way to recursively navigate the directory and perform my changes. A potential solution might have been nesting several foreach tasks and assigned the child directories to a property and subsequently call a different task to pull that directory out of the property, it didn't seem very natural or safe (could the property change before the second NAnt task requests it's value?). To my knowledge there is no locking mechanism in NAnt for properties. I'm interested if anyone has tackled this issue before directly within NAnt. Because custom NAnt tasks are easy to write, here is what I came up with. Initially I was performing the reading and writing of the file synchronously but the performance was horrible, a simple rewrite of that portion allowed me to perform asynchronous file I/O and my performance bottleneck disappeared. The follow code below allows me to define my task in NAnt as follows:

 

<recurse directory="${ProjectPath\src\${ProjectName}.Web" lookfor="SomeWord" replacewith="${ProjectName}" />

 

 

namespace developernotes.tasks
{
using System;
using
System.IO;
using
System.Text;
using
NAnt.Core.Util;
using
NAnt.Core.Attributes;
using
System.Text.RegularExpressions;

[TaskName("recurse")]
public class RecurseTask : NAnt.Core.Task
{
public RecurseTask(){}

[TaskAttribute(
"directory", Required=true)]
public string Directory
{
get{return directory;}
set{directory = value;}
}
private string directory = string.Empty;

[TaskAttribute("lookfor", Required=true)]
public string LookFor
{
get{return lookfor;}
set{lookfor = value;}
}
private string lookfor = string.Empty;

[TaskAttribute("replacewith", Required=true)]
public string ReplaceWith
{
get{return replacewith;}
set{replacewith = value;}
}
private string replacewith = string.Empty;

[TaskAttribute("pattern")]
public string Pattern
{
get{return pattern;}
set{pattern = value;}
}
private string pattern = "*.*";

private void
WalkDirectory(DirectoryInfo di)
{
if(di != null)
{
FileInfo[] fia
= di.GetFiles(this.Pattern);
if
(fia != null)
{
foreach(FileInfo fi in fia)
{
ReplaceFileContent(fi.FullName)
;
}
}

foreach(DirectoryInfo d in di.GetDirectories("*.*"))
{
WalkDirectory(d)
;
}
}
}

protected override void ExecuteTask()
{
DirectoryInfo di
= new DirectoryInfo(this.Directory);
WalkDirectory(di);
}

public class FileState
{
public byte[] Data;
public string
FileName;
public
FileStream FileStream;
}

private void ReplaceFileContent(string file)
{
FileStream fileStream
= new FileStream(file, FileMode.Open);
FileState state = new FileState();
state.FileStream = fileStream;
state.FileName = file;
state.Data = new byte[fileStream.Length];
fileStream.BeginRead(state.Data, 0,
(
int)fileStream.Length,
new AsyncCallback(ReadDone), state);
}

private void ReadDone(IAsyncResult result)
{
FileState state
= result.AsyncState as FileState;
Stream stream = state.FileStream;
int
bytesRead = stream.EndRead(result);
stream.Close();
if
(bytesRead != state.Data.Length)
{
throw new ApplicationException("Invalid read:"
+ state.FileName);
}
string content = ASCIIEncoding.ASCII.GetString(state.Data);
string
update = Regex.Replace(content, this.LookFor,
this.ReplaceWith, RegexOptions.IgnoreCase);
WriteContent(state.FileName, update);
}

private void WriteContent(string file, string content)
{
FileStream fileStream
= new FileStream(file,
FileMode.Truncate)
;
FileState state = new FileState();
state.FileStream = fileStream;
byte
[] data = ASCIIEncoding.ASCII.GetBytes(content);
fileStream.BeginWrite(data, 0, data.Length,
new AsyncCallback(WriteDone), state);
}

private void WriteDone(IAsyncResult result)
{
FileState state
= (FileState)result.AsyncState;
Stream stream = state.FileStream;
stream.EndWrite(result);
stream.Close();
}
}
}