DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 11.23 Watching the Filesystem for Specific Changes to One or More Files or Directories

Problem

You want to be notified when a file and/or directory is created, modified, or deleted. In addition, you might also need to be notified of any of these actions for a group of files and/or directories. This can aid in alerting your application as to when a file, such as a log file, grows to a certain size, after which it must be truncated.

Solution

To be notified when an action takes place in the filesystem, you need to employ the FileSystemWatcher class. The following method, TestWatcher, sets up a FileSystemWatcher object to watch the entire C:\ drive for any changes. The changes are limited to any file with the extension .txt. At the end of this method, the events are wired up for each one of the changes listed in the NotifyFilter property:

public void TestWatcher( )
{
    FileSystemWatcher fsw = new FileSystemWatcher( );
    fsw.Path = @"c:\";
    fsw.Filter = @"*.txt";
    fsw.IncludeSubdirectories = true;

    fsw.NotifyFilter = NotifyFilters.FileName    | 
        NotifyFilters.Attributes  | 
        NotifyFilters.LastAccess  | 
        NotifyFilters.LastWrite   | 
        NotifyFilters.Security    | 
        NotifyFilters.Size        |
        NotifyFilters.CreationTime|
        NotifyFilters.DirectoryName;

    fsw.Changed += new FileSystemEventHandler(OnChanged);
    fsw.Created += new FileSystemEventHandler(OnCreated);
    fsw.Deleted += new FileSystemEventHandler(OnDeleted);
    fsw.Renamed += new RenamedEventHandler(OnRenamed);
    fsw.Error += new ErrorEventHandler(OnError);

    fsw.EnableRaisingEvents = true;

    string file = @"c:\myfile.txt";
    string newfile = @"c:\mynewfile.txt";
    FileStream stream = File.Create(file);
    stream.Close( );
    File.Move(file,newfile);
    File.Delete(newfile);

    fsw.Dispose( );
}

The following code implements the event handlers to handle the events that are raised by the FileSystemWatcher object that was created and initialized in the TestWatcher method:

public void OnChanged(object source, FileSystemEventArgs e) 
{
    Console.WriteLine("File " + e.FullPath + " --> " + e.ChangeType.ToString( ));
}

public void OnDeleted(object source, FileSystemEventArgs e) 
{
    Console.WriteLine("File " + e.FullPath + " --> " + e.ChangeType.ToString( ));
}

public void OnCreated(object source, FileSystemEventArgs e) 
{
    Console.WriteLine("File " + e.FullPath + " --> " + e.ChangeType.ToString( ));
}

public void OnRenamed(object source, RenamedEventArgs e) 
{
    Console.WriteLine("File " + e.OldFullPath + " (renamed to)--> " + e.FullPath);
}

public void OnError(object source, ErrorEventArgs e) 
{
    Console.WriteLine("Error " + e.ToString( ));
}

Discussion

Watching for changes in the filesystem centers around the FileSystemWatcher class. This class can watch for filesystem changes on the local machine, a networked drive, and even a remote machine. The limitations of watching files on a remote machine are that the watching machine must be running versions of Windows starting from Windows NT 4.0 through 2000, XP, Server 2003, and Longhorn. The one caveat for Windows NT 4.0 is that a Windows NT 4.0 machine cannot watch another remote Windows NT 4.0 machine.

The FileSystemWatcher object cannot watch directories or files on a CD or DVD drive (including rewritables) in the current versions of the framework. This limitation might be revisited in a future version of the framework. This object does watch files regardless of whether their hidden property is set. To start watching a filesystem, we need to create an instance of the FileSystemWatcher class. After creating the FileSystemWatcher object, we can set its properties in order to focus our efforts in watching a filesystem. Table 11-10 examines the various properties that can be set on this object.

Table 11-10. Properties that can be set on the FileSystemWatcher object

Property name

Description

Path

A path to a directory in which to watch. The following are some examples of valid values for this property:

@"C:\temp"
@"C:\Program Files"
@"C:\Progra~1"
@"..\..\temp"
@"\\MyServer\temp"
@"."
@""

Note that if a directory is specified, changes to it, such as deleting it or changing its attributes, are not watched. Only changes within the temp directory are watched. Assigning an empty string forces the current directory to be watched.

IncludeSubdirectories

Set to true to monitor all subdirectories as well, or false to watch only the specified directory.

Filter

Specifies a specific subset of files to watch. The following are some examples of valid values for this property:

@"*.exe"   // Watch only .exe files
@"*"       // Watch all files
@""        // Watch all files
@"a*"      // Watch all files beginning with the letter 'a'
@"test.d??" // Watch all files with the name "test" and   
               having a three letter extension starting 
               with the letter 'd'

NotifyFilter

One or more NotifyFilters enumeration values. This enumeration is marked with the FlagsAttribute, so each enumeration value can be ORed together using the | operator. By default, this property is set to FileName, DirectoryName, and LastWrite. The members of the NotifyFilters enumeration are shown in Table 11-11.

EnableRaisingEvents

When this property is set to true, the FileSystemWatcher object starts watching the filesystem. To stop this object from watching the filesystem, set this property to false.

