5

This is more a question about what's the best practice in implementing this.

I have a FileSystemWatcher which should inform me about User's changes on files and folders. Also subdirs are watched. In the same directory my program does also sometimes changes. I don't want the FileSystemWatcher to detect events on these program changes.

My first implementation was a list where I can add expected events. When I get a file system event I check the list and ignore it if it's there. This doesn't sound very robust but it seemed to work.

Now I detected the real problem:
D: is watched by the FileSystemWatcher.
I have two folders like that: D:\folder1\folder2
Now I want to delete folder1 (with folder2 in it) with my application. So I put D:\folder1 in my delete list. Then I call something like Directory.Delete(@"D:\folder1", true) . Now I notice that folder1 can't be deleted (why ever) over an exception. I remove the delete entry from my list but folder2 was already deleted and I get his FileSystemEvent. So I get a FileSystem event for D:\folder1\folder2. My program thinks now the user has deleted this folder and is doing the wrong things.

I had some ideas now:

1.) recursively delete the folder by deleting every file and every folder by it's own. With this I get for every subfolder and file an own list entry. I already implemented it but it is very very very slow.

2.) Maybe there is a better way to have clever filters in the FileSystemWatcher to make my list obsolete?

3.) Maybe it is possible to only delete a directory tree if it's possible to delete everything. So if it fails I still have everything and if not everything is deleted. This seems to be the most elegant solution for me but no idea if this is even possible?

4.) Is it possible to exclusively lock all files and folders by my software? If this this went ok it should be possible delete everything with one delete command or somehow like this?

I'm also open for other additional solutions.

Edit 1 to make it more clear:

I only want to "see" user actions on a folder. If I manipulate things from my program in here I don't want to see this events.

With my implementation I get events for subfolders if a folder is locked and can't be deleted.

It's not so easy to explain in english because I'm no english native speaker ;).

Edit 2:

5.) Maybe it is possible to filter in FileSystemWatcher all events from a defined process?

fpdragon
  • 1,867
  • 4
  • 25
  • 36
  • Where's the part in your question about predicting things? – Cody Gray - on strike Feb 11 '11 at 09:16
  • the list I was writing about is a prediction because it holds the expected events which should come from the FileSystemWatcher later. It works so: 1.) remember in list 2.) manipulate files/folder 3.) ignore events with list – fpdragon Feb 11 '11 at 09:23
  • Seriously, I am not getting this. What event do you expect? What events are you getting instead? What exceptions are you getting? Please update your questions with these points. – Edwin de Koning Feb 11 '11 at 09:27
  • With respect to your idea #3 above - if you are using NTFS you may be able to use file system transactions. http://msdn.microsoft.com/en-us/magazine/cc163388.aspx - however I do not know how they interact with file system watchers within your own process. – Robert Horvick Feb 16 '11 at 11:32

3 Answers3

2

I've done exactly this sort of thing recently; the trick is to have your 'list' recognise that where there's a folder name in the list, also discard any events for anything within that folder if it's expecting a delete event, and only remove it from your prediction list if it's the folder itself.

I should warn you though, you're likely to encounter problems with the FileSystemWatchers buffer becoming full if too many events happen in rapid succession; if this does, it'll fire an Error event, and fail to notify you of a whole bunch of events. If your prediction list removes items as it receives the event, you run the risk of ignoring future events just because the event you intended to ignore was never received due to a buffer overflow. It may also get very large, as items are never being removed from the list.

I've yet to find a reliably way of doing this, although you can set the size of the FileSystemWatchers buffer to maximum to mitigate it to a degree.

EDIT: Also very important: The events that come back do so on a different thread (unless your handler is on an object that implements ISynchronizeInvoke, such as a Control or Form, and you set the SynchronizingObject to your Control/Form. This means you need to be very careful how you maintain your list, taking into account potential race conditions. I'm still struggling with this one; my code flushes the prediction list on receiving an error event, but by the time it handles the event, other change events since the error have already been fired and handled, and it flushes things it shouldn't.

Flynn1179
  • 11,925
  • 6
  • 38
  • 74
  • very very interesting... thank you. do you know how large this FileSystemWatcher buffer is? I also tried to implement a expected life time for list entrys to clean this up a little bit but the question is how fast comes the FileSystem events (worst case)? it would be nice if you could keep me up to date and tell me your experiences. 1+ – fpdragon Feb 11 '11 at 10:05
  • if I'd have this life time implementation and it would be robust I could use regex for folder deletes and just let the list entry be out of life time. – fpdragon Feb 11 '11 at 10:07
  • 1
    `FileSystemWatcher` has a property `InternalBufferSize`. The default is 8k, but it can go up to 64k. http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.internalbuffersize.aspx – Flynn1179 Feb 11 '11 at 10:11
  • Or to say it other: How long takes it (worst case) to delete a folder and get a file system event... – fpdragon Feb 11 '11 at 11:01
  • Worst case? A LONG time depending on where that folder is, and how much is in it. A folder with 1000000 files and subfolders on a server on the other side of the planet with a dial-up connection could take days. – Flynn1179 Feb 11 '11 at 11:06
1

Regarding the buffer overflow. The best way to solve it is to do the work in response to an event on another thread. I do it like this

// Queue of changed paths.
private readonly Queue<string> mEventQueue = new Queue<string>();

// add this as handler for filesystemwatcher events
public void FileSystemEvent(object source, FileSystemEventArgs e) {
    lock (mEventQueue) {
        if (!mEventQueue.Contains(e.FullPath)) {
            mEventQueue.Enqueue(e.FullPath);
            Monitor.Pulse(mEventQueue);
        }
    }
}

// start this on another thread
public void WatchLoop() {
    string path;
    while (true) {
        lock (mEventQueue) {
            while (mEventQueue.Count == 0)
                Monitor.Wait(mEventQueue);
            path = mEventQueue.Dequeue();
            if (path == null)
               break;
        }
        // do whatever you want to do
     }
}

That way I will never miss an event

adrianm
  • 14,468
  • 5
  • 55
  • 102
  • thank you... I'm already having something similar. I was just thinking of a situation where the pc is very overloaded. I'm not sure if it's possible but the default FileSystemWatcher can hold about 500 entrys. This is very less even if you have a worker thread and just pulse the events in from the FileSystemWatcher's thread. So I don't think it would be a fault to set the buffer to the maximum. – fpdragon Feb 11 '11 at 11:34
0

Ok here is my solution of the problem:

Just for delete command:

I implemented 2 lists, one for file deletes and one for folder deletes.

file list entrys have no timeout. If I delete a file I create a list entry and if I get the expected delete event I remove the entry as before.

folder list entrys have no timeout after create. I can command them manually to time out after a second by a special method. If I delete a folder I add a deleteFolder list entry. Every delete event from this folder or file or subfolder or subfolderfile is ignored because of this delete folder entry. After the delete is finished I arm the timeout for the deleteFolder entry. If the delete throws an exception I do the same. So I expect to get all events after a second. So I'm ignoring all events if the delete command works or not. After this the deleteFolder list entry gets deleted.

Limitations: 1. all delete events have to come in one second after the delete happened. 2. it is not allowed to delete a folder like this:
delete folder (completed)
create folder again
wait less then 1 second
delete folder again (timeout has not finished of the delete folder list entry)

Hope this helps somebody even it is very complicated ^^

fpdragon
  • 1,867
  • 4
  • 25
  • 36