none
FileSystemWatcher and File.Copy

    Question

  •  

    I'm trying to do some actions after a file is copied to a target folder.

    My problem is that the FileSystemWatcher send one 'created' event and 2 'changed' event - how can I find out when the file copy process has been completed and it is safe to do some work with it?

     

     

    Tuesday, September 11, 2007 10:10 AM

Answers

  • Create a list to store file names, and create (but don't start) a Timer. Set the timer interval to a few seconds.

     

    When you receive an event from the FileSystemWatcher, check the list to see if the file name is in it. If it is not in the list, add it. Regardless of whether it was in the list or not, restart your timer.

     

    Your Timer's Tick event will finally fire after the events from the FileSystemWatcher have stopped. Then you know it is safe to start processing your list of files that you have accumulated. Be sure to stop your timer as soon as it ticks.

     

    There are a couple of challenges though. If someone copies a large number of files at once, you may not get events for each one of them. And if you set your timer interval too short you risk getting your timer Tick event before all the FileSystemWatcher events have finished. This is particularly of interest if you're monitoring a folder that is used as an FTP site, as FTP transfers can be quite slow sometimes.

     

    Tuesday, September 11, 2007 10:13 PM

All replies

  • Create a list to store file names, and create (but don't start) a Timer. Set the timer interval to a few seconds.

     

    When you receive an event from the FileSystemWatcher, check the list to see if the file name is in it. If it is not in the list, add it. Regardless of whether it was in the list or not, restart your timer.

     

    Your Timer's Tick event will finally fire after the events from the FileSystemWatcher have stopped. Then you know it is safe to start processing your list of files that you have accumulated. Be sure to stop your timer as soon as it ticks.

     

    There are a couple of challenges though. If someone copies a large number of files at once, you may not get events for each one of them. And if you set your timer interval too short you risk getting your timer Tick event before all the FileSystemWatcher events have finished. This is particularly of interest if you're monitoring a folder that is used as an FTP site, as FTP transfers can be quite slow sometimes.

     

    Tuesday, September 11, 2007 10:13 PM
  • This is a nice way to go, thanks.

     

    I've tried another 3 solutions and it seems that trying tp open a file for 'write' without getting an Exception is the safest way (altough I don't like the use of Exceptions as a coding method...).

    What do you think about that solution?

     

    Here are the solutions I've found, anyone can choose the best way for him.

    1. Use 'delayed' file watcher (same as your idea):

    http://blogs.msdn.com/ahamza/archive/2006/02/06/526222.aspx

    2. Create a new FileSystemWatcher for created files and listen to LastWrite events specificly for thoose files (I've found an example some where in the Code-Project).

    3. Try to open the file for write to make sure the File creation was completed.

     

    Here is the class for it:

     

     

    /// <summary>

    /// Extends the FileSystemWatcher to provide events for FileCreationStarted

    /// and FileCreationEnd.

    /// </summary>

    public class FileCreationWatcher : FileSystemWatcher

    {

    #region Events

    /// <summary>

    /// Occurs when a new file creation process starts

    /// </summary>

    public event FileSystemEventHandler FileCreationStarted;

    /// <summary>

    /// Raises the FileCreationStarted event

    /// </summary>

    /// <param name="e"></param>

    protected void OnFileCreationStarted(FileSystemEventArgs e)

    {

    if (FileCreationStarted != null)

    FileCreationStarted(this, e);

    }

    /// <summary>

    /// Occurs when a file creation process completes

    /// </summary>

    public event FileSystemEventHandler FileCreationEnded;

    /// <summary>

    /// Raises the FileCreationEnded event

    /// </summary>

    /// <param name="e"></param>

    protected void OnFileCreationEnded(FileSystemEventArgs e)

    {

    if (FileCreationEnded != null)

    FileCreationEnded(this, e);

    }

    #endregion

    #region Properties

    // to keep track of the subwatchers..

    private Dictionary<string, object> _pendingFiles = new Dictionary<string,object>();

    #endregion

    #region Ctor

    /// <summary>

    /// Intializes a new instance of the FileCreationWatcher class

    /// </summary>

    public FileCreationWatcher()

    {

    this.Created += new FileSystemEventHandler(watcher_Created);

    this.Changed += new FileSystemEventHandler(watcher_Changed);

    }

    #endregion

    #region Methods

    private void watcher_Created(object sender, FileSystemEventArgs e)

    {

    // We don't 'care' about folders:

    if (Directory.Exists(e.FullPath))

    return;

    // Fires the creation-start event:

    OnFileCreationStarted(e);

    // "Saves" the file that was created so for change event of it, we will test

    // for creation ended.

    if (_pendingFiles.ContainsKey(e.FullPath))

    {

    _pendingFiles.Remove(e.FullPath);

    }

    _pendingFiles.Add(e.FullPath, null);

    }

    /// <summary>

    /// The sub watch changed event indicate that the file creation has been completed.

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void watcher_Changed(object sender, FileSystemEventArgs e)

    {

    if (_pendingFiles.ContainsKey(e.FullPath))

    {

    // Check whether this file is released - if yes, the creation was completed:

    try

    {

    // Ignore is this is a directory:

    if (!Directory.Exists(e.FullPath))

    {

    FileStream stream = File.OpenWrite(e.FullPath);

    stream.Close();

    }

    _pendingFiles.Remove(e.FullPath);

    OnFileCreationEnded(new FileSystemEventArgs(

    WatcherChangeTypes.Created,

    System.IO.Path.GetDirectoryName(e.FullPath),

    System.IO.Path.GetFileName(e.FullPath)));

    }

    catch

    {

    // Eat the exception - the file is being copied.

    }

    }

    }

     

    #endregion

    }

    }

     

    Wednesday, September 12, 2007 9:57 AM
  • I've had the best luck with the delayed file watcher.

     

    One of the problems with the FSW is that it has a fixed size buffer to hold the names of files that are changing. If an operation copying a large number of files fills that buffer, then you simply won't receive every message for each and every file. So if you're waiting for a LastWrite event, and that event doesn't come because the buffer filled up, you end up failing to act on a file that you may have received another event about.

     

    I definitely recommend against a solution that uses an Exception to determine if it can write the file. That type of code is a nuisance to debug, and it also tends to be less efficient.

     

    If you do decide to code by exception make sure you handle unexpected exceptions as well as expected ones. For example, the code you posted has an empty catch block. It doesn't check the exception type, so it has no idea if the expected exception occurred (the file is locked because it is in use) or if something else happened (the disk failed, permissions are inadequate, memory is full etc.). So instead, trap specifically for the types of exceptions that you expect, but still catch everything else and at least log the exception somewhere.

    Wednesday, September 12, 2007 3:56 PM