#include "externals.hh" #include "c-filewatcher.hh" /*= T_FilesWatcher ===========================================================*/ T_FilesWatcher::T_FilesWatcher( ) : fd( inotify_init1( O_NONBLOCK ) ) { } T_FilesWatcher::~T_FilesWatcher( ) { if ( fd ) { close( fd ); } for ( T_WatchSet_* wf : watched_ ) { wf->watcher = nullptr; } } void T_FilesWatcher::check( ) { // Reset triggered status for ( T_WatchSet_* wf : watched_ ) { wf->triggered = false; } // Handle events from inotify inotify_event ie; while ( read( fd , &ie , sizeof( ie ) ) == sizeof( ie ) ) { #ifdef INTRUSIVE_TRACES printf( "inotify wd %d / evt %x\n" , ie.wd , ie.mask ); fflush( stdout ); #endif // INTRUSIVE_TRACES if ( ( ie.mask & ( IN_CLOSE_WRITE | IN_DELETE_SELF ) ) == 0 ) { #ifdef INTRUSIVE_TRACES printf( " -> skipping this event\n" ); #endif // INTRUSIVE_TRACES continue; } T_FSPath const* name( inotify_.get( ie.wd ) ); if ( !name ) { #ifdef INTRUSIVE_TRACES printf( " -> no matching file\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES continue; } #ifdef INTRUSIVE_TRACES printf( " -> file '%s'\n" , name->toString( ).toOSString( ).data( ) ); #endif // INTRUSIVE_TRACES auto* entry( fileWatchers_.get( *name ) ); assert( entry ); entry->trigger( ); if ( ( ie.mask & IN_DELETE_SELF ) != 0 ) { #ifdef INTRUSIVE_TRACES printf( " -> deleted, added as missing\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES inotify_rm_watch( fd , entry->wd ); missing_.add( *name ); inotify_.remove( entry->wd ); entry->wd = -2; } } // Handle missing/deleted files for ( auto it = missing_.begin( ) ; it < missing_.end( ) ; ++it ) { auto const& path( *it ); #ifdef INTRUSIVE_TRACES printf( "Checking missing file '%s'\n" , path.toString( ).toOSString( ).data( ) ); fflush( stdout ); #endif // INTRUSIVE_TRACES auto* const p( fileWatchers_.get( path ) ); #ifdef INTRUSIVE_TRACES printf( " -> found? %c\n" , p ? 'Y' : 'N' ); #endif // INTRUSIVE_TRACES assert( p ); #ifdef INTRUSIVE_TRACES printf( " -> wd = %d\n" , p->wd ); #endif // INTRUSIVE_TRACES assert( p->wd < 0 ); auto& we( *p ); const auto nwd( inotify_add_watch( fd , (char*) path.toString( ).toOSString( ).data( ) , IN_CLOSE_WRITE | IN_DELETE_SELF ) ); #ifdef INTRUSIVE_TRACES printf( " -> inotify add watch %d\n" , nwd ); fflush( stdout ); #endif // INTRUSIVE_TRACES if ( nwd < 0 ) { #ifdef INTRUSIVE_TRACES printf( " -> still missing\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES we.wd = -1; continue; } // Trigger if this was really a missing file if ( we.wd == -1 ) { #ifdef INTRUSIVE_TRACES printf( " -> triggering\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES we.trigger( ); } // Update cache we.wd = nwd; inotify_.add( nwd , path ); missing_.remove( it.pos( ) ); it --; } } /*----------------------------------------------------------------------------*/ void T_FilesWatcher::T_File_::trigger( ) { for ( T_WatchSet_* const wf : watchers ) { if ( !wf->triggered ) { wf->triggered = true; wf->callback( ); } } } void T_FilesWatcher::watchFile( T_FSPath const& path , T_WatchSet_* const watcher ) { #ifdef INTRUSIVE_TRACES printf( "creating watch on '%s' for watcher %p\n" , path.toString( ).toOSString( ).data( ) , watcher ); fflush( stdout ); #endif // INTRUSIVE_TRACES auto* const exFilePos( fileWatchers_.get( path ) ); if ( exFilePos ) { #ifdef INTRUSIVE_TRACES printf( " -> existing\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES auto& wl( exFilePos->watchers ); if ( !wl.contains( watcher ) ) { wl.add( watcher ); } } else { T_File_ f; const auto fstr{ path.toString( ).toOSString( ) }; f.wd = inotify_add_watch( fd , (char*) fstr.data( ) , IN_CLOSE_WRITE | IN_DELETE_SELF ); #ifdef INTRUSIVE_TRACES printf( " -> inotify wd %d\n" , f.wd ); fflush( stdout ); #endif // INTRUSIVE_TRACES if ( f.wd == -1 ) { missing_.add( path ); #ifdef INTRUSIVE_TRACES printf( " -> missing\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES } else { inotify_.add( f.wd , path ); #ifdef INTRUSIVE_TRACES printf( " -> found\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES } f.watchers.add( watcher ); fileWatchers_.add( path , std::move( f ) ); } } void T_FilesWatcher::unwatchAll( T_WatchSet_* const watcher ) { for ( auto i = 0u ; i < fileWatchers_.size( ) ; ) { auto const& path( fileWatchers_.keys( )[ i ] ); auto& fw( *fileWatchers_.get( path ) ); auto& wl( fw.watchers ); const auto pos( wl.indexOf( watcher ) ); if ( pos == -1 ) { i ++; continue; } wl.remove( pos ); if ( wl.empty( ) ) { const auto wd( fw.wd ); if ( wd < 0 ) { auto const pos( missing_.indexOf( path ) ); assert( pos != -1 ); missing_.remove( pos ); } else { inotify_rm_watch( fd , wd ); inotify_.remove( wd ); } fileWatchers_.remove( path ); } else { i ++; } } } /*----------------------------------------------------------------------------*/ T_FilesWatcher::T_WatchSet_::T_WatchSet_( T_FilesWatcher* watcher , F_OnFileChanges callback ) noexcept : watcher( watcher ) , callback( std::move( callback ) ) , triggered( false ) { watcher->watched_.add( this ); } T_FilesWatcher::T_WatchSet_::~T_WatchSet_( ) { if ( watcher ) { watcher->unwatchAll( this ); watcher->watched_.remove( watcher->watched_.indexOf( this ) ); } } /*= T_WatchedFiles ===========================================================*/ T_WatchedFiles::T_WatchedFiles( T_WatchedFiles&& other ) noexcept : watchSet_( std::move( other.watchSet_ ) ) { } T_WatchedFiles::T_WatchedFiles( T_FilesWatcher& watcher , F_OnFileChanges callback ) : watchSet_( NewOwned< T_FilesWatcher::T_WatchSet_ >( &watcher , std::move( callback ) ) ) { } void T_WatchedFiles::clear( ) { if ( watchSet_->watcher ) { watchSet_->watcher->unwatchAll( watchSet_.get( ) ); } } bool T_WatchedFiles::watch( T_FSPath const& file ) { if ( !watchSet_->watcher || !file.isValid( ) ) { return false; } watchSet_->watcher->watchFile( file , watchSet_.get( ) ); return true; }