Search code examples
c#filesystemwatcherfile-watcher

FileSystemWatcher raised only after first change


I am using FileSystemWatcher.

All is working fine. When I save my file, the Changed event is raised properly. But if I save file a second time, the Changed event isn't raised anymore.

Do I need to put additional code, so it would raise everytime I save a file?

Here is a part of my code, if this would be of any help :

I made a class MachineWatcher, because in my project, I need to create a list of different watcher types (but I don't think it changes anything to the question) :

public class MachineWatcher
{
    private Machine machine { get; set; }
    public Machine Machine { get { return this.machine; } set { this.machine = value; } }
    private string typeWatcher { get; set; } = "";
    public string TypeWatcher { get { return this.typeWatcher; } set { this.typeWatcher = value; } }
    FileSystemWatcher watcher { get; set; }
    public FileSystemWatcher Watcher { get { return this.watcher; } set { this.watcher = value; } }
    public MachineWatcher()
    {

    }
    public MachineWatcher(string type,string directoryStock,string fileFilter)
    {
        this.typeWatcher = type;
        this.watcher = new FileSystemWatcher();
        this.watcher.Path = Path.GetDirectoryName(directoryStock);
        this.watcher.Filter = fileFilter;
        this.watcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
        this.watcher.Changed += new FileSystemEventHandler(OnFeedBackNesterCreated);
        this.watcher.Created += new FileSystemEventHandler(OnFeedBackNesterCreated);
        
        this.watcher.EnableRaisingEvents = true;
    }
    private void OnFeedBackNesterCreated(object source, FileSystemEventArgs e)
    {
        string filePath = e.FullPath;
        LaunchingOrder newLo = this.readXmlFile(filePath);
        if(newLo!=null)
        {
            newLo.Fichier = filePath;
            newLo.AddOrUpdate();
        }
    }
        
}

Solution

  • My suggestion is to try adding this.watcher.Renamed to the events that are handled, and then take care not to filter out the Renamed event when you apply the this.watcher.NotifyFilter expression. This will at least compensate for the way Excel performs a Save operation called from its menu. Based on the test methodology shown below, the Changed event will never be fired. When I made a mock MachineWatcher and hooked up all the events including Renamed, the first events occur when a temporary file named ~$ExcelTestFile.xlsx is created when opening the file. Then, every time a Save operation happens, the following sequence takes place:

    1. Created event for temporary file (for example, on this pass it's arbitrarily named ECA83C30).
    2. This tmp file fires two Changed events (presumably as the modified version of the main file is copied to it).
    3. Renamed event for D03DF176.tmp (presumably from ECA83C30)
    4. Renamed event for the real target, a file named ExcelTestFile.xlxs (presumably a copy operation from D03DF176.tmp)
    5. Delete event for D03DF176.tmp

    But no Change events for the target Excel file even though it ends up with a new LastWriteTime. This was a shocker to me, but see if you can repro!

    test log


    Mock a minimal MachineWatcher

    public class MachineWatcher
    {
        public MachineWatcher(string type, string directoryStock, string fileFilter)
        {
            watcher = new FileSystemWatcher(directoryStock, fileFilter);
            watcher.Created += onModified;
            watcher.Changed += onModified;
            watcher.Renamed += onModified;
            watcher.Deleted += onModified; 
            // watcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
            watcher.EnableRaisingEvents = true;
        }
        FileSystemWatcher watcher { get; }
    
        private void onModified(object sender, FileSystemEventArgs e)
        {
            switch (e.ChangeType)
            {
                case WatcherChangeTypes.Created:
                    OnFeedBackNesterCreated(sender, e);
                    Console.WriteLine($" LastWriteTime: {new FileInfo(e.FullPath).LastWriteTime}");
                    break;
                case WatcherChangeTypes.Deleted:
                    Console.WriteLine($"Deleted: {e.Name}");
                    break;
                case WatcherChangeTypes.Changed:
                    var ext = Path.GetExtension(e.FullPath);
                    switch (ext)
                    {
                        case ".xlsx":
                            Console.Write($"Changed: {e.Name}");
                            break;
                        case ".txt":
                            try
                            {
                                Console.Write($"Changed: {e.Name} {File.ReadAllLines(e.FullPath).Last()}");
                            }
                            catch
                            {
                                Console.Write($"Changed: {e.Name} (in transition)");
                            }
                            break;
                        case "":
                            Console.Write($"Changed: {e.Name} (no extension)");
                            break;
                        default:
                            Console.Write($"The '{ext}' extension is not supported");
                            break;
                    }
                    Console.WriteLine($" LastWriteTime: {new FileInfo(e.FullPath).LastWriteTime}");
                    break;
                case WatcherChangeTypes.Renamed:
                    Console.Write($"Renamed: {e.Name}");
                    Console.WriteLine($" LastWriteTime: {new FileInfo(e.FullPath).LastWriteTime}");
                    break;
                default:
                    break;
            }
        }
    
        private void OnFeedBackNesterCreated(object source, FileSystemEventArgs e)
        {
            Console.Write($"Created: {e.Name}");
        }
    }
    

    Exercise the MachineWatcher using Console

    static void Main(string[] args)
    {
        const int SPACING = 500;
        string appData = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
            "custom_file_system_watcher");
    
        // Ensure that the root directory exists
        Directory.CreateDirectory(appData);
    
        // Make an instance of MachineWatcher
        var mw = new MachineWatcher(
            null, // In minimal reproducible sample this is unused
            appData, 
            "*.*");
    
        // Force Delete (if exists)
        var testFile = Path.Combine(appData, "testFile.txt");
        File.Delete(testFile);
        Thread.Sleep(SPACING);
    
        // Force Create + Change
        File.WriteAllText(
                testFile, 
                $"{DateTime.Now}{Environment.NewLine}");
        Thread.Sleep(SPACING);
    
        // Force N Changes
        var N = 5;
        for (int i = 1; i <= N; i++)
        {
            // Using Append because File.WriteAllText is two events not one.
            File.AppendAllText(testFile, $"Change #{i}{Environment.NewLine}");
            Thread.Sleep(SPACING);
        }
    
        // Force Rename
        var testFileRenamed = Path.Combine(appData, "testFile.Renamed.txt");
        File.Copy(testFile, testFileRenamed, overwrite: true);
        Thread.Sleep(SPACING);
    
        // Prove that if the Excel file LastWriteTime changes, we'll see it
        var excelFile = Path.Combine(appData, "ExcelTestFile.xlsx");
        var fileInfo = new FileInfo(excelFile);
        if(fileInfo.Exists)
        {
            Console.WriteLine();
            Console.WriteLine("Proves that if the Excel file LastWriteTime changes, we'll see it:");
            try
            {
                fileInfo.LastWriteTime = DateTime.Now;
            }
            catch
            {
                Console.WriteLine("CANNOT CHANGE TIMESTAMP: EXCEL FILE IS ALREADY OPEN");
            }
        }
        Thread.Sleep(SPACING);
    
        Console.WriteLine();
        Console.WriteLine("Waiting for Excel file changes...");
        Console.ReadKey();
    }