It is not uncommon to need to watch a folder for changes to files.   It is possible to monitor for changes using FindFirstChangeNotification() and FindNextChangeNotification(). These functions don’t work like FindFirstFile() does, instead FindFirstChangeNotification() returns a HANDLE that can be used in WaitForSingleObject() and WaitForMultipleObjects(). In this post, I will develop a function that will monitor for changes to a list of folders. The function is FolderWatcherThread().
FolderWatcherThread() takes to parameters, an array of string pointers that point to strings containing folder names, and the number of pointers. It starts by looping through the array calling FindFirstChangeNotification() to get the handles to wait on. It is important to check the results, if the folder doesn’t exist the call will fail.
I found that the second parameter, which is a Boolean indicating if it should notify recursively, isn’t particularly useful. It does work for notifications when set to true, but I couldn’t get the data for the subfolders. A workaround for this would be to use FindFirstFile() and FindNextFile() to create an array of all folders in the tree.
DWORD FolderWatcherThread(TCHAR *Folders[], DWORD NumFolders)
{
                DWORD NumFolderWatches = 0;
                TCHAR **FolderNames;
                HANDLE *Events;
                DWORD dwWait;
                DWORD Index;
 
                Events = LocalAlloc(LMEM_FIXED, NumFolders * sizeof( HANDLE ) );
                FolderNames = LocalAlloc(LMEM_FIXED, NumFolders * sizeof( TCHAR * ) );
 
                for (Index = 0; Index < NumFolders; Index++)
                {
                                Events[NumFolderWatches] = FindFirstChangeNotification(Folders[Index], FALSE,
                                                FILE_NOTIFY_CHANGE_CEGETINFO |
                                                FILE_NOTIFY_CHANGE_FILE_NAME |
                                                FILE_NOTIFY_CHANGE_DIR_NAME |
                                                FILE_NOTIFY_CHANGE_SIZE |
                                                FILE_NOTIFY_CHANGE_LAST_WRITE|
                                                FILE_NOTIFY_CHANGE_ATTRIBUTES|
                                                FILE_NOTIFY_CHANGE_LAST_ACCESS|
                                                FILE_NOTIFY_CHANGE_CREATION|
                                                FILE_NOTIFY_CHANGE_SECURITY);
                                if (INVALID_HANDLE_VALUE != Events[NumFolderWatches])
                                {
                                                FolderNames[NumFolderWatches] = Folders[Index];
                                                NumFolderWatches++;
                                }
                }
 
                if (NumFolderWatches == 0)
                                return 0;
 
Now we have an array of HANDLES that we can wait on. When one of the HANDLES is signaled we can use CeGetFileNotificationInfo() to get information about what the change was that occurred. This is the function that doesn’t work for recursive notifications. It does return a data structure, but it doesn’t seem to write data to the structure.
CeGetFileNotificationInfo() is first called to find out how much data it has for us, then allocate a buffer for the data and call it again to get the data. There can be more than one piece of data, so loop through to handle all of the data.
                while (TRUE)
                {
                                dwWait = WaitForMultipleObjects(NumFolderWatches, Events, FALSE, INFINITE);
                                if (NumFolderWatches > dwWait)
                                {
                                                DWORD RequiredBufferSize = 0;
                                                LPBYTE NotificationData = NULL;
                                                FILE_NOTIFY_INFORMATION * pNotifyData;
                                                DWORD FNIOffset;
                                                if (!CeGetFileNotificationInfo(Events[dwWait],
                                                                                0, NULL, 0, NULL, &RequiredBufferSize))
                                                {
                                                                continue;
                                                }
                                                NotificationData = (LPBYTE) LocalAlloc(LMEM_FIXED, RequiredBufferSize);
                                               
                                                if(NotificationData == NULL)
                                                                break;
                                                               
                                                if(!CeGetFileNotificationInfo(Events[dwWait],
                                                                                0, NotificationData, RequiredBufferSize, NULL, NULL))
                                                {
                                                                LocalFree(NotificationData);
                                                                continue;
                                                }
 
Now output information about the change that occurred.
                                                RETAILMSG( 1, (TEXT("Folder: %s\n"), FolderNames[dwWait]));
                                                pNotifyData = NULL;
                                                FNIOffset = 0;
                                                do
                                                {
                                                                pNotifyData = (FILE_NOTIFY_INFORMATION *) (NotificationData+FNIOffset);
                                                                RETAILMSG( 1, (TEXT("NextEntryOffset %d\n"), pNotifyData->NextEntryOffset));
                                                                RETAILMSG( 1, (TEXT("Action %d\n"), pNotifyData->Action));
                                                                RETAILMSG( 1, (TEXT("FileNameLength %d\n"), pNotifyData->FileNameLength));
                                                                RETAILMSG( 1, (TEXT("FileName %s\n"), pNotifyData->FileName));
                               
                                                                switch (pNotifyData->Action)
                                                                {
                                                                                case FILE_ACTION_CHANGE_COMPLETED:
                                                                                                RETAILMSG( 1, (TEXT("File change complete %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                case FILE_ACTION_RENAMED_NEW_NAME:
                                                                                                RETAILMSG( 1, (TEXT("File name changed %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                case FILE_ACTION_REMOVED:
                                                                                                RETAILMSG( 1, (TEXT("File removed %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                case FILE_ACTION_RENAMED_OLD_NAME:
                                                                                                RETAILMSG( 1, (TEXT("File remaned, old name %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                case FILE_ACTION_ADDED:
                                                                                                RETAILMSG( 1, (TEXT("File Added %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                case FILE_ACTION_MODIFIED:
                                                                                                RETAILMSG( 1, (TEXT("File modified %s\n"), pNotifyData->FileName ));
                                                                                                break;
                                                                                default:
                                                                                                RETAILMSG( 1, (TEXT("File unkown Action %X\n"), pNotifyData->Action ));
                                                                                                break;
                                                                }
                                                                FNIOffset += pNotifyData->NextEntryOffset;
                                                } while(pNotifyData->NextEntryOffset);
                                                LocalFree(NotificationData);
                                                FindNextChangeNotification( Events[dwWait]);
                                }
                                else
                                {
                                                break;
                                }
                }
 
And finally clean up by calling FindCloseChangeNotification() for each HANDLE, free the Events and FolderNames arrays and return.
                for (Index = 0; Index < NumFolderWatches; Index++)
                {
                                FindCloseChangeNotification( Events[Index]);
                }
                LocalFree(Events);
                LocalFree( FolderNames );
               
                return 0;
}
 
FolderWatcherThread() can be called with a simple array of pointers to folder names like:
DWORD DiskWatcher()
{
                TCHAR *Folders[] = { TEXT("\\Hard Disk"), TEXT("\\Storage Card"), TEXT("\\Storage Card\\Data") };
 
                return FolderWatcherThread( Folders, sizeof(Folders)/sizeof(*Folders));
}
 
Or you can get creative with a dynamically created list of folders.
 Go to Serial Debug Menu Summary
Copyright © 2008 – Bruce Eitman
All Rights Reserved