diff --git a/.gitignore b/.gitignore index 5d0f6a9..abba35d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ CMakeFiles/ *.dylib *.lib /.cache +/efsw-test +/efsw-test-stdc diff --git a/CMakeLists.txt b/CMakeLists.txt index e37438a..89c61b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,10 +98,6 @@ if(APPLE) src/efsw/WatcherFSEvents.cpp src/efsw/WatcherKqueue.cpp ) - - if(NOT CMAKE_SYSTEM_VERSION GREATER 9) - target_compile_definitions(efsw PRIVATE EFSW_FSEVENTS_NOT_SUPPORTED) - endif() elseif(WIN32) list(APPEND EFSW_CPP_SOURCE src/efsw/FileWatcherWin32.cpp @@ -113,7 +109,8 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") src/efsw/WatcherInotify.cpp ) - if(NOT EXISTS "/usr/include/sys/inotify.h" AND NOT EXISTS "/usr/local/include/sys/inotify.h") + find_path(EFSW_INOTIFY_H NAMES sys/inotify.h NO_CACHE) + if(EFSW_INOTIFY_H STREQUAL "EFSW_INOTIFY_H-NOTFOUND") target_compile_definitions(efsw PRIVATE EFSW_INOTIFY_NOSYS) endif() elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") @@ -123,7 +120,8 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") ) endif() -if(MSVC) +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR + (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")) target_compile_definitions(efsw PRIVATE _SCL_SECURE_NO_WARNINGS) else() target_compile_options(efsw PRIVATE -Wall -Wno-long-long -fPIC) @@ -160,7 +158,9 @@ configure_package_config_file( ) export(TARGETS efsw NAMESPACE efsw:: FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Targets.cmake) -export(TARGETS efsw-static NAMESPACE efsw:: FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}-staticTargets.cmake) +if(BUILD_STATIC_LIBS) + export(TARGETS efsw-static NAMESPACE efsw:: APPEND FILE ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Targets.cmake) +endif() if(EFSW_INSTALL) install(TARGETS efsw EXPORT efswExport @@ -175,25 +175,16 @@ if(EFSW_INSTALL) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/efsw ) - install(EXPORT efswExport NAMESPACE efsw:: DESTINATION "${packageDestDir}" FILE ${PROJECT_NAME}Targets.cmake) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/efswConfig.cmake DESTINATION "${packageDestDir}") - - if(BUILD_SHARED_LIBS) + if(BUILD_STATIC_LIBS) install(TARGETS efsw-static EXPORT efswExport LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - - install( - FILES - include/efsw/efsw.h include/efsw/efsw.hpp - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/efsw - ) - - install(EXPORT efswExport NAMESPACE efsw:: DESTINATION "${packageDestDir}" FILE ${PROJECT_NAME}-staticTargets.cmake) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/efswConfig.cmake DESTINATION "${packageDestDir}") endif() + + install(EXPORT efswExport NAMESPACE efsw:: DESTINATION "${packageDestDir}" FILE ${PROJECT_NAME}Targets.cmake) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/efswConfig.cmake DESTINATION "${packageDestDir}") endif() if(BUILD_TEST_APP) diff --git a/efswConfig.cmake.in b/efswConfig.cmake.in index 9e08e72..5340f33 100644 --- a/efswConfig.cmake.in +++ b/efswConfig.cmake.in @@ -3,5 +3,8 @@ @PACKAGE_INIT@ @DEPENDENCIES_SECTION@ +include(CMakeFindDependencyMacro) + +find_dependency(Threads) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") diff --git a/src/efsw/Debug.hpp b/src/efsw/Debug.hpp index 78d3557..fefaec4 100644 --- a/src/efsw/Debug.hpp +++ b/src/efsw/Debug.hpp @@ -49,8 +49,10 @@ void efPRINTC( unsigned int cond, const char* format, ... ); #define efDEBUGC( cond, format, args... ) \ {} #else -#define efDEBUG -#define efDEBUGC +#define efDEBUG( ... ) \ + {} +#define efDEBUGC( ... ) \ + {} #endif #endif diff --git a/src/efsw/FileSystem.cpp b/src/efsw/FileSystem.cpp index f975640..b6d2d63 100644 --- a/src/efsw/FileSystem.cpp +++ b/src/efsw/FileSystem.cpp @@ -1,6 +1,6 @@ +#include #include #include -#include #if EFSW_OS == EFSW_OS_MACOSX #include @@ -107,7 +107,7 @@ std::string FileSystem::precomposeFileName( const std::string& name ) { return std::string(); } - std::string result( maxSize, '\0' ); + std::string result( maxSize + 1, '\0' ); if ( CFStringGetCString( cfMutable, &result[0], result.size(), kCFStringEncodingUTF8 ) ) { result.resize( std::strlen( result.c_str() ) ); CFRelease( cfStringRef ); diff --git a/src/efsw/FileWatcherWin32.cpp b/src/efsw/FileWatcherWin32.cpp index 2e26eeb..19b71d7 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, @@ -143,7 +144,8 @@ void FileWatcherWin32::run() { break; } else { Lock lock( mWatchesLock ); - WatchCallback( numOfBytes, ov ); + if (mWatches.find( (WatcherStructWin32*)ov ) != mWatches.end()) + WatchCallback( numOfBytes, ov ); } } } else { diff --git a/src/efsw/FileWatcherWin32.hpp b/src/efsw/FileWatcherWin32.hpp index 9cc7d59..3016aac 100644 --- a/src/efsw/FileWatcherWin32.hpp +++ b/src/efsw/FileWatcherWin32.hpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include namespace efsw { @@ -17,7 +17,7 @@ namespace efsw { class FileWatcherWin32 : public FileWatcherImpl { public: /// type for a map from WatchID to WatcherWin32 pointer - typedef std::set Watches; + typedef std::unordered_set Watches; FileWatcherWin32( FileWatcher* parent ); diff --git a/src/efsw/WatcherKqueue.cpp b/src/efsw/WatcherKqueue.cpp index 3972641..424b989 100644 --- a/src/efsw/WatcherKqueue.cpp +++ b/src/efsw/WatcherKqueue.cpp @@ -354,7 +354,8 @@ void WatcherKqueue::watch() { bool needScan = false; // Then we get the the events of the current folder - while ( ( nev = kevent( mKqueue, &mChangeList[0], mChangeListCount + 1, &event, 1, + while ( !mChangeList.empty() && + ( nev = kevent( mKqueue, mChangeList.data(), mChangeListCount + 1, &event, 1, &mWatcher->mTimeOut ) ) != 0 ) { // An error ocurred? if ( nev == -1 ) { diff --git a/src/efsw/WatcherWin32.cpp b/src/efsw/WatcherWin32.cpp index ad206e3..c4dd86a 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]; +}; - if ( NULL == lpOverlapped ) { - return; +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; + + 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 ( pWatch->Extended ) { + WatchCallbackEx( pWatch ); + } else { + WatchCallbackOld( pWatch ); + } if ( !pWatch->StopNow ) { RefreshWatch( tWatch ); @@ -69,17 +181,40 @@ 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; +RefreshResult RefreshWatch( WatcherStructWin32* pWatch ) { + initReadDirectoryChangesEx(); + + bool bRet = false; + RefreshResult ret = RefreshResult::Failed; + pWatch->Watch->Extended = 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; + if ( bRet ) { + ret = RefreshResult::SucessEx; + pWatch->Watch->Extended = true; + } + } + + if ( !bRet ) { + 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 ) + ret = RefreshResult::Success; + } if ( !bRet ) { std::string error = std::to_string( GetLastError() ); Errors::Log::createLastError( Errors::WatcherFailed, error ); } - return bRet; + return ret; } /// Stops monitoring a directory. @@ -91,19 +226,17 @@ void DestroyWatch( WatcherStructWin32* pWatch ) { CloseHandle( pWatch->Watch->DirHandle ); efSAFE_DELETE_ARRAY( pWatch->Watch->DirName ); efSAFE_DELETE( pWatch->Watch ); + efSAFE_DELETE( pWatch ); } } /// Starts monitoring a directory. WatcherStructWin32* CreateWatch( LPCWSTR szDirectory, bool recursive, DWORD bufferSize, DWORD notifyFilter, HANDLE iocp ) { - WatcherStructWin32* tWatch; - size_t ptrsize = sizeof( *tWatch ); - tWatch = static_cast( - HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, ptrsize ) ); - + WatcherStructWin32* tWatch = new WatcherStructWin32(); 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, @@ -121,7 +254,7 @@ WatcherStructWin32* CreateWatch( LPCWSTR szDirectory, bool recursive, CloseHandle( pWatch->DirHandle ); efSAFE_DELETE( pWatch->Watch ); - HeapFree( GetProcessHeap(), 0, tWatch ); + efSAFE_DELETE( tWatch ); return NULL; } diff --git a/src/efsw/WatcherWin32.hpp b/src/efsw/WatcherWin32.hpp index ae050b7..5625cee 100644 --- a/src/efsw/WatcherWin32.hpp +++ b/src/efsw/WatcherWin32.hpp @@ -22,6 +22,8 @@ namespace efsw { class WatcherWin32; +enum RefreshResult { Success, SucessEx, Failed }; + /// Internal watch data struct WatcherStructWin32 { OVERLAPPED Overlapped; @@ -33,7 +35,7 @@ struct sLastModifiedEvent { std::string fileName; }; -bool RefreshWatch( WatcherStructWin32* pWatch ); +RefreshResult RefreshWatch( WatcherStructWin32* pWatch ); void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped ); @@ -51,6 +53,7 @@ class WatcherWin32 : public Watcher { lParam( 0 ), NotifyFilter( 0 ), StopNow( false ), + Extended( false ), Watch( NULL ), DirName( NULL ) { Buffer.resize(dwBufferSize); @@ -62,9 +65,11 @@ class WatcherWin32 : public Watcher { LPARAM lParam; DWORD NotifyFilter; bool StopNow; + bool Extended; FileWatcherImpl* Watch; char* DirName; sLastModifiedEvent LastModifiedEvent; + std::vector> OldFiles; }; } // namespace efsw