diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 625df0e..62ba57a 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -2,7 +2,7 @@ "linux-debug": { "build": [ { - "args": " --thread-sanitizer --verbose gmake2", + "args": "--thread-sanitizer --verbose gmake2", "command": "premake5", "working_dir": "" }, @@ -101,6 +101,74 @@ } } }, + "macos-debug": { + "build": [ + { + "args": "--thread-sanitizer --verbose gmake2", + "command": "premake5", + "working_dir": "" + }, + { + "args": "-j${nproc} -C make/macosx", + "command": "make", + "working_dir": "" + } + ], + "build_types": [], + "clean": [ + { + "args": "", + "command": "", + "working_dir": "" + } + ], + "config": { + "clear_sys_env": false + }, + "os": [ + "macos" + ], + "output_parser": { + "config": { + "preset": "generic", + "relative_file_paths": true + } + } + }, + "macos-release": { + "build": [ + { + "args": "gmake2", + "command": "premake5", + "working_dir": "" + }, + { + "args": "-j${nproc} -C make/macosx -e config=release_arm64", + "command": "make", + "working_dir": "" + } + ], + "build_types": [], + "clean": [ + { + "args": "", + "command": "", + "working_dir": "" + } + ], + "config": { + "clear_sys_env": false + }, + "os": [ + "macos" + ], + "output_parser": { + "config": { + "preset": "generic", + "relative_file_paths": true + } + } + }, "mingw-debug": { "build": [ { diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1e3c8f1..034303e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,8 +11,8 @@ jobs: run: | sudo apt-get update sudo apt-get install -y wget - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha14/premake-5.0.0-alpha14-linux.tar.gz - tar xvzf premake-5.0.0-alpha14-linux.tar.gz + wget https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-linux.tar.gz + tar xvzf premake-5.0.0-beta2-linux.tar.gz - name: Build run: | ./premake5 --verbose gmake2 @@ -24,29 +24,27 @@ jobs: - uses: actions/checkout@v2 - name: Install dependencies run: | - brew install wget - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha14/premake-5.0.0-alpha14-macosx.tar.gz - tar -xzf premake-5.0.0-alpha14-macosx.tar.gz + brew install premake - name: Build run: | - ./premake5 --verbose gmake2 - make -C make/macosx/ -j`nproc` all + premake5 --verbose gmake2 + make -C make/macosx/ -j$(sysctl -n hw.ncpu) all Windows: - runs-on: windows-2019 + runs-on: windows-latest env: - MSBUILD_PATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\ + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v2 - name: Install dependencies shell: powershell run: | - Invoke-WebRequest -Uri "https://github.com/premake/premake-core/releases/download/v5.0.0-alpha14/premake-5.0.0-alpha14-windows.zip" -OutFile "premake-5.0.0-alpha14-windows.zip" - Expand-Archive -DestinationPath . -Path premake-5.0.0-alpha14-windows.zip + Invoke-WebRequest -Uri "https://github.com/premake/premake-core/releases/download/v5.0.0-beta2/premake-5.0.0-beta2-windows.zip" -OutFile "premake-5.0.0-beta2-windows.zip" + Expand-Archive -DestinationPath . -Path premake-5.0.0-beta2-windows.zip - name: Create project shell: powershell run: | - ./premake5.exe --verbose vs2019 + ./premake5.exe --verbose vs2022 - name: Build shell: cmd run: | - "%MSBUILD_PATH%\MSBuild.exe" .\make\windows\efsw.sln -m + "%MSBUILD_PATH%\MSBuild.exe" .\make\windows\efsw.sln -m /p:Platform="x64" diff --git a/README.md b/README.md index 1c71940..bb35a69 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Entropia File System Watcher ![efsw](https://web.ensoft.dev/efsw/efsw-logo.svg) * Windows via [I/O Completion Ports](http://en.wikipedia.org/wiki/IOCP) -* Mac OS X via [FSEvents](http://en.wikipedia.org/wiki/FSEvents) or [kqueue](http://en.wikipedia.org/wiki/Kqueue) +* macOS via [FSEvents](http://en.wikipedia.org/wiki/FSEvents) or [kqueue](http://en.wikipedia.org/wiki/Kqueue) * FreeBSD/BSD via [kqueue](http://en.wikipedia.org/wiki/Kqueue) @@ -113,6 +113,11 @@ or There is also a cmake file that I don't officially support but it works just fine, provided by [Mohammed Nafees](https://github.com/mnafees) and improved by [Eugene Shalygin](https://github.com/zeule). +**Packages** +------------ + +Community has been kind and contributed the recipes for different popular packages systems: [Conan](https://conan.io/center/recipes/efsw), [vcpkg](https://vcpkg.io/en/package/efsw), [xmake-repo](https://github.com/xmake-io/xmake-repo/blob/dev/packages/e/efsw/xmake.lua). I don't personally maintain those packages so they could be not up to date, but they tend to be quite up to date. + **Platform limitations and clarifications** ------------------------------------------- @@ -120,19 +125,21 @@ Directory paths are expected to be encoded as UTF-8 strings in all platforms. handleFileAction returns UTF-8 strings in all platforms. -Windows and FSEvents Mac OS X implementation can't follow symlinks ( it will ignore followSymlinks() and allowOutOfScopeLinks() ). +File modification events may be reported multiple times during a copy operation, typically after each write flush. This behavior is inherent to how file watchers operate and cannot be altered. If you need to open the file after receiving a modification event, it is recommended to wait for a reasonable period of time after the last event to avoid potential issues. -Kqueue implementation is limited by the maximum number of file descriptors allowed per process by the OS. In the case of reaching the file descriptors limit ( in BSD around 18000 and in OS X around 10240 ), it will fallback to the generic file watcher. +Windows and FSEvents macOS implementation can't follow symlinks ( it will ignore followSymlinks() and allowOutOfScopeLinks() ). -OS X will use only Kqueue if the OS X version is below 10.5. This implementation needs to be compiled separately from the OS X >= 10.5 implementation, since there's no way to compile FSEvents backend in OS X below 10.5. +Kqueue implementation is limited by the maximum number of file descriptors allowed per process by the OS. In the case of reaching the file descriptors limit ( in BSD around 18000 and in macOS around 10240 ), it will fallback to the generic file watcher. -FSEvents for OS X Lion and beyond in some cases will generate more actions than in reality ocurred, since fine-grained implementation of FSEvents doesn't give the order of the actions retrieved. In some cases I need to guess/approximate the order of them. +macOS will use only Kqueue if the macOS version is below 10.5. This implementation needs to be compiled separately from the macOS >= 10.5 implementation, since there's no way to compile FSEvents backend in macOS below 10.5. + +FSEvents for macOS Lion and beyond in some cases will generate more actions than in reality ocurred, since fine-grained implementation of FSEvents doesn't give the order of the actions retrieved. In some cases I need to guess/approximate the order of them. Generic watcher relies on the inode information to detect file and directories renames/move. Since Windows has no concept of inodes as Unix platforms do, there is no current reliable way of determining file/directory movement on Windows without help from the Windows API ( this is replaced with Add/Delete events ). Linux versions below 2.6.13 are not supported, since inotify wasn't implemented yet. I'm not interested in supporting older kernels, since I don't see the point. If someone needs this, open an issue in the issue tracker and I may consider implementing a dnotify backend. -OS-independent watcher, Kqueue and FSEvents for OS X below 10.5 keep cache of the directories structures, to be able to detect changes in the directories. This means that there's a memory overhead for these backends. +OS-independent watcher, Kqueue and FSEvents for macOS below 10.5 keep cache of the directories structures, to be able to detect changes in the directories. This means that there's a memory overhead for these backends. **Useful information** -------------------- @@ -140,7 +147,6 @@ The project also comes with a C API wrapper, contributed by [Sepul Sepehr Taghdi There's a string manipulation class not exposed in the efsw header ( efsw::String ) that can be used to make string encoding conversion. - **Clarifications** ---------------- diff --git a/include/efsw/efsw.h b/include/efsw/efsw.h index ecb9ec4..8a682c4 100644 --- a/include/efsw/efsw.h +++ b/include/efsw/efsw.h @@ -95,7 +95,15 @@ enum efsw_option /// For Windows, per default all events are captured but we might only be interested /// in a subset; the value of the option should be set to a bitwise or'ed set of /// FILE_NOTIFY_CHANGE_* flags. - EFSW_OPT_WIN_NOTIFY_FILTER = 2 + EFSW_OPT_WIN_NOTIFY_FILTER = 2, + /// For macOS (FSEvents backend), per default all modified event types are capture but we might + // only be interested in a subset; the value of the option should be set to a set of bitwise + // from: + // kFSEventStreamEventFlagItemFinderInfoMod + // kFSEventStreamEventFlagItemModified + // kFSEventStreamEventFlagItemInodeMetaMod + // Default configuration will set the 3 flags + EFSW_OPT_MAC_MODIFIED_FILTER = 3, }; /// Basic interface for listening for file events. @@ -131,7 +139,7 @@ EFSW_API void efsw_clearlasterror(); /// Add a directory watch /// On error returns WatchID with Error type. -efsw_watchid EFSW_API efsw_addwatch(efsw_watcher watcher, const char* directory, +efsw_watchid EFSW_API efsw_addwatch(efsw_watcher watcher, const char* directory, efsw_pfn_fileaction_callback callback_fn, int recursive, void* param); /// Add a directory watch, specifying options diff --git a/include/efsw/efsw.hpp b/include/efsw/efsw.hpp index b6519b6..25547db 100644 --- a/include/efsw/efsw.hpp +++ b/include/efsw/efsw.hpp @@ -135,7 +135,15 @@ enum Option { /// For Windows, per default all events are captured but we might only be interested /// in a subset; the value of the option should be set to a bitwise or'ed set of /// FILE_NOTIFY_CHANGE_* flags. - WinNotifyFilter = 2 + WinNotifyFilter = 2, + /// For macOS (FSEvents backend), per default all modified event types are capture but we might + // only be interested in a subset; the value of the option should be set to a set of bitwise + // from: + // kFSEventStreamEventFlagItemFinderInfoMod + // kFSEventStreamEventFlagItemModified + // kFSEventStreamEventFlagItemInodeMetaMod + // Default configuration will set the 3 flags + MacModifiedFilter = 3, }; } typedef Options::Option Option; @@ -168,7 +176,7 @@ class EFSW_API FileWatcher { /// @param recursive Set this to true to include subdirectories /// @param options Allows customization of a watcher /// @return Returns the watch id for the directory or, on error, a WatchID with Error type. - WatchID addWatch( const std::string& directory, FileWatchListener* watcher, bool recursive, + WatchID addWatch( const std::string& directory, FileWatchListener* watcher, bool recursive, const std::vector &options ); /// Remove a directory watch. This is a brute force search O(nlogn). diff --git a/project/qtcreator-osx/efsw.creator.user b/project/qtcreator-osx/efsw.creator.user index cc08407..c1e8795 100644 --- a/project/qtcreator-osx/efsw.creator.user +++ b/project/qtcreator-osx/efsw.creator.user @@ -1,10 +1,10 @@ - + EnvironmentId - {1e2eacf3-3fa1-45d9-bcd5-871df4d12ee9} + {49267ae2-f136-4b84-8041-cf11a20f6a32} ProjectExplorer.Project.ActiveTarget @@ -37,6 +37,7 @@ true true 1 + 0 false true false @@ -54,6 +55,7 @@ *.md, *.MD, Makefile false true + true @@ -77,6 +79,7 @@ true Builtin.DefaultTidyAndClazy 4 + false @@ -88,9 +91,9 @@ ProjectExplorer.Project.Target.0 Desktop - Desktop - Desktop - {03ce7d1a-f943-45a1-9d10-661febc7fb89} + Desktop (arm-darwin-generic-mach_o-64bit) + Desktop (arm-darwin-generic-mach_o-64bit) + {6d6b6d62-1e99-4e76-b5e2-cf731a0dbd92} 0 0 0 @@ -99,13 +102,12 @@ true - --file=../../premake4.lua --thread-sanitizer gmake + --file=../../premake4.lua --thread-sanitizer --verbose gmake /usr/local/bin/premake4 %{buildDir} ProjectExplorer.ProcessStep - -j4 true GenericProjectManager.GenericMakeStep @@ -147,7 +149,6 @@ ProjectExplorer.ProcessStep - -j4 config=release true GenericProjectManager.GenericMakeStep @@ -194,49 +195,42 @@ 1 - dwarf - - cpu-cycles - - -F true - 0 + 0 true true 2 - efsw-test-debug + false + /Users/prognoz/programming/efsw/bin/efsw-test-debug debug ProjectExplorer.CustomExecutableRunConfiguration - true + true + 0 false - false - true + 1 + false false - %{buildDir}/../../bin/ + %{buildDir}/../../bin - dwarf - - cpu-cycles - - -F true - 0 + 0 true true 2 + false efsw-test release ProjectExplorer.CustomExecutableRunConfiguration - true + false + 1 false - false true false %{buildDir}/../../bin/ diff --git a/src/efsw/FileSystem.cpp b/src/efsw/FileSystem.cpp index b6d2d63..1ed346c 100644 --- a/src/efsw/FileSystem.cpp +++ b/src/efsw/FileSystem.cpp @@ -1,11 +1,19 @@ #include #include #include +#include #if EFSW_OS == EFSW_OS_MACOSX #include #endif +#if EFSW_OS == EFSW_OS_WIN +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + namespace efsw { bool FileSystem::isDirectory( const std::string& path ) { @@ -133,4 +141,21 @@ std::string FileSystem::getCurrentWorkingDirectory() { return Platform::FileSystem::getCurrentWorkingDirectory(); } +std::string FileSystem::getRealPath( const std::string& path ) { + std::string realPath; +#if defined( EFSW_PLATFORM_POSIX ) + char dir[PATH_MAX]; + realpath( path.c_str(), &dir[0] ); + realPath = std::string( dir ); +#elif EFSW_OS == EFSW_OS_WIN + wchar_t dir[_MAX_PATH + 1]; + GetFullPathNameW( String::fromUtf8( path ).toWideString().c_str(), _MAX_PATH, &dir[0], + nullptr ); + realPath = String( dir ).toUtf8(); +#else +#warning FileSystem::getRealPath() not implemented on this platform. +#endif + return realPath; +} + } // namespace efsw diff --git a/src/efsw/FileSystem.hpp b/src/efsw/FileSystem.hpp index 6c24386..1d66ece 100644 --- a/src/efsw/FileSystem.hpp +++ b/src/efsw/FileSystem.hpp @@ -3,7 +3,6 @@ #include #include -#include namespace efsw { @@ -34,6 +33,9 @@ class FileSystem { static bool changeWorkingDirectory( const std::string& path ); static std::string getCurrentWorkingDirectory(); + + static std::string getRealPath( const std::string& path ); + }; } // namespace efsw diff --git a/src/efsw/FileWatcherCWrapper.cpp b/src/efsw/FileWatcherCWrapper.cpp index 860d7d5..8712d6e 100644 --- a/src/efsw/FileWatcherCWrapper.cpp +++ b/src/efsw/FileWatcherCWrapper.cpp @@ -28,12 +28,12 @@ class Watcher_CAPI : public efsw::FileWatchListener { */ static std::vector g_callbacks; -Watcher_CAPI* find_callback( efsw_watcher watcher, efsw_pfn_fileaction_callback fn ) { +Watcher_CAPI* find_callback( efsw_watcher watcher, efsw_pfn_fileaction_callback fn, void* param ) { for ( std::vector::iterator i = g_callbacks.begin(); i != g_callbacks.end(); ++i ) { Watcher_CAPI* callback = *i; - if ( callback->mFn == fn && callback->mWatcher == watcher ) + if ( callback->mFn == fn && callback->mWatcher == watcher && callback->mParam == param ) return *i; } @@ -84,7 +84,7 @@ efsw_watchid efsw_addwatch_withoptions(efsw_watcher watcher, const char* direct efsw_pfn_fileaction_callback callback_fn, int recursive, efsw_watcher_option *options, int options_number, void* param) { - Watcher_CAPI* callback = find_callback( watcher, callback_fn ); + Watcher_CAPI* callback = find_callback( watcher, callback_fn, param ); if ( callback == NULL ) { callback = new Watcher_CAPI( watcher, callback_fn, param ); diff --git a/src/efsw/FileWatcherFSEvents.cpp b/src/efsw/FileWatcherFSEvents.cpp index 0fa7452..dfb10a1 100644 --- a/src/efsw/FileWatcherFSEvents.cpp +++ b/src/efsw/FileWatcherFSEvents.cpp @@ -41,7 +41,33 @@ bool FileWatcherFSEvents::isGranular() { return getOSXReleaseNumber() >= 11; } -void FileWatcherFSEvents::FSEventCallback( ConstFSEventStreamRef streamRef, void* userData, +static std::string convertCFStringToStdString( CFStringRef cfString ) { + // Try to get the C string pointer directly + const char* cStr = CFStringGetCStringPtr( cfString, kCFStringEncodingUTF8 ); + + if ( cStr ) { + // If the pointer is valid, directly return a std::string from it + return std::string( cStr ); + } else { + // If not, manually convert it + CFIndex length = CFStringGetLength( cfString ); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ) + + 1; // +1 for null terminator + + char* buffer = new char[maxSize]; + + if ( CFStringGetCString( cfString, buffer, maxSize, kCFStringEncodingUTF8 ) ) { + std::string result( buffer ); + delete[] buffer; + return result; + } else { + delete[] buffer; + return ""; + } + } +} + +void FileWatcherFSEvents::FSEventCallback( ConstFSEventStreamRef /*streamRef*/, void* userData, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[] ) { @@ -51,8 +77,24 @@ void FileWatcherFSEvents::FSEventCallback( ConstFSEventStreamRef streamRef, void events.reserve( numEvents ); for ( size_t i = 0; i < numEvents; i++ ) { - events.push_back( FSEvent( std::string( ( (char**)eventPaths )[i] ), (long)eventFlags[i], - (Uint64)eventIds[i] ) ); + if ( isGranular() ) { + CFDictionaryRef pathInfoDict = + static_cast( CFArrayGetValueAtIndex( (CFArrayRef)eventPaths, i ) ); + CFStringRef path = static_cast( + CFDictionaryGetValue( pathInfoDict, kFSEventStreamEventExtendedDataPathKey ) ); + CFNumberRef cfInode = static_cast( + CFDictionaryGetValue( pathInfoDict, kFSEventStreamEventExtendedFileIDKey ) ); + + if ( cfInode ) { + unsigned long inode = 0; + CFNumberGetValue( cfInode, kCFNumberLongType, &inode ); + events.push_back( FSEvent( convertCFStringToStdString( path ), (long)eventFlags[i], + (Uint64)eventIds[i], inode ) ); + } + } else { + events.push_back( FSEvent( std::string( ( (char**)eventPaths )[i] ), + (long)eventFlags[i], (Uint64)eventIds[i] ) ); + } } watcher->handleActions( events ); @@ -84,8 +126,8 @@ FileWatcherFSEvents::~FileWatcherFSEvents() { } WatchID FileWatcherFSEvents::addWatch( const std::string& directory, FileWatchListener* watcher, - bool recursive, const std::vector &options ) { - std::string dir( directory ); + bool recursive, const std::vector& options ) { + std::string dir( FileSystem::getRealPath( directory ) ); FileInfo fi( dir ); @@ -125,6 +167,8 @@ WatchID FileWatcherFSEvents::addWatch( const std::string& directory, FileWatchLi pWatch->Directory = dir; pWatch->Recursive = recursive; pWatch->FWatcher = this; + pWatch->ModifiedFlags = + getOptionValue( options, Option::MacModifiedFilter, efswFSEventsModified ); pWatch->init(); @@ -169,8 +213,8 @@ void FileWatcherFSEvents::removeWatch( WatchID watchid ) { void FileWatcherFSEvents::watch() {} -void FileWatcherFSEvents::handleAction( Watcher* watch, const std::string& filename, - unsigned long action, std::string oldFilename ) { +void FileWatcherFSEvents::handleAction( Watcher* /*watch*/, const std::string& /*filename*/, + unsigned long /*action*/, std::string /*oldFilename*/ ) { /// Not used } diff --git a/src/efsw/FileWatcherFSEvents.hpp b/src/efsw/FileWatcherFSEvents.hpp index 5ad182e..daa538c 100644 --- a/src/efsw/FileWatcherFSEvents.hpp +++ b/src/efsw/FileWatcherFSEvents.hpp @@ -16,27 +16,6 @@ namespace efsw { -/* OSX < 10.7 has no file events */ -/* So i declare the events constants */ -enum FSEventEvents { - efswFSEventStreamCreateFlagNoDefer = 0x00000002, - efswFSEventStreamCreateFlagFileEvents = 0x00000010, - efswFSEventStreamEventFlagItemCreated = 0x00000100, - efswFSEventStreamEventFlagItemRemoved = 0x00000200, - efswFSEventStreamEventFlagItemInodeMetaMod = 0x00000400, - efswFSEventStreamEventFlagItemRenamed = 0x00000800, - efswFSEventStreamEventFlagItemModified = 0x00001000, - efswFSEventStreamEventFlagItemFinderInfoMod = 0x00002000, - efswFSEventStreamEventFlagItemChangeOwner = 0x00004000, - efswFSEventStreamEventFlagItemXattrMod = 0x00008000, - efswFSEventStreamEventFlagItemIsFile = 0x00010000, - efswFSEventStreamEventFlagItemIsDir = 0x00020000, - efswFSEventStreamEventFlagItemIsSymlink = 0x00040000, - efswFSEventsModified = efswFSEventStreamEventFlagItemFinderInfoMod | - efswFSEventStreamEventFlagItemModified | - efswFSEventStreamEventFlagItemInodeMetaMod -}; - /// Implementation for Win32 based on ReadDirectoryChangesW. /// @class FileWatcherFSEvents class FileWatcherFSEvents : public FileWatcherImpl { diff --git a/src/efsw/String.hpp b/src/efsw/String.hpp index 65bce33..b42b945 100644 --- a/src/efsw/String.hpp +++ b/src/efsw/String.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -24,7 +23,7 @@ namespace efsw { * **/ class String { public: - typedef Uint32 StringBaseType; + typedef char32_t StringBaseType; typedef std::basic_string StringType; typedef StringType::iterator Iterator; //! Iterator type typedef StringType::const_iterator ConstIterator; //! Constant iterator type diff --git a/src/efsw/WatcherFSEvents.cpp b/src/efsw/WatcherFSEvents.cpp index 3621df7..11b7e81 100644 --- a/src/efsw/WatcherFSEvents.cpp +++ b/src/efsw/WatcherFSEvents.cpp @@ -35,7 +35,9 @@ void WatcherFSEvents::init() { Uint32 streamFlags = kFSEventStreamCreateFlagNone; if ( FileWatcherFSEvents::isGranular() ) { - streamFlags = efswFSEventStreamCreateFlagFileEvents | efswFSEventStreamCreateFlagNoDefer; + streamFlags = efswFSEventStreamCreateFlagFileEvents | efswFSEventStreamCreateFlagNoDefer | + efswFSEventStreamCreateFlagUseExtendedData | + efswFSEventStreamCreateFlagUseCFTypes; } else { WatcherGen = new WatcherGeneric( ID, Directory, Listener, FWatcher.load(), Recursive ); } @@ -77,7 +79,7 @@ void WatcherFSEvents::handleAddModDel( const Uint32& flags, const std::string& p } } - if ( flags & efswFSEventsModified ) { + if ( flags & ModifiedFlags ) { sendFileAction( ID, dirPath, filePath, Actions::Modified ); } @@ -128,19 +130,20 @@ void WatcherFSEvents::handleActions( std::vector& events ) { // been added modified and erased, but i can't know if first was erased and then added // and modified, or added, then modified and then erased. I don't know what they were // thinking by doing this... - efDEBUG( "Event in: %s - flags: %ld\n", event.Path.c_str(), event.Flags ); + efDEBUG( "Event in: %s - flags: 0x%x\n", event.Path.c_str(), event.Flags ); if ( event.Flags & efswFSEventStreamEventFlagItemRenamed ) { if ( ( i + 1 < esize ) && ( events[i + 1].Flags & efswFSEventStreamEventFlagItemRenamed ) && - ( events[i + 1].Id == event.Id + 1 ) ) { + ( events[i + 1].inode == event.inode ) ) { FSEvent& nEvent = events[i + 1]; std::string newDir( FileSystem::pathRemoveFileName( nEvent.Path ) ); std::string newFilepath( FileSystem::fileNameFromPath( nEvent.Path ) ); if ( event.Path != nEvent.Path ) { if ( dirPath == newDir ) { - if ( !FileInfo::exists( event.Path ) ) { + if ( !FileInfo::exists( event.Path ) || + 0 == strcasecmp( event.Path.c_str(), nEvent.Path.c_str() ) ) { sendFileAction( ID, dirPath, newFilepath, Actions::Moved, filePath ); } else { @@ -151,7 +154,7 @@ void WatcherFSEvents::handleActions( std::vector& events ) { sendFileAction( ID, dirPath, filePath, Actions::Delete ); sendFileAction( ID, newDir, newFilepath, Actions::Add ); - if ( nEvent.Flags & efswFSEventsModified ) { + if ( nEvent.Flags & ModifiedFlags ) { sendFileAction( ID, newDir, newFilepath, Actions::Modified ); } } @@ -172,7 +175,7 @@ void WatcherFSEvents::handleActions( std::vector& events ) { } else if ( FileInfo::exists( event.Path ) ) { sendFileAction( ID, dirPath, filePath, Actions::Add ); - if ( event.Flags & efswFSEventsModified ) { + if ( event.Flags & ModifiedFlags ) { sendFileAction( ID, dirPath, filePath, Actions::Modified ); } } else { diff --git a/src/efsw/WatcherFSEvents.hpp b/src/efsw/WatcherFSEvents.hpp index a18c06d..125c137 100644 --- a/src/efsw/WatcherFSEvents.hpp +++ b/src/efsw/WatcherFSEvents.hpp @@ -14,15 +14,40 @@ namespace efsw { +/* OSX < 10.7 has no file events */ +/* So i declare the events constants */ +enum FSEventEvents { + efswFSEventStreamCreateFlagUseCFTypes = 0x00000001, + efswFSEventStreamCreateFlagNoDefer = 0x00000002, + efswFSEventStreamCreateFlagFileEvents = 0x00000010, + efswFSEventStreamCreateFlagUseExtendedData = 0x00000040, + efswFSEventStreamEventFlagItemCreated = 0x00000100, + efswFSEventStreamEventFlagItemRemoved = 0x00000200, + efswFSEventStreamEventFlagItemInodeMetaMod = 0x00000400, + efswFSEventStreamEventFlagItemRenamed = 0x00000800, + efswFSEventStreamEventFlagItemModified = 0x00001000, + efswFSEventStreamEventFlagItemFinderInfoMod = 0x00002000, + efswFSEventStreamEventFlagItemChangeOwner = 0x00004000, + efswFSEventStreamEventFlagItemXattrMod = 0x00008000, + efswFSEventStreamEventFlagItemIsFile = 0x00010000, + efswFSEventStreamEventFlagItemIsDir = 0x00020000, + efswFSEventStreamEventFlagItemIsSymlink = 0x00040000, + efswFSEventsModified = efswFSEventStreamEventFlagItemFinderInfoMod | + efswFSEventStreamEventFlagItemModified | + efswFSEventStreamEventFlagItemInodeMetaMod +}; + class FileWatcherFSEvents; class FSEvent { public: - FSEvent( std::string path, long flags, Uint64 id ) : Path( path ), Flags( flags ), Id( id ) {} + FSEvent( std::string path, long flags, Uint64 id, Uint64 inode = 0 ) : + Path( path ), Flags( flags ), Id( id ), inode( inode ) {} std::string Path; long Flags; Uint64 Id; + Uint64 inode{ 0 }; }; class WatcherFSEvents : public Watcher { @@ -42,6 +67,7 @@ class WatcherFSEvents : public Watcher { Atomic FWatcher; FSEventStreamRef FSStream; + Uint64 ModifiedFlags{ efswFSEventsModified }; protected: void handleAddModDel( const Uint32& flags, const std::string& path, std::string& dirPath, diff --git a/src/efsw/WatcherWin32.cpp b/src/efsw/WatcherWin32.cpp index d06cc92..712419e 100644 --- a/src/efsw/WatcherWin32.cpp +++ b/src/efsw/WatcherWin32.cpp @@ -46,7 +46,7 @@ static void initReadDirectoryChangesEx() { if ( !hModule ) return; - pReadDirectoryChangesExW = + pReadDirectoryChangesExW = (EFSW_LPREADDIRECTORYCHANGESEXW)GetProcAddress( hModule, "ReadDirectoryChangesExW" ); } } @@ -169,7 +169,7 @@ void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOve } // Fork watch depending on the Windows API supported - if ( pReadDirectoryChangesExW ) { + if ( pWatch->Extended ) { WatchCallbackEx( pWatch ); } else { WatchCallbackOld( pWatch ); @@ -181,21 +181,32 @@ void CALLBACK WatchCallback( DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOve } /// Refreshes the directory monitoring. -bool RefreshWatch( WatcherStructWin32* pWatch ) { +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; - } else { + 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 ) { @@ -203,7 +214,7 @@ bool RefreshWatch( WatcherStructWin32* pWatch ) { Errors::Log::createLastError( Errors::WatcherFailed, error ); } - return bRet; + return ret; } /// Stops monitoring a directory. @@ -236,7 +247,7 @@ WatcherStructWin32* CreateWatch( LPCWSTR szDirectory, bool recursive, pWatch->NotifyFilter = notifyFilter; pWatch->Recursive = recursive; - if ( RefreshWatch( tWatch ) ) { + if ( RefreshResult::Failed != RefreshWatch( tWatch ) ) { return tWatch; } } diff --git a/src/efsw/WatcherWin32.hpp b/src/efsw/WatcherWin32.hpp index 1b622a5..ea1e8e4 100644 --- a/src/efsw/WatcherWin32.hpp +++ b/src/efsw/WatcherWin32.hpp @@ -22,6 +22,8 @@ namespace efsw { class WatcherWin32; +enum RefreshResult { Failed, Success, SucessEx }; + /// 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,6 +65,7 @@ class WatcherWin32 : public Watcher { LPARAM lParam; DWORD NotifyFilter; bool StopNow; + bool Extended; FileWatcherImpl* Watch; char* DirName; sLastModifiedEvent LastModifiedEvent;