Skip to content

Commit

Permalink
Use ReadDirectoryChangesExW when available (should fix #178).
Browse files Browse the repository at this point in the history
  • Loading branch information
SpartanJ committed Aug 4, 2024
1 parent 1e981c9 commit c666b18
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 18 deletions.
3 changes: 2 additions & 1 deletion src/efsw/FileWatcherWin32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ FileWatcherWin32::~FileWatcherWin32() {

removeAllWatches();

CloseHandle( mIOCP );
if ( mIOCP )
CloseHandle( mIOCP );
}

WatchID FileWatcherWin32::addWatch( const std::string& directory, FileWatchListener* watcher,
Expand Down
159 changes: 142 additions & 17 deletions src/efsw/WatcherWin32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 );
Expand All @@ -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() );
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/efsw/WatcherWin32.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class WatcherWin32 : public Watcher {
FileWatcherImpl* Watch;
char* DirName;
sLastModifiedEvent LastModifiedEvent;
std::vector<std::pair<std::string, LARGE_INTEGER>> OldFiles;
};

} // namespace efsw
Expand Down

0 comments on commit c666b18

Please sign in to comment.