private void ParseEventBufferAndNotifyForEach(byte[] buffer)
{
// Parse each event from the buffer and notify appropriate delegates
/******
Format for the buffer is the following C struct:
typedef struct _FILE_NOTIFY_INFORMATION {
DWORD NextEntryOffset;
DWORD Action;
DWORD FileNameLength;
WCHAR FileName[1];
} FILE_NOTIFY_INFORMATION;
NOTE1: FileNameLength is length in bytes.
NOTE2: The Filename is a Unicode string that's NOT NULL terminated.
NOTE3: A NextEntryOffset of zero means that it's the last entry
*******/
// Parse the file notify buffer:
int offset = 0;
int nextOffset, action, nameLength;
string oldName = null;
string name = null;
do
{
unsafe
{
fixed (byte* buffPtr = buffer)
{
// Get next offset:
nextOffset = *((int*)(buffPtr + offset));
// Get change flag:
action = *((int*)(buffPtr + offset + 4));
// Get filename length (in bytes):
nameLength = *((int*)(buffPtr + offset + 8));
name = new string((char*)(buffPtr + offset + 12), 0, nameLength / 2);
}
}
/* A slightly convoluted piece of code follows. Here's what's happening:
We wish to collapse the poorly done rename notifications from the
ReadDirectoryChangesW API into a nice rename event. So to do that,
it's assumed that a FILE_ACTION_RENAMED_OLD_NAME will be followed
immediately by a FILE_ACTION_RENAMED_NEW_NAME in the buffer, which is
all that the following code is doing.
On a FILE_ACTION_RENAMED_OLD_NAME, it asserts that no previous one existed
and saves its name. If there are no more events in the buffer, it'll
assert and fire a RenameEventArgs with the Name field null.
If a NEW_NAME action comes in with no previous OLD_NAME, we assert and fire
a rename event with the OldName field null.
If the OLD_NAME and NEW_NAME actions are indeed there one after the other,
we'll fire the RenamedEventArgs normally and clear oldName.
If the OLD_NAME is followed by another action, we assert and then fire the
rename event with the Name field null and then fire the next action.
In case it's not a OLD_NAME or NEW_NAME action, we just fire the event normally.
(Phew!)
*/
// If the action is RENAMED_FROM, save the name of the file
if (action == Interop.Kernel32.FileOperations.FILE_ACTION_RENAMED_OLD_NAME)
{
Debug.Assert(oldName == null, "Two FILE_ACTION_RENAMED_OLD_NAME in a row! [" + oldName + "], [ " + name + "]");
oldName = name;
}
else if (action == Interop.Kernel32.FileOperations.FILE_ACTION_RENAMED_NEW_NAME)
{
if (oldName != null)
{
NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
oldName = null;
}
else
{
Debug.Fail("FILE_ACTION_RENAMED_NEW_NAME with no old name! [ " + name + "]");
NotifyRenameEventArgs(WatcherChangeTypes.Renamed, name, oldName);
oldName = null;
}
}
else
{
if (oldName != null)
{
Debug.Fail("Previous FILE_ACTION_RENAMED_OLD_NAME with no new name! [" + oldName + "]");
NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
oldName = null;
}
switch (action)
{
case Interop.Kernel32.FileOperations.FILE_ACTION_ADDED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Created, name);
break;
case Interop.Kernel32.FileOperations.FILE_ACTION_REMOVED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, name);
break;
case Interop.Kernel32.FileOperations.FILE_ACTION_MODIFIED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Changed, name);
break;
default:
Debug.Fail("Unknown FileSystemEvent action type! Value: " + action);
break;
}
}
offset += nextOffset;
} while (nextOffset != 0);
if (oldName != null)
{
Debug.Fail("FILE_ACTION_RENAMED_OLD_NAME with no new name! [" + oldName + "]");
NotifyRenameEventArgs(WatcherChangeTypes.Renamed, null, oldName);
oldName = null;
}
}