From c666b18dc7a5e9b0416172b9e5e47932793b93a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 3 Aug 2024 21:26:09 -0300 Subject: [PATCH] Use ReadDirectoryChangesExW when available (should fix SpartanJ/efsw#178). --- src/efsw/FileWatcherWin32.cpp | 3 +- src/efsw/WatcherWin32.cpp | 159 ++++++++++++++++++++++++++++++---- src/efsw/WatcherWin32.hpp | 1 + 3 files changed, 145 insertions(+), 18 deletions(-) diff --git a/src/efsw/FileWatcherWin32.cpp b/src/efsw/FileWatcherWin32.cpp index 2e26eeb..edd0d5f 100644 --- a/src/efsw/FileWatcherWin32.cpp +++ b/src/efsw/FileWatcherWin32.cpp @@ -26,7 +26,8 @@ FileWatcherWin32::~FileWatcherWin32() { removeAllWatches(); - CloseHandle( mIOCP ); + if ( mIOCP ) + CloseHandle( mIOCP ); } WatchID FileWatcherWin32::addWatch( const std::string& directory, FileWatchListener* watcher, diff --git a/src/efsw/WatcherWin32.cpp b/src/efsw/WatcherWin32.cpp index ad206e3..d47d269 100644 --- a/src/efsw/WatcherWin32.cpp +++ b/src/efsw/WatcherWin32.cpp @@ -8,30 +8,95 @@ namespace efsw { -/// Unpacks events and passes them to a user defined callback. -void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped ) { +struct EFSW_FILE_NOTIFY_EXTENDED_INFORMATION_EX { + DWORD NextEntryOffset; + DWORD Action; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastModificationTime; + LARGE_INTEGER LastChangeTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER AllocatedLength; + LARGE_INTEGER FileSize; + DWORD FileAttributes; + DWORD ReparsePointTag; + LARGE_INTEGER FileId; + LARGE_INTEGER ParentFileId; + DWORD FileNameLength; + WCHAR FileName[1]; +}; + +typedef EFSW_FILE_NOTIFY_EXTENDED_INFORMATION_EX* EFSW_PFILE_NOTIFY_EXTENDED_INFORMATION_EX; + +typedef BOOL( WINAPI* EFSW_LPREADDIRECTORYCHANGESEXW )( HANDLE hDirectory, LPVOID lpBuffer, + DWORD nBufferLength, BOOL bWatchSubtree, + DWORD dwNotifyFilter, LPDWORD lpBytesReturned, + LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, + DWORD ReadDirectoryNotifyInformationClass ); + +static EFSW_LPREADDIRECTORYCHANGESEXW pReadDirectoryChangesExW = NULL; + +#define EFSW_ReadDirectoryNotifyExtendedInformation 2 + +static void initReadDirectoryChangesEx() { + static bool hasInit = false; + if ( !hasInit ) { + hasInit = true; + + HMODULE hModule = GetModuleHandleW( L"Kernel32.dll" ); + if ( !hModule ) + return; - if ( NULL == lpOverlapped ) { - return; + pReadDirectoryChangesExW = + (EFSW_LPREADDIRECTORYCHANGESEXW)GetProcAddress( hModule, "ReadDirectoryChangesExW" ); } +} +void WatchCallbackOld( WatcherWin32* pWatch ) { PFILE_NOTIFY_INFORMATION pNotify; - WatcherStructWin32* tWatch = (WatcherStructWin32*)lpOverlapped; - WatcherWin32* pWatch = tWatch->Watch; size_t offset = 0; + do { + bool skip = false; - if ( dwNumberOfBytesTransfered == 0 ) { - if ( nullptr != pWatch && !pWatch->StopNow ) { - RefreshWatch( tWatch ); - } else { - return; + pNotify = (PFILE_NOTIFY_INFORMATION)&pWatch->Buffer[offset]; + offset += pNotify->NextEntryOffset; + int count = + WideCharToMultiByte( CP_UTF8, 0, pNotify->FileName, + pNotify->FileNameLength / sizeof( WCHAR ), NULL, 0, NULL, NULL ); + if ( count == 0 ) + continue; + + std::string nfile( count, '\0' ); + + count = WideCharToMultiByte( CP_UTF8, 0, pNotify->FileName, + pNotify->FileNameLength / sizeof( WCHAR ), &nfile[0], count, + NULL, NULL ); + + if ( FILE_ACTION_MODIFIED == pNotify->Action ) { + FileInfo fifile( std::string( pWatch->DirName ) + nfile ); + + if ( pWatch->LastModifiedEvent.file.ModificationTime == fifile.ModificationTime && + pWatch->LastModifiedEvent.file.Size == fifile.Size && + pWatch->LastModifiedEvent.fileName == nfile ) { + skip = true; + } + + pWatch->LastModifiedEvent.fileName = nfile; + pWatch->LastModifiedEvent.file = fifile; } - } + if ( !skip ) { + pWatch->Watch->handleAction( pWatch, nfile, pNotify->Action ); + } + } while ( pNotify->NextEntryOffset != 0 ); +} + +void WatchCallbackEx( WatcherWin32* pWatch ) { + EFSW_PFILE_NOTIFY_EXTENDED_INFORMATION_EX pNotify; + size_t offset = 0; do { bool skip = false; - pNotify = (PFILE_NOTIFY_INFORMATION)&pWatch->Buffer[offset]; + pNotify = (EFSW_PFILE_NOTIFY_EXTENDED_INFORMATION_EX)&pWatch->Buffer[offset]; offset += pNotify->NextEntryOffset; int count = WideCharToMultiByte( CP_UTF8, 0, pNotify->FileName, @@ -56,12 +121,59 @@ void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOve pWatch->LastModifiedEvent.fileName = nfile; pWatch->LastModifiedEvent.file = fifile; + } else if ( FILE_ACTION_RENAMED_OLD_NAME == pNotify->Action ) { + pWatch->OldFiles.emplace_back( nfile, pNotify->FileId ); + skip = true; + } else if ( FILE_ACTION_RENAMED_NEW_NAME == pNotify->Action ) { + std::string oldFile; + LARGE_INTEGER oldFileId{}; + + for ( auto it = pWatch->OldFiles.begin(); it != pWatch->OldFiles.end(); ++it ) { + if ( it->second.QuadPart == pNotify->FileId.QuadPart ) { + oldFile = it->first; + oldFileId = it->second; + it = pWatch->OldFiles.erase( it ); + break; + } + } + + if ( oldFile.empty() ) { + pWatch->Watch->handleAction( pWatch, nfile, FILE_ACTION_ADDED ); + skip = true; + } else { + pWatch->Watch->handleAction( pWatch, oldFile, FILE_ACTION_RENAMED_OLD_NAME ); + } } if ( !skip ) { pWatch->Watch->handleAction( pWatch, nfile, pNotify->Action ); } } while ( pNotify->NextEntryOffset != 0 ); +} + +/// Unpacks events and passes them to a user defined callback. +void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped ) { + if ( NULL == lpOverlapped ) { + return; + } + + WatcherStructWin32* tWatch = (WatcherStructWin32*)lpOverlapped; + WatcherWin32* pWatch = tWatch->Watch; + + if ( dwNumberOfBytesTransfered == 0 ) { + if ( nullptr != pWatch && !pWatch->StopNow ) { + RefreshWatch( tWatch ); + } else { + return; + } + } + + // Fork watch depending on the Windows API supported + if ( pReadDirectoryChangesExW ) { + WatchCallbackEx( pWatch ); + } else { + WatchCallbackOld( pWatch ); + } if ( !pWatch->StopNow ) { RefreshWatch( tWatch ); @@ -70,9 +182,21 @@ void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOve /// Refreshes the directory monitoring. bool RefreshWatch( WatcherStructWin32* pWatch ) { - bool bRet = ReadDirectoryChangesW( pWatch->Watch->DirHandle, pWatch->Watch->Buffer.data(), - pWatch->Watch->Buffer.size(), pWatch->Watch->Recursive, - pWatch->Watch->NotifyFilter, NULL, &pWatch->Overlapped, NULL ) != 0; + initReadDirectoryChangesEx(); + + bool bRet = false; + + if ( pReadDirectoryChangesExW ) { + bRet = pReadDirectoryChangesExW( pWatch->Watch->DirHandle, pWatch->Watch->Buffer.data(), + (DWORD)pWatch->Watch->Buffer.size(), pWatch->Watch->Recursive, + pWatch->Watch->NotifyFilter, NULL, &pWatch->Overlapped, + NULL, EFSW_ReadDirectoryNotifyExtendedInformation ) != 0; + } else { + bRet = ReadDirectoryChangesW( pWatch->Watch->DirHandle, pWatch->Watch->Buffer.data(), + (DWORD)pWatch->Watch->Buffer.size(), pWatch->Watch->Recursive, + pWatch->Watch->NotifyFilter, NULL, &pWatch->Overlapped, + NULL ) != 0; + } if ( !bRet ) { std::string error = std::to_string( GetLastError() ); @@ -103,7 +227,8 @@ WatcherStructWin32* CreateWatch( LPCWSTR szDirectory, bool recursive, HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, ptrsize ) ); WatcherWin32* pWatch = new WatcherWin32(bufferSize); - tWatch->Watch = pWatch; + if (tWatch) + tWatch->Watch = pWatch; pWatch->DirHandle = CreateFileW( szDirectory, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, diff --git a/src/efsw/WatcherWin32.hpp b/src/efsw/WatcherWin32.hpp index ae050b7..1b622a5 100644 --- a/src/efsw/WatcherWin32.hpp +++ b/src/efsw/WatcherWin32.hpp @@ -65,6 +65,7 @@ class WatcherWin32 : public Watcher { FileWatcherImpl* Watch; char* DirName; sLastModifiedEvent LastModifiedEvent; + std::vector> OldFiles; }; } // namespace efsw