
The DirectoryMirror Application
This application creates and maintains a mirror of a selected directory; it monitors IO activity in the specified directory and reacts to this in order to keep a copy of the contents (including subfolders) in another directory. This is an implementation of the System.IO.FileSystemWatcher class.
I've created a class called directoryMirror which is based on the FileSystemWatcher class. The handlers for the events (changed, created, deleted, renamed) of this class do all the work of maintaining a copy of the "source" directory in a "mirror" directory. I've added an event to the class which sends messages about IO activity and various exceptions. I've also added a property called mirrorDirectory that points to a string containing the path of the mirror folder, and a second property called sourceDirectory which is nothing more than the Path property of the FileSystemWatcher class.
There are eight different NotifyFilters, and as you will notice, our directoryMirror class uses three of them: the FileName, DirectoryName, and LastWrite NotifyFilters. This determines what changes to monitor. The Filter property lets you select what kind of files you want to watch. Just set the value to a file extension, for example, ".txt" for text files. Our directoryMirror class' Filter property is set to an empty value "" so we'll be monitoring all files and directories, as well as subdirectories because the IncludeSubdirectories property is set to true.
It's a well known bug that file paths returned by the event arguments System.IO.FileSystemEventArgs lose their original casing and are all in lower case. This is not too bad since the operating system makes no difference in the casing of file paths.
Believe it or not, I actually had a practical use for this little application when I created it! I might also say that I created the need because I had been itching to experiment with the FileSystemWatcher class for a long time. I made minor changes to my original application to make it more educational. If the uses of this application are somewhat limited, I think the code can be helpful to anybody looking for an introduction to the FileSystemWatcher class.
The directoryMirror class
The directoryMirror class is presented here, but the download contains all the code, including the form.
using System;
using System.IO;
namespace mirror
{
public class directoryMirror: System.IO.FileSystemWatcher
{
string mirDir = "";
public string sourceDirectory
{
get { return this.Path; }
set { this.Path = value; }
}
public string mirrorDirectory
{
get { return mirDir; }
set { mirDir = value; }
}
public delegate void infoMessageDelegate(string infoMessage);
public event infoMessageDelegate infoMessageEvent;
public directoryMirror(string srcDir, string mirDir)
{
sourceDirectory = srcDir;
mirrorDirectory = mirDir;
this.Filter = "";
this.NotifyFilter =
((System.IO.NotifyFilters)((System.IO.NotifyFilters.FileName |
System.IO.NotifyFilters.DirectoryName |
System.IO.NotifyFilters.LastWrite)));
this.IncludeSubdirectories = true;
this.EnableRaisingEvents = true;
this.Changed += new FileSystemEventHandler(fsw_onChanged);
this.Created += new FileSystemEventHandler(fsw_onCreated);
this.Deleted += new FileSystemEventHandler(fsw_onDeleted);
this.Renamed += new RenamedEventHandler(fsw_onRenamed);
}
private void fsw_onChanged(object sender, System.IO.FileSystemEventArgs e)
{
try
{
if(!System.IO.Directory.Exists(e.FullPath))
{
string destination =
e.FullPath.Replace(sourceDirectory, mirrorDirectory);
System.IO.File.Copy(e.FullPath, destination, true);
infoMessageEvent("\r\n" + e.ChangeType + " " + e.FullPath);
}
}
catch(DirectoryNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION " +
"(onChanged): Directory No Found , " + iox.Message);
}
catch(FileNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onChanged): File Not Found, " + iox.Message);
}
catch(IOException iox)
{
infoMessageEvent("\r\nEXCEPTION " +
"(onChanged): IO Error, " + iox.Message);
}
catch(Exception ex)
{
infoMessageEvent("\r\nEXCEPTION " +
"(onChanged): " + ex.Message);
}
}
private void fsw_onCreated(object sender,
System.IO.FileSystemEventArgs e)
{
try
{
if(System.IO.Directory.Exists(e.FullPath))
{
System.IO.Directory.CreateDirectory(
e.FullPath.Replace(sourceDirectory, mirrorDirectory));
infoMessageEvent("\r\n" +
e.ChangeType + ", " + e.FullPath);
}
else
{
string destination =
e.FullPath.Replace(sourceDirectory, mirrorDirectory);
System.IO.File.Copy(e.FullPath, destination, true);
infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
}
}
catch(DirectoryNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onCreated): DIRECTORY NOT FOUND , " + iox.Message);
}
catch(FileNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION (onCreated): " +
"FILE NOT FOUND, " + iox.FileName + ", " + iox.Message);
}
catch(IOException iox)
{
infoMessageEvent("\r\nEXCEPTION " +
"(onCreated): IO ERROR, " + iox.Message);
}
catch(Exception ex)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onCreated): " + ex.Message);
}
}
private void fsw_onDeleted(object sender,
System.IO.FileSystemEventArgs e)
{
try
{
if(System.IO.Directory.Exists(
e.FullPath.Replace(sourceDirectory,
mirrorDirectory)))
{
System.IO.Directory.Delete(
e.FullPath.Replace(sourceDirectory,
mirrorDirectory), true);
infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
}
else
{
string destination =
e.FullPath.Replace(sourceDirectory,
mirrorDirectory);
System.IO.File.Delete(destination);
infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
}
}
catch(DirectoryNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onDeleted): DIRECTORY NOT FOUND, "
+ iox.Message);
}
catch(FileNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION (onDeleted):" +
" FILE NOT FOUND, " + iox.FileName +
", " + iox.Message);
}
catch(IOException iox)
{
infoMessageEvent("\r\nEXCEPTION " +
"(onDeleted): IO ERROR, " + iox.Message);
}
catch(Exception ex)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onDeleted): " + ex.Message);
}
}
private void fsw_onRenamed(object sender, RenamedEventArgs e)
{
try
{
if(System.IO.Directory.Exists(e.FullPath))
{
if(System.IO.Directory.Exists(
e.OldFullPath.Replace(sourceDirectory,
mirrorDirectory)))
{
string oldFPath =
e.OldFullPath.Replace(sourceDirectory,
mirrorDirectory);
string newFPath = e.FullPath.Replace(sourceDirectory,
mirrorDirectory);
System.IO.Directory.Move(oldFPath, newFPath);
infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
}
}
else
{
string oldFPath =
e.OldFullPath.Replace(sourceDirectory, mirrorDirectory);
string newFPath =
e.FullPath.Replace(sourceDirectory, mirrorDirectory);
System.IO.File.Move(oldFPath, newFPath);
infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
}
}
catch(DirectoryNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onRenamed): DIRECTORY NOT FOUND, " + iox.Message);
}
catch(FileNotFoundException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onRenamed): FILE NOT FOUND, " + iox.FileName +
", " + iox.Message);
}
catch(IOException iox)
{
infoMessageEvent("\r\nEXCEPTION" +
" (onRenamed): IO ERROR, " + iox.Message);
}
catch(Exception ex)
{
infoMessageEvent("\r\nEXCEPTION (onRenamed): "
+ ex.Message);
}
}
}
}
Points of Interest
By watching the messages sent back by the directoryMirror object, you'll quickly realize that the sequence of events that are raised is often not what you would expect. Also, exceptions are sometimes raised but the application manages to successfully complete the "faulty" task like nothing happened! An IO error like this: "The process cannot access the file "D:\_mysys\mirror\22\file_test.txt" because it is being used by another process" seems to be a classic. This seems to happen when there are many actions to take care of; dropping a folder containing many files and subfolders in the source directory will often trigger this.