#include <efsw/Debug.hpp> #include <efsw/FileSystem.hpp> #include <efsw/FileWatcherFSEvents.hpp> #include <efsw/Lock.hpp> #include <efsw/String.hpp> #include <efsw/System.hpp> #if EFSW_PLATFORM == EFSW_PLATFORM_FSEVENTS #include <sys/utsname.h> namespace efsw { int getOSXReleaseNumber() { static int osxR = -1; if ( -1 == osxR ) { struct utsname os; if ( -1 != uname( &os ) ) { std::string release( os.release ); size_t pos = release.find_first_of( '.' ); if ( pos != std::string::npos ) { release = release.substr( 0, pos ); } int rel = 0; if ( String::fromString<int>( rel, release ) ) { osxR = rel; } } } return osxR; } bool FileWatcherFSEvents::isGranular() { return getOSXReleaseNumber() >= 11; } void FileWatcherFSEvents::FSEventCallback( ConstFSEventStreamRef /*streamRef*/, void* userData, size_t numEvents, void* eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[] ) { WatcherFSEvents* watcher = static_cast<WatcherFSEvents*>( userData ); std::vector<FSEvent> events; 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] ) ); } watcher->handleActions( events ); watcher->process(); efDEBUG( "\n" ); } FileWatcherFSEvents::FileWatcherFSEvents( FileWatcher* parent ) : FileWatcherImpl( parent ), mRunLoopRef( NULL ), mLastWatchID( 0 ), mThread( NULL ) { mInitOK = true; watch(); } FileWatcherFSEvents::~FileWatcherFSEvents() { mInitOK = false; if ( mRunLoopRef.load() ) CFRunLoopStop( mRunLoopRef.load() ); efSAFE_DELETE( mThread ); WatchMap::iterator iter = mWatches.begin(); for ( ; iter != mWatches.end(); ++iter ) { WatcherFSEvents* watch = iter->second; efSAFE_DELETE( watch ); } mWatches.clear(); } WatchID FileWatcherFSEvents::addWatch( const std::string& directory, FileWatchListener* watcher, bool recursive ) { /// Wait to the RunLoopRef to be ready while ( NULL == mRunLoopRef.load() ) { System::sleep( 1 ); } std::string dir( directory ); FileInfo fi( dir ); if ( !fi.isDirectory() ) { return Errors::Log::createLastError( Errors::FileNotFound, dir ); } else if ( !fi.isReadable() ) { return Errors::Log::createLastError( Errors::FileNotReadable, dir ); } FileSystem::dirAddSlashAtEnd( dir ); if ( pathInWatches( dir ) ) { return Errors::Log::createLastError( Errors::FileRepeated, directory ); } /// Check if the directory is a symbolic link std::string curPath; std::string link( FileSystem::getLinkRealPath( dir, curPath ) ); if ( "" != link ) { /// If it's a symlink check if the realpath exists as a watcher, or /// if the path is outside the current dir if ( pathInWatches( link ) ) { return Errors::Log::createLastError( Errors::FileRepeated, directory ); } else if ( !linkAllowed( curPath, link ) ) { return Errors::Log::createLastError( Errors::FileOutOfScope, dir ); } else { dir = link; } } mLastWatchID++; WatcherFSEvents* pWatch = new WatcherFSEvents(); pWatch->Listener = watcher; pWatch->ID = mLastWatchID; pWatch->Directory = dir; pWatch->Recursive = recursive; pWatch->FWatcher = this; pWatch->init(); Lock lock( mWatchesLock ); mWatches.insert( std::make_pair( mLastWatchID, pWatch ) ); return pWatch->ID; } void FileWatcherFSEvents::removeWatch( const std::string& directory ) { Lock lock( mWatchesLock ); WatchMap::iterator iter = mWatches.begin(); for ( ; iter != mWatches.end(); ++iter ) { if ( directory == iter->second->Directory ) { removeWatch( iter->second->ID ); return; } } } void FileWatcherFSEvents::removeWatch( WatchID watchid ) { Lock lock( mWatchesLock ); WatchMap::iterator iter = mWatches.find( watchid ); if ( iter == mWatches.end() ) return; WatcherFSEvents* watch = iter->second; mWatches.erase( iter ); efDEBUG( "Removed watch %s\n", watch->Directory.c_str() ); efSAFE_DELETE( watch ); } void FileWatcherFSEvents::watch() { if ( NULL == mThread ) { mThread = new Thread( &FileWatcherFSEvents::run, this ); mThread->launch(); } } void FileWatcherFSEvents::run() { mRunLoopRef = CFRunLoopGetCurrent(); while ( mInitOK ) { mNeedInitMutex.lock(); if ( !mNeedInit.empty() ) { for ( std::vector<WatcherFSEvents*>::iterator it = mNeedInit.begin(); it != mNeedInit.end(); ++it ) { ( *it )->initAsync(); } mNeedInit.clear(); } mNeedInitMutex.unlock(); if ( mWatches.empty() ) { System::sleep( 100 ); } else { CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.5, kCFRunLoopRunTimedOut ); } } mRunLoopRef = NULL; } void FileWatcherFSEvents::handleAction( Watcher* /*watch*/, const std::string& /*filename*/, unsigned long /*action*/, std::string /*oldFilename*/ ) { /// Not used } std::list<std::string> FileWatcherFSEvents::directories() { std::list<std::string> dirs; Lock lock( mWatchesLock ); for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) { dirs.push_back( std::string( it->second->Directory ) ); } return dirs; } bool FileWatcherFSEvents::pathInWatches( const std::string& path ) { for ( WatchMap::iterator it = mWatches.begin(); it != mWatches.end(); ++it ) { if ( it->second->Directory == path ) { return true; } } return false; } } // namespace efsw #endif