InternalBufferSize

The internal buffer size in bytes for this object. It is used to store information about the raised filesystem events. This buffer defaults in size to 8192 bytes. See additional information about this property next.

Table 11-11. NotifyFilters enumeration value definitions

Enumeration name

Description

Attributes

Watches for changes to a file or directory's attributes.

CreationTime

Watches for changes to a file or directory's creation time.

DirectoryName

Watches for changes to a directory's name.

FileName

Watches for changes to a file's name.

LastAccess

Watches for changes to a file or directory's last-accessed property.

LastWrite

Watches for changes to a file or directory's last-written-to property.

Security

Watches for changes to a file or directory's security settings.

Size

Watches for changes to a file or directory's size.

The NotifyFilters enumeration values in Table 11-11 determine which events the FileSystemWatcher object watches. For example, the OnChanged event can be raised when any of the following NotifyFilters enumeration values are passed to the NotifyFilter property:

NotifyFilters.Attributes
NotifyFilters.Size
NotifyFilters.LastAccess
NotifyFilters.LastWrite
NotifyFilters.Security
NotifyFilters.CreationTime

The OnRenamed event can be raised when any of the following NotifyFilters enumeration values are passed to the NotifyFilter property:

NotifyFilters.DirectoryName
NotifyFilters.FileName

The OnCreated and OnDeleted events can be raised when any of the following NotifyFilters enumeration values are passed to the NotifyFilter property:

NotifyFilters.DirectoryName
NotifyFilters.FileName

There are times when the FileSystemWatcher object cannot handle the number of raised events coming from the filesystem. In this case, the Error event is raised, informing you that the buffer has overflowed and specific events may have been lost. To reduce the likelihood of this problem, we can limit the number of raised events by minimizing the number of events watched for in the NotifyFilter property. To decrease the number of raised events further, you can set the IncludeSubdirectories property to false. You should note that adding a narrower filter to the Filter property to filter out more files does not affect the number of raised events this object receives. The Filter property is applied to the information already stored in the buffer, so this will not help if you are losing notifications due to the buffer overflows.

If the NotifyFilter and IncludeSubdirectories properties cannot be modified, consider increasing the InternalBufferSize property. To estimate what size to increase this buffer to, Microsoft provides the following tips:

  • A 4k byte buffer can keep track of changes for about 80 files in a directory.

  • Every event consumes 16 bytes of buffer space.

  • In addition to these 16 bytes, the filename is stored as Unicode characters.

  • If you are using Windows 2000, consider increasing/decreasing the buffer size by a multiple of 4k bytes. This is the same size as a default memory page.

  • If you do not know your operating system's page size, use the following code to increase the FileSystemWatcher's buffer size:

    FileSystemWatcher fsw = new FileSystemWatcher( );
    fsw.InternalBufferSize *= Multiplier;

    where Multiplier is an integer used to increase the size of the buffer. This makes the most efficient use of the buffer space.

If possible, increase the InternalBufferSize as a last resort since this is an expensive operation due to the buffer space being created in nonpaged memory. Nonpaged memory is memory available to the process that will always be in physical memory. It is a limited resource and is shared across all processes on the machine, so it is possible to affect the operation of other processes using this pool if too much is requested.

In many cases, a single action performed by the user produces many filesystem events. Creating a text file on the desktop yields the following changes:

File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\documents and settings\administrator\ntuser.dat --> Changed
File c:\documents and settings\administrator\ntuser.dat --> Changed
File c:\documents and settings\administrator\ntuser.dat --> Changed
File c:\documents and settings\administrator\ntuser.dat --> Changed
File c:\documents and settings\administrator\ntuser.dat.log --> Changed
File c:\winnt\system32\config\software.log --> Changed
File c:\winnt\system32\config\software.log --> Changed
File c:\winnt\system32\config\software.log --> Changed
File c:\winnt\system32\config\software --> Changed
File c:\winnt\system32\config\software --> Changed
File c:\winnt\system32\config\software --> Changed
File c:\winnt\system32\config\software --> Changed
File c:\winnt\system32\config\software.log --> Changed
File c:\documents and settings\administrator\desktop\newdoc.txt Created

Much of this work is simply registry access, but you notice at the end of this listing that the text file is finally created.

Another example of multiple filesystem events firing for a single action is when this newly created text file is opened by double-clicking on it. The following events are raised by this action:

File c:\winnt\system32\notepad.exe --> Changed
File c:\winnt\system32\notepad.exe --> Changed
File c:\documents and settings\administrator\recent\newdoc.txt.lnk --> Deleted
File c:\documents and settings\administrator\recent\newdoc.txt.lnk --> Created
File c:\documents and settings\administrator\recent\newdoc.txt.lnk --> Changed
File c:\winnt\system32\config\software.log --> Changed
File c:\winnt\system32\shell32.dll --> Changed
File c:\winnt\system32\shell32.dll --> Changed

Of course, your results may vary, especially if another application accesses the registry or another file while the text file is being opened. Even more events may be raised if a background process or service, such as a virus checker, is accessing the filesystem.

See Also

See the "FileSystemWatcher Class" and "NotifyFilters Enumeration" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section