-1

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();
        }
    }
        
}
Siegfried.V
  • 1,508
  • 1
  • 16
  • 34
  • The second time you're saving is the file being modified or is it like pressing twice the save button? Also, check if [this](https://stackoverflow.com/a/6001055/5762332) could be the issue. – Magnetron Jul 26 '22 at 17:16
  • @Magnetron no, I just click on "Save" Button several times (I wait the first raise is finished between them). I want it would work also in that case, cause the `Changed` evnt is not working if I'd edit the file, but the size is unchanged. I just found a way to solve it : I replaced `NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime` by `NotifyFilters.Size | NotifyFilters.LastWrite| NotifyFilters.LastAccess | NotifyFilters.CreationTime`. But I don't understand why this is the `LastAccess` that is taken in account? – Siegfried.V Jul 26 '22 at 17:22
  • @Magnetron forgot to say, even the first time I raise the event, I just pressed on save button. – Siegfried.V Jul 26 '22 at 17:24
  • Also looked at your link. I removed the `LastAccess` thing, because now it is raising 4 times for one time saving. That time, I waited 30 seconds before the second save, bt still the same issue. – Siegfried.V Jul 26 '22 at 17:29
  • 1
    Please provide [mre] - in particular code that does "save a file" is missing. (Most real programs don't ever overwrite the file during save despite your expectations). – Alexei Levenkov Jul 26 '22 at 17:35
  • @AlexeiLevenkov I save the file from an external software. And I also tried to Edit/Save, Edit/Save again (after 30s), and this is the same problem. Also saw a different syntax, what is the difference between `this.watcher.Created += new FileSystemEventHandler(OnFeedBackNesterCreated)` and `this.watcher.Created += OnFeedBackNesterCreated;`? (they give the same result). – Siegfried.V Jul 26 '22 at 17:37
  • @AlexeiLevenkov `don't ever overwrite the file during save despite your expectations` : You mean that could be because of external software? But as minimum, if the software edits the file, then saves it, I may have the `LastWrite` filter working? I could solve the problem adding the `LastAccess` filter. Then in my event, I just check the name of file, and its LastWriteTime, are not the same as the last time it was raised. It makes the job, but am not sure this is the best way. – Siegfried.V Jul 26 '22 at 18:06
  • 1
    Note that I have not looked closely at your post, this is very generic comment based on dozens nearly identical questions I've seen in the past. Most responsible programs save new file and than rename/delete... That's why it is useful to have real [mre] so it is clear what actually happens with files and what, if anything is missing from your code. (It would be 3-4 lines of code to mimic "save" for most cases - combination of WriteAllBytes/File.Rename/File.Delete should cover most cases how external programs behave) – Alexei Levenkov Jul 26 '22 at 18:23
  • It could be it just holds open the file until you close it: this is what Excel does. You wouldn't get an event when you save it again because the file is still open – Charlieface Jul 26 '22 at 19:23
  • @Charlieface in fact I think you're right, but I don't understand why the `LastWrite` filter isn't taken in account?. as written before, I could solve it adding a `LastAccess` filter. May I just delete the question? – Siegfried.V Jul 26 '22 at 19:28
  • Not sure if `LastWrite` changes when the file gets opened for the first write access, or when the first write actually happens, or when the file is closed, but it's unlikely to be more than one of those – Charlieface Jul 26 '22 at 20:45

1 Answers1

1

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();
}
IVSoftware
  • 5,732
  • 2
  • 12
  • 23
  • [Clone](https://github.com/IVSoftware/custom-file-system-watcher.git) this testbench. – IVSoftware Jul 26 '22 at 20:44
  • 1
    thanks, in fact, removing the filter it works fine.(it's not Excel file but anyway) I made your test, and all is working as expected, Even if I think somethng is wrong with how that program saves its files. The only thing I see in Console is `LastWriteTime`, I guess because it just overwrites the file each time, but each time, the event is launched 2, 3 or 4 times (it may be only 2). – Siegfried.V Jul 27 '22 at 03:53
  • yes, I agree that sounds right, and also that there's probably a `Delete` event when those overwrites happen. If you haven't hooked `this.watcher.Delete` try that and see whether it makes the log output more complete. – IVSoftware Jul 27 '22 at 06:09
  • either that or make sure that in `OnFeedbackNesterCreated` you're getting the first half of the log line with a Console.Write there. – IVSoftware Jul 27 '22 at 06:14
  • 1
    in fact I already have the `Delete`, but it's not appearing in log. the only thing I see is `Changed` event (forgot that precision in previous post). That means it is just edited I guess. Anyway removing filter worked, I just added in my `MachineWatcher` class 2 parameters (last file name, and last modification of the file), so even when it's launching 2, or 4 times, I will make the job only once. The only question is why it's launching so many times, but I guess this could be the external software edits xml file not at once. – Siegfried.V Jul 27 '22 at 06:41