2

I have an application which works with XML file formats. The application can make changes to these XML files, but it also uses the System.IO.FileSystemWatcher to see if the XML files have changed outside of the program while the program was running. If the files have changed, then the application re-loads the XML files.

Of course, if the application saves the XML file, it causes Windows to detect that the file has changed, which kicks off the FileSystemWatcher events. I don't want the file to reload the XML files because of changes it has initiated, so whenever it saves a XML file, it keeps a count of the number of times to ignore when a particular file changes. Saving the XML file in the app increases the count. Reacting to a change on the file decreases the count if nonzero, or processes the change if the count is already 0.

The problem I'm encountering is that when I save the XML file, I do it as follows:

Stream fileStream = null;

if(System.IO.File.Exists(fileToSave)
{
   fileStream = System.IO.File.Open(fileName, FileMode.OpenOrCreate | FileMode.Truncate);
}
else
{
   fs = System.IO.File.Open(fileName, FileMode.OpenOrCreate);
}
...

Then I serialize my XML object to the stream using an XmlWriter then close the stream. All works fine except this causes my events in FileSystemWatcher to be raised twice. I've spent some time debugging and I've found that calling System.IO.File.Open with the Truncate option actually changes the file on disk. I've hit a breakpoint right after calling that and found the XML file is on disk with a size of 0 bytes. Therefore, it seems like this approach hits the disk twice, causing my FileSystemWatcher events to be raised twice.

How can I solve this problem?

As a workaround I've found that I can delete the file if it exists, then do a simple File.Open without the Truncate option and in my case that solves it...at least so far. I'm assuming this would kick of a Delete event on the FileSystemWatcher. Technically I don't want to delete the file, and if I were to add specific logic to when a file is deleted at a later time, this code would probably cause that to fire improperly.

Any other ways to change an existing file without causing 2 FileSystemWatcher events?

Update 1

It's been many years since I've asked this question and the file watch system has become a central part of an app I've been working with and maintaining ever since I originally asked the question over 5 years ago.

Unfortunately since that time I still haven't found a better solution to serliaizing XML to disk without causing 2 file watch changes.

For future readers here's the code I'm using:

if (System.IO.File.Exists(fileName))
{
  System.IO.File.Delete(fileName);
}

using( var fs = System.IO.File.Open(fileName, FileMode.OpenOrCreate))
{
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.Indent = true;
  using(var writer = XmlWriter.Create(fs, settings))
  {
    // for info on this, see
    // http://stackoverflow.com/questions/1127431/xmlserializer-giving-filenotfoundexception-at-constructor
    XmlSerializer serializer = XmlSerializer.FromTypes(new[] { type })[0];

    serializer.Serialize(writer, objectToSerialize);

  }
}
Victor Chelaru
  • 4,491
  • 3
  • 35
  • 49
  • have you tried fs.Length = 0; ? – Polity Dec 15 '11 at 06:05
  • You mean inside the FileWatchManager? I suppose I could do that, but what if the XML file is modified outside of the app and changed to be an empty (0 length) file? My app would miss that and still think the XML has contents when it in fact is empty. – Victor Chelaru Dec 15 '11 at 06:17
  • No, i mean when you save the XML file. Just open or create the xml file, set the position to 0, do your writing and finally set the length to position- or set the length to 0 in advance – Polity Dec 15 '11 at 06:48
  • There is no way to not cause an FSW event if you save the file. Unless you save it to a different directory. Filtering them should not be a problem, you *know* that you are saving the file. – Hans Passant Dec 15 '11 at 10:12
  • Length is a readonly (only has a getter) property, so you can't set the Length on it on Stream or FileStream. @Hans - Perhaps you misread the post. I am not trying to avoid getting an event, I'm trying to get *2* events when saving the file. – Victor Chelaru Dec 15 '11 at 16:10
  • I did. I read `to change an existing file without causing events', last sentence. You might want to edit that. – Hans Passant Dec 15 '11 at 16:14
  • Oops, typo on my previous comment. Meant to say: " I am not trying to avoid getting an event, I'm trying to AVOID getting 2 events when saving the file. " – Victor Chelaru Dec 15 '11 at 16:23
  • Why don't you just lock the file, and disable the watcher ? – Luiz Felipe Jul 04 '21 at 03:57
  • If the file is locked, you are sure it won't ever be modified by other programs that is not you, then you can just disable the watcher from firing events. See here https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.lock?view=net-5.0 – Luiz Felipe Jul 04 '21 at 04:00

2 Answers2

3

So what you have is a situation in which changes can occur in a directory, and you want your program to react to those changes. That in itself is no problem. The problem is that your program can't tell what process is making the changes. So if it changes things in that directory, then you end up in an infinite loop.

Any scheme you come up with to solve that problem is going to be fragile because it will suffer from race conditions. You can try to devise a scheme that lets you ignore the next N update notifications for a file that you changed, but at some point you'll discover that some other process made an update that you ignored.

The only reliable way for this to work is to have two directories. You have a submissions directory where client programs add files. Your program monitors that directory for additions. When a client adds a file to submissions, your program moves the file to the repository directory. Clients have read-only access to the repository directory. Only your program can add, modify, or delete files in the repository.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
1

I wouldn't implement any logic to avoid reading the Xml file in certain situation. That would make your programm much easier. Just deal with the situation that your program reads the changes it has done by itself and you will decrease the possibility to miss some changes due to a kind of "magic mechanism ignoring your own changes".

Fischermaen
  • 12,238
  • 2
  • 39
  • 56
  • I'd like to do that but my app deals with a variety of different files, and in many cases it's deserializing using libraries that I don't own. These files can be +100k lines long and when the user works in the tool the file gets saved essentially after every operation. – Victor Chelaru Dec 15 '11 at 16:12