Wednesday, May 13, 2009

FileSystemWatcher Done Right.

For some reason the FileSystemWatcher (aka ChangeNotify) in Windows is just not right. If you search for this in on the internet, you'll find tons of issues with people attempting to use it and running into problems. A couple of the top issues:

* The FileSystemWatcher calls multiple times on a single change.
* It does not call when certain programs change the file.
* If my process changes the file, the FileSystemWatcher calls me. How do I know I was the only one who changed it (and someone else didn't slip in after I called Close()).

I built the following class to encapsulate all of these issues, and to do file watching correctly.

The only things you need to know:
* Create a FileWatcher on a file on the disk. If it changes by an external process, you will get called once per change.
* If this process needs to write the file, call the FileWatcher's CloseFileInThisProcess() method when ready to close the file. This will ensure you don't get called when you change the file.

This is written for WPF. The only reason that matters is because of the threading issues. It can easily be tweaked for WinForms.

There is an example usage following the code.



///
/// This filewatcher works around the 'issues'
/// with Window's file watcher .NET FileSystemWatcher
/// or Win32::ChangeNotify). This code is written for
/// WPF, but can be tweaked to work with Winforms as well.
///
/// It solves these two main problems:
/// * The FileSystemWatcher calls multiple times on
/// a single change.
/// * If my process changes the file, the
/// FileSystemWatcher calls me.
///
/// It solves the former by using a 100 ms timer to
/// collapse multiple calls into a single call. It
/// solves the latter by storing the file size and
/// time when this process writes a file, and
/// comparing this to the values when notified by
/// FileSystemWatcher.
///
/// Usage is straightforward, except that you must
/// call CloseFileInThisProcess when you are closing
/// the file that this watcher is watching. It will
/// carefully close the file in such a way that it
/// can later tell if the change was by this process
/// or another.
///
///

public class FileWatcher : FileSystemWatcher
{
public delegate void FileChangedHandler(string filepath);
public FileWatcher(string filepath, FileChangedHandler handler) :
base(System.IO.Path.GetDirectoryName(filepath),
System.IO.Path.GetFileName(filepath))
{
FilePath = filepath;
Handler = handler;
NotifyFilter =
NotifyFilters.FileName |
NotifyFilters.Attributes |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Security |
NotifyFilters.Size;
Changed += new FileSystemEventHandler(delegate(object sender, FileSystemEventArgs e)
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new VoidDelegate(this.FileChanged));
});
UpdateFileInfo();
Timer = new Timer(100);
Timer.AutoReset = false;
Timer.Elapsed += new ElapsedEventHandler(delegate(object sender, ElapsedEventArgs e)
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(
new VoidDelegate(this.TimerElapsed));
});
EnableRaisingEvents = true;
}

///
/// This only works with StreamWriters. You may need
/// to make your own version for whatever writer you are
/// using.
///
/// How this works: It stores the file size before
/// calling close. Then, after close it grabs the write
/// time. There is a minute corner case here: If
/// another process gets in and writes the file after
/// the close, but before the GetLastWriteTime call, and the
/// file is the same length, we will not detect the change.
/// If that is critical, one could do a checksum on the file....
///

public void CloseFileInThisProcess(StreamWriter writer)
{
writer.Flush();
LastFileLength = writer.BaseStream.Length;
writer.Close();
LastWriteTime = File.GetLastWriteTime(FilePath);
}


void UpdateFileInfo()
{
var fileInfo = new FileInfo(FilePath);
LastWriteTime = fileInfo.LastWriteTime;
LastFileLength = fileInfo.Length;
}

public delegate void VoidDelegate();

void FileChanged()
{
var fileInfo = new FileInfo(FilePath);
if (LastWriteTime != fileInfo.LastWriteTime || LastFileLength != fileInfo.Length)
if (!Timer.Enabled)
Timer.Start();
}

void TimerElapsed()
{
UpdateFileInfo();
Handler(FilePath);
}

string FilePath;
FileChangedHandler Handler;
DateTime LastWriteTime;
long LastFileLength;
Timer Timer;
}




Here is an example usage:



public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();

// Set up the file watcher given a filename and a handler.
Watcher = new FileWatcher(@"c:\temp\test.txt", FileChanged);
}
FileWatcher Watcher;
void FileChanged(string filename)
{
// For now just display a message when the file changed.
MessageBox.Show("Got here");
}

// Here is an example of writing the file.
protected override void OnMouseDown(MouseButtonEventArgs e)
{
var writer = new StreamWriter(@"c:\temp\test.txt");
try
{
writer.Write("Hello, world, number " + I++);
}
finally
{
Watcher.CloseFileInThisProcess(writer);
}
}
int I = 0;
}

Monday, March 9, 2009

Natural Language .NET Date Parser.

As part of the Smpl application, we (Precision Software) built a natural language date and time parser for .NET. This is not just another DateTime.Parse("...") class. It is built to handle the majority of ways that humans communicate dates, times, date spans, time spans, etc. in English. And, by the way, English is a much simpler and compact way to communicate complex time specifications than form based UI. If you don't believe me, take a look at Outlook's Recurring date form. Wow. All this to describe what English describes in a few words (ex: "The 9th of every month").

Have you needed a date parser that can handle recurring (repeating) dates?
How about one that can parse something like, "Every Thursday, 2 PM".
Need to parse dates strings that occur once per month, like "The first Tuesday of every month". If you searched the web, you probably found the Perl Date::Manip module. If you are writing a Unix script, that is great, but for those of us C#, Visual Basic, or other .NET languages, calling Perl to parse dates is not a practical option.


This parser treats all dates and times as spans, with optional recurrence. Thus, "Tuesday, 10AM" is treated as a span from 10-11AM. This creates a really convenient API: date.Overlaps(otherDate) will check if two dates overlap. This will return true, for example, if date = "Jan 5", and otherDate = "Tuesday, 10AM", assuming this Tuesday is January 5th.

The optional recurrence allows enter dates in the form of "Every Monday, 8PM", or "Every January 5", "Every January", or even "Everyday, 5-9PM". To iterate these recurrences, one simply asks for date.Next("today"), for example, which will return the next occurrences of a given date after today. Combining this with Overlaps makes a very tight, easy to read program.

To deal with ambiguous situations, the parser has a set of flags to control preferences. For example, "Tuesday from 3-5 PM" could mean *this* Tuesday, or it could mean *every* Tuesday. By default, this causes a parse error, but by preferring recurring or not, the error can be eliminated. In such a case, one can still, for example, explicitly state "Every Tuesday, 3-5PM".

The smart date parser is a .NET module, and therefore can be used with any .NET language, including ASP and other web based languages.

You can see it in action here.

If this module would be of use to you, let us know by e-mail: contact@precisionsoftware.us. We will respond within 1-2 business days of your e-mail. For faster response, call us at 208-882-5980.

Tuesday, January 13, 2009

Specification Authoring Tool

Does anyone else out there find it annoying that there is not a good specification authoring application on the market? I'm looking for an application that allows one to author requirements specifications for anything from software to airplanes.

One would argue that Microsoft Word is the universal document authoring tool. Why would you want a different tool? For those that do development primarily on computers (software developers, electrical engineers, etc.), Word doesn't work very well. Some of its drawbacks:
* It has a binary file format. This doesn't play well with version control. The new XML file format is better, but its not text. It won't merge. Their diff support is also poor.
* Its styles are horrible. If you try to use them the way they are intended to be used, you get very frustrated.
* It has no document consistency checking. Its all Greek to the computer.
* It doesn't help you author specifications using your format.
* Anyone that has used Word with a file locking version control system knows that Word is broke. Word nicely doesn't inform you that the file you are editing is locked until AFTER you've made changes--ARGH!!!
* And finally, you cannot programmatically get data out of a word document!! The most important information about your product is stored in a format that allows you to do two things: Read it (including print, e-mail, etc), and Text search it (thanks to Google, who had to come along and make it practical). Why can't I generate manuals by pulling out features from specifications? Why can't I generate or configure parts of the product directly from the specifications?

Is it too much to ask for a tool that meets the above? The tool should also support:
* Outlines
* Tables
* Styles--done right
* Embedded images
* Ability to publish directly to HTML/PDF

All while storing the source in a plain text, human readable (not geek readable, like XML) file. I think most of us would drop Word in a heartbeat if such a tool came along.