#include #include #include #include #if EFSW_PLATFORM == EFSW_PLATFORM_FSEVENTS namespace efsw { WatcherFSEvents::WatcherFSEvents() : Watcher(), FWatcher( NULL ), FSStream( NULL ), WatcherGen( NULL ) {} WatcherFSEvents::~WatcherFSEvents() { if ( NULL != FSStream ) { FSEventStreamStop( FSStream ); FSEventStreamInvalidate( FSStream ); FSEventStreamRelease( FSStream ); } efSAFE_DELETE( WatcherGen ); } void WatcherFSEvents::init() { CFStringRef CFDirectory = CFStringCreateWithCString( NULL, Directory.c_str(), kCFStringEncodingUTF8 ); CFArrayRef CFDirectoryArray = CFArrayCreate( NULL, (const void**)&CFDirectory, 1, NULL ); Uint32 streamFlags = kFSEventStreamCreateFlagNone; if ( FileWatcherFSEvents::isGranular() ) { streamFlags = efswFSEventStreamCreateFlagFileEvents | efswFSEventStreamCreateFlagNoDefer | efswFSEventStreamCreateFlagUseExtendedData | efswFSEventStreamCreateFlagUseCFTypes; } else { WatcherGen = new WatcherGeneric( ID, Directory, Listener, FWatcher.load(), Recursive ); } FSEventStreamContext ctx; /* Initialize context */ ctx.version = 0; ctx.info = this; ctx.retain = NULL; ctx.release = NULL; ctx.copyDescription = NULL; dispatch_queue_t queue = dispatch_queue_create( NULL, NULL ); FSStream = FSEventStreamCreate( kCFAllocatorDefault, &FileWatcherFSEvents::FSEventCallback, &ctx, CFDirectoryArray, kFSEventStreamEventIdSinceNow, 0., streamFlags ); FSEventStreamSetDispatchQueue( FSStream, queue ); FSEventStreamStart( FSStream ); CFRelease( CFDirectoryArray ); CFRelease( CFDirectory ); } void WatcherFSEvents::sendFileAction( WatchID watchid, const std::string& dir, const std::string& filename, Action action, std::string oldFilename ) { Listener->handleFileAction( watchid, FileSystem::precomposeFileName( dir ), FileSystem::precomposeFileName( filename ), action, FileSystem::precomposeFileName( oldFilename ) ); } void WatcherFSEvents::handleAddModDel( const Uint32& flags, const std::string& path, std::string& dirPath, std::string& filePath, Uint64 inode ) { if ( ( flags & efswFSEventStreamEventFlagItemCreated ) && FileInfo::exists( path ) && ( !SanitizeEvents || FilesAdded.find( inode ) != FilesAdded.end() ) ) { sendFileAction( ID, dirPath, filePath, Actions::Add ); if ( SanitizeEvents ) FilesAdded.insert( inode ); } if ( flags & ModifiedFlags ) { sendFileAction( ID, dirPath, filePath, Actions::Modified ); } if ( ( flags & efswFSEventStreamEventFlagItemRemoved ) && !FileInfo::exists( path ) ) { // Since i don't know the order, at least i try to keep the data consistent with the real // state sendFileAction( ID, dirPath, filePath, Actions::Delete ); if ( SanitizeEvents ) FilesAdded.erase( inode ); } } void WatcherFSEvents::handleActions( std::vector& events ) { size_t esize = events.size(); for ( size_t i = 0; i < esize; i++ ) { FSEvent& event = events[i]; if ( event.Flags & ( kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped | kFSEventStreamEventFlagEventIdsWrapped | kFSEventStreamEventFlagHistoryDone | kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount | kFSEventStreamEventFlagRootChanged ) ) { continue; } if ( !Recursive ) { /** In case that is not recursive the watcher, ignore the events from subfolders */ if ( event.Path.find_last_of( FileSystem::getOSSlash() ) != Directory.size() - 1 ) { continue; } } if ( FileWatcherFSEvents::isGranular() ) { std::string dirPath( FileSystem::pathRemoveFileName( event.Path ) ); std::string filePath( FileSystem::fileNameFromPath( event.Path ) ); if ( event.Flags & ( efswFSEventStreamEventFlagItemCreated | efswFSEventStreamEventFlagItemRemoved | efswFSEventStreamEventFlagItemRenamed ) ) { if ( dirPath != Directory ) { DirsChanged.insert( dirPath ); } } // This is a mess. But it's FSEvents faults, because shrinks events from the same file // in one single event ( so there's no order for them ) For example a file could have // 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: 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].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 ) || 0 == strcasecmp( event.Path.c_str(), nEvent.Path.c_str() ) ) { sendFileAction( ID, dirPath, newFilepath, Actions::Moved, filePath ); } else { sendFileAction( ID, dirPath, filePath, Actions::Moved, newFilepath ); } } else { sendFileAction( ID, dirPath, filePath, Actions::Delete ); sendFileAction( ID, newDir, newFilepath, Actions::Add ); if ( nEvent.Flags & ModifiedFlags ) { sendFileAction( ID, newDir, newFilepath, Actions::Modified ); } } } else { handleAddModDel( nEvent.Flags, nEvent.Path, dirPath, filePath, event.inode ); } if ( nEvent.Flags & ( efswFSEventStreamEventFlagItemCreated | efswFSEventStreamEventFlagItemRemoved | efswFSEventStreamEventFlagItemRenamed ) ) { if ( newDir != Directory ) { DirsChanged.insert( newDir ); } } // Skip the renamed file i++; } else if ( FileInfo::exists( event.Path ) ) { sendFileAction( ID, dirPath, filePath, Actions::Add ); if ( event.Flags & ModifiedFlags ) { sendFileAction( ID, dirPath, filePath, Actions::Modified ); } } else { sendFileAction( ID, dirPath, filePath, Actions::Delete ); } } else { handleAddModDel( event.Flags, event.Path, dirPath, filePath, event.inode ); } } else { efDEBUG( "Directory: %s changed\n", event.Path.c_str() ); DirsChanged.insert( event.Path ); } } } void WatcherFSEvents::process() { std::unordered_set::iterator it = DirsChanged.begin(); for ( ; it != DirsChanged.end(); it++ ) { if ( !FileWatcherFSEvents::isGranular() ) { WatcherGen->watchDir( ( *it ) ); } else { sendFileAction( ID, FileSystem::pathRemoveFileName( ( *it ) ), FileSystem::fileNameFromPath( ( *it ) ), Actions::Modified ); } } DirsChanged.clear(); } } // namespace efsw #endif