#include "externals.hh" #include "c-filewatcher.hh" //#define INTRUSIVE_TRACES /*= 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* namePtr( inotify_.get( ie.wd ) ); if ( !namePtr ) { #ifdef INTRUSIVE_TRACES // {{{ printf( " -> no matching file\n" ); fflush( stdout ); #endif // INTRUSIVE_TRACES }}} continue; } const auto name( *namePtr ); #ifdef INTRUSIVE_TRACES // {{{ printf( " -> file '%s'\n" , name.toString( ).toOSString( ).data( ) ); #endif // INTRUSIVE_TRACES }}} auto* const entry( fileWatchers_.get( name ) ); assert( entry ); const auto wd{ entry->wd }; entry->trigger( ); if ( ( ie.mask & IN_DELETE_SELF ) != 0 ) { #ifdef INTRUSIVE_TRACES // {{{ printf( " -> %s deleted, added as missing\n" , name.toString( ).toOSString( ).data( ) ); fflush( stdout ); #endif // INTRUSIVE_TRACES }}} inotify_rm_watch( fd , wd ); missing_.add( name ); inotify_.remove( wd ); // We need to fetch the correct entry back, as the callback // might have changed the underlying data. auto* const nEntry{ fileWatchers_.get( name ) }; assert( nEntry ); nEntry->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 }}} const int32_t idx{ missing_.indexOf( path ) }; if ( idx >= 0 ) { missing_.removeSwap( idx ); #ifdef INTRUSIVE_TRACES // {{{ printf( " -> removing from missing files (index %d)\n" , idx ); 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; }