Click here to Skip to main content
Licence 
First Posted 10 Feb 2005
Views 82,166
Bookmarked 85 times

Directory Mirror using the FileSystemWatcher class

By | 10 Feb 2005 | Article
An application that monitors a directory and maintains a copy of it.

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
{

    // This class is based on the FileSystemWatcher class
    public class directoryMirror: System.IO.FileSystemWatcher
    {
        // Path of the mirror directory
        string mirDir = "";

        // Source directory
        public string sourceDirectory
        {
            get { return this.Path; }
            set { this.Path = value; }
        }

        // Mirror Directory
        public string mirrorDirectory
        {
            get { return mirDir; }
            set { mirDir = value; }
        }

        // Delegate and event to send messages about IO activity
        // and various exceptions.
        public delegate void infoMessageDelegate(string infoMessage);
        public event infoMessageDelegate infoMessageEvent;

        public directoryMirror(string srcDir, string mirDir)
        {
            // Set up the path to the source directory
            sourceDirectory = srcDir;
            // Set up the path to the mirror directory
            mirrorDirectory = mirDir;
            
            // Set up the different properties
            // Monitor all files and directories
            this.Filter = "";
            // Listen for changes in the name of files and directories
            // and changes in date/time of the last modification.
            this.NotifyFilter = 
              ((System.IO.NotifyFilters)((System.IO.NotifyFilters.FileName | 
                System.IO.NotifyFilters.DirectoryName | 
                System.IO.NotifyFilters.LastWrite)));
            this.IncludeSubdirectories = true;
            this.EnableRaisingEvents = true;

            // Set up the handlers for the FileSystemWatcher events
            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)
        {
            // Because of the NotifyFilters we are using, this event can only
            // pertain to a file, not a directory
            try
            {
                if(!System.IO.Directory.Exists(e.FullPath))
                    // It's a file
                {
                    // Destination to copy the file to
                    string destination = 
                      e.FullPath.Replace(sourceDirectory, mirrorDirectory);
                    // Overwrite or copy the file in the mirror directory
                    System.IO.File.Copy(e.FullPath, destination, true);
                    // Send a message to report this activity
                    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))
                    // It's a directory
                {
                    // Create a matching directory in the mirror directory
                    System.IO.Directory.CreateDirectory(
                       e.FullPath.Replace(sourceDirectory, mirrorDirectory));
                    // Send a message to report this activity
                    infoMessageEvent("\r\n" + 
                                 e.ChangeType + ", " + e.FullPath);
                }
                else
                    // It's a file
                {
                    // Destination to copy the file to
                    string destination = 
                      e.FullPath.Replace(sourceDirectory, mirrorDirectory);
                    // Copy the file to the mirror directory
                    System.IO.File.Copy(e.FullPath, destination, true);
                    // Send a message to report this activity
                    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)))
                // It's a directory
                {
                    System.IO.Directory.Delete(
                       e.FullPath.Replace(sourceDirectory, 
                       mirrorDirectory), true);
                    // Send a message to report this activity
                    infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
                }
                else
                    // It's a file
                {
                    string destination = 
                      e.FullPath.Replace(sourceDirectory, 
                      mirrorDirectory);
                    System.IO.File.Delete(destination);
                    // Send a message to report this activity
                    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))
                    // It's a directory
                {
                    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);
                        // Send a message to report this activity
                        infoMessageEvent("\r\n" + e.ChangeType + ", " + e.FullPath);
                    }
                }
                else
                    // It's a file
                {
                    string oldFPath = 
                       e.OldFullPath.Replace(sourceDirectory, mirrorDirectory);
                    string newFPath = 
                       e.FullPath.Replace(sourceDirectory, mirrorDirectory);
                    System.IO.File.Move(oldFPath, newFPath);
                    // Send a message to report this activity
                    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Luc Archambault

Web Developer

Canada Canada

Member

I have been working with different web technologies (ASP, ASP.NET, ColdFusion, Perl/CGI) for the past 7 years. I live in Montreal, where I work for a little consultant group specializing in Microsoft technologies.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionVery nice work. PinmemberJipin20:23 9 Apr '12  
AnswerRe: Very nice work. PinmemberLuc Archambault10:31 10 Apr '12  
GeneralRe: Very nice work. PinmemberJipin7:15 11 Apr '12  
GeneralRe: Very nice work. PinmemberLuc Archambault12:38 20 Apr '12  
Questioncan u provide the document for this project PinmemberMember 82364840:51 20 Sep '11  
AnswerRe: can u provide the document for this project PinmemberLuc Archambault8:11 20 Sep '11  
QuestionHow to get closing event of other application in my running application PinmemberPatel Pranav20:32 23 Apr '10  
AnswerRe: How to get closing event of other application in my running application PinmemberLuc Archambault12:40 27 Apr '10  
GeneralThanks PinmemberEdward Ceballos4:59 4 Aug '08  
GeneralThank you Pinmembernirvansk81515:28 7 Apr '08  
GeneralWorks great, but we need a few tweaks. Pinmemberlmeyers17:04 15 Feb '08  
GeneralEvents firing twice Pinmemberp10003:45 6 Mar '07  
Generalnice work ~!great Pinmembernicesnow15:57 5 Feb '07  
Questionhow bout web application? Pinmember!ndera16:51 12 Dec '06  
AnswerRe: how bout web application? PinmemberLuc Archambault7:51 13 Dec '06  
GeneralMonitor a list of path Pinmemberpasdan22:41 22 May '06  
QuestionHow can we solve the IO exception? Pinmemberchallengerking17:06 19 Apr '06  
AnswerRe: How can we solve the IO exception? PinmemberLuc Archambault18:45 19 Apr '06  
Hi,
When I started playing around with the FileSystemWatcher I saw enormous potential and uses for this class but I quickly realized that it had some major flaws; this is why I built this little experimental application, to find out more about the flaws of the FileSystemWatcher class.
 
Here is my hypostasis about the flaws:
When you copy or drop large amounts of data somewhere it can take several seconds for the system to write the data to the disk. The system cannot send feedback about the status of the operation (completed, failed, queued, etc) while it is performing it or while it is queued, and during this time other requests to the file system are queued by the system. This is what I think is the source of the problem: The FileSystemWatcher has no patience and throws an exception if it doesn’t get an EMMEDIATE response from the file system. So any big operation that takes a bit of time will end up throwing an exception and often but not always, cause the operation to abort. This class seems to be completely ignorant of how the windows file system works.
 
This would explain why it usually works well when dealing with small amounts of data but cannot handle large amounts of data and/or large amounts of files.
 
On a first level it can handle small amounts of file and data. On a second level with somewhat larger amounts of data and files an exception is thrown but the operation is still carried out successfully. On a third level with very large amounts of data there is an exception and the operation fails to complete.
 
Personally I can’t see any workaround to this problem, but if anyone can I would love to hear about it.
 

Luc

GeneralRe: How can we solve the IO exception? Pinmemberchallengerking19:32 19 Apr '06  
GeneralRe: How can we solve the IO exception? Pinmemberchallengerking16:11 20 Apr '06  
AnswerRe: How can we solve the IO exception? PinmemberFayyaz Lodhi19:21 3 Jan '07  
AnswerRe: How can we solve the IO exception? Pinmembersides_dale20:23 14 Apr '07  
GeneralRe: How can we solve the IO exception? PinmemberFayyaz Lodhi20:28 15 Apr '07  
GeneralRe: How can we solve the IO exception? Pinmembersides_dale14:28 22 Apr '07  
GeneralRe: How can we solve the IO exception? PinmemberEpaxx11:28 15 Aug '07  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120529.1 | Last Updated 11 Feb 2005
Article Copyright 2005 by Luc Archambault
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid