diff --git a/filewatcher.cc b/filewatcher.cc index b76d92e..683e7fb 100644 --- a/filewatcher.cc +++ b/filewatcher.cc @@ -10,11 +10,11 @@ T_FilesWatcher::T_FilesWatcher( ) { } T_FilesWatcher::T_FilesWatcher( T_FilesWatcher&& other ) noexcept - : fd( 0 ) , watched( std::move( other.watched ) ) + : fd( 0 ) , watched_( std::move( other.watched_ ) ) { std::swap( fd , other.fd ); - other.watched.clear( ); - for ( T_WatchedFiles* wf : watched ) { + other.watched_.clear( ); + for ( T_WatchedFiles* wf : watched_ ) { if ( wf ) { wf->watcher = this; } @@ -26,7 +26,7 @@ T_FilesWatcher::~T_FilesWatcher( ) if ( fd ) { close( fd ); } - for ( T_WatchedFiles* wf : watched ) { + for ( T_WatchedFiles* wf : watched_ ) { if ( wf ) { wf->watcher = nullptr; } @@ -35,41 +35,148 @@ T_FilesWatcher::~T_FilesWatcher( ) void T_FilesWatcher::check( ) { - for ( T_WatchedFiles* wf : watched ) { - if ( wf ) { - wf->triggered = false; - } + // Reset triggered status + for ( T_WatchedFiles* wf : watched_ ) { + wf->triggered = false; } + // Handle events from inotify inotify_event ie; while ( read( fd , &ie , sizeof( ie ) ) == sizeof( ie ) ) { + printf( "inotify wd %d / evt %x\n" , ie.wd , ie.mask ); fflush( stdout ); if ( ( ie.mask & ( IN_CLOSE_WRITE | IN_DELETE_SELF ) ) == 0 ) { + printf( " -> skipping this event\n" ); continue; } - for ( T_WatchedFiles* wf : watched ) { - if ( !wf || wf->triggered ) { - continue; - } - auto const& idl( wf->identifiers ); - if ( find( idl , ie.wd ) != idl.end( ) ) { - wf->triggered = true; - wf->callback( ); - } + const auto pos( inotify_.find( ie.wd ) ); + if ( pos == inotify_.end( ) ) { + printf( " -> no matching file\n" ); fflush( stdout ); + continue; + } + printf( " -> file '%s'\n" , pos->second.c_str( ) ); + + auto& entry( fileWatchers_.find( pos->second )->second ); + entry.trigger( ); + + if ( ( ie.mask & IN_DELETE_SELF ) != 0 ) { + printf( " -> deleted, added as missing\n" ); fflush( stdout ); + inotify_rm_watch( fd , entry.wd ); + missing_.push_back( pos->second ); + entry.wd = -2; + inotify_.erase( entry.wd ); + } + } + + // Handle missing/deleted files + for ( auto it = missing_.begin( ) ; it < missing_.end( ) ; ++it ) { + auto const& path( *it ); + //printf( "Checking missing file '%s'\n" , path.c_str( ) ); fflush( stdout ); + const auto p( fileWatchers_.find( path ) ); + //printf( " -> found? %c\n" , p != fileWatchers_.end( ) ? 'Y' : 'N' ); + assert( p != fileWatchers_.end( ) ); + //printf( " -> wd = %d\n" , p->second.wd ); + assert( p->second.wd < 0 ); + auto& we( p->second ); + + const auto nwd( inotify_add_watch( fd , path.c_str( ) , + IN_CLOSE_WRITE | IN_DELETE_SELF ) ); + //printf( " -> inotify add watch %d\n" , nwd ); fflush( stdout ); + if ( nwd < 0 ) { + //printf( " -> still missing\n" ); fflush( stdout ); + we.wd = -1; + continue; + } + + // Trigger if this was really a missing file + if ( we.wd == -1 ) { + //printf( " -> triggering\n" ); fflush( stdout ); + we.trigger( ); + } + + // Update cache + we.wd = nwd; + missing_.erase( it ); + inotify_.emplace( nwd , path ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_FilesWatcher::T_File_::trigger( ) +{ + for ( T_WatchedFiles* const wf : watchers ) { + if ( !wf->triggered ) { + wf->triggered = true; + wf->callback( ); } } } +void T_FilesWatcher::watchFile( + __rd__ std::string const& path , + __rd__ T_WatchedFiles* const watcher ) +{ + //printf( "creating watch on '%s' for watcher %p\n" , path.c_str( ) , watcher ); fflush( stdout ); + auto const exFilePos( fileWatchers_.find( path ) ); + if ( exFilePos != fileWatchers_.end( ) ) { + //printf( " -> existing\n" ); fflush( stdout ); + auto& wl( exFilePos->second.watchers ); + if ( !Contains( wl , watcher ) ) { + wl.push_back( watcher ); + } + } else { + T_File_ f; + f.wd = inotify_add_watch( fd , path.c_str( ) , + IN_CLOSE_WRITE | IN_DELETE_SELF ); + //printf( " -> inotify wd %d\n" , f.wd ); fflush( stdout ); + if ( f.wd == -1 ) { + missing_.push_back( path ); + //printf( " -> missing\n" ); fflush( stdout ); + } else { + inotify_.emplace( f.wd , path ); + //printf( " -> found\n" ); fflush( stdout ); + } + f.watchers.push_back( watcher ); + fileWatchers_.emplace( path , std::move( f ) ); + } +} + +void T_FilesWatcher::unwatchAll( + __rd__ T_WatchedFiles* const watcher ) +{ + for ( auto& entry : fileWatchers_ ) { + auto& wl( entry.second.watchers ); + const auto pos( find( wl , watcher ) ); + if ( pos == wl.end( ) ) { + continue; + } + wl.erase( pos ); + if ( wl.empty( ) ) { + const auto wd( entry.second.wd ); + if ( wd < 0 ) { + auto const pos( find( missing_ , entry.first ) ); + assert( pos != missing_.end( ) ); + missing_.erase( pos ); + } else { + inotify_rm_watch( fd , wd ); + inotify_.erase( wd ); + } + fileWatchers_.erase( entry.first ); + } + } +} + + /*= T_WatchedFiles ===========================================================*/ T_WatchedFiles::T_WatchedFiles( T_WatchedFiles&& other ) noexcept : watcher( other.watcher ) , callback( other.callback ) , - triggered( other.triggered ) , - identifiers( std::move( other.identifiers ) ) + triggered( other.triggered ) { if ( watcher ) { other.watcher = nullptr; - *( find( watcher->watched , &other ) ) = this; + *( find( watcher->watched_ , &other ) ) = this; } } @@ -78,43 +185,31 @@ T_WatchedFiles::T_WatchedFiles( __rd__ const F_OnFileChanges callback ) : watcher( &watcher ) , callback( callback ) , triggered( false ) { - watcher.watched.push_back( this ); + watcher.watched_.push_back( this ); } T_WatchedFiles::~T_WatchedFiles( ) { - clear( ); if ( watcher ) { - watcher->watched.erase( find( watcher->watched , this ) ); + watcher->unwatchAll( this ); + watcher->watched_.erase( find( watcher->watched_ , this ) ); } } void T_WatchedFiles::clear( ) { if ( watcher ) { - const auto fd( watcher->fd ); - for ( int wd : identifiers ) { - inotify_rm_watch( fd , wd ); - } + watcher->unwatchAll( this ); } - identifiers.clear( ); } bool T_WatchedFiles::watch( __rd__ std::string const& file ) { - static constexpr auto inFlags( IN_CLOSE_WRITE | IN_DELETE_SELF ); - if ( watcher ) { - const auto wd( inotify_add_watch( watcher->fd , - file.c_str( ) , inFlags ) ); - if ( wd == -1 ) { - return false; - } - if ( find( identifiers , wd ) == identifiers.end( ) ) { - identifiers.push_back( wd ); - } - return true; + if ( !watcher ) { + return false; } - return false; + watcher->watchFile( file , this ); + return true; } diff --git a/filewatcher.hh b/filewatcher.hh index 11d7861..18f32e3 100644 --- a/filewatcher.hh +++ b/filewatcher.hh @@ -6,6 +6,13 @@ /*= T_FilesWatcher / T_WatchedFiles ==========================================*/ +/* So we need to be able to watch for files being created, updated and deleted. + * Watching for updates & deletions is pretty easy. For files being created, + * one has to watch the parent directory. In addition, the case where a file + * is being updated by rotation (move old, write new, rm old) needs to be + * handled correctly as it is what vim does. + */ + struct T_FilesWatcher; struct T_WatchedFiles; using F_OnFileChanges = std::function< void( void ) >; @@ -23,8 +30,21 @@ struct T_FilesWatcher void check( ); private: + struct T_File_ { + int wd; + std::vector< T_WatchedFiles* > watchers; + void trigger( ); + }; + int fd; - std::vector< T_WatchedFiles* > watched; + std::map< std::string , T_File_ > fileWatchers_; + std::map< int , std::string > inotify_; + std::vector< std::string > missing_; + std::vector< T_WatchedFiles* > watched_; + + void watchFile( __rd__ std::string const& path , + __rd__ T_WatchedFiles* const watcher ); + void unwatchAll( __rd__ T_WatchedFiles* const watcher ); }; /*----------------------------------------------------------------------------*/ @@ -50,6 +70,5 @@ struct T_WatchedFiles T_FilesWatcher* watcher; const F_OnFileChanges callback; bool triggered; - std::vector< int > identifiers; }; using P_WatchedFiles = std::unique_ptr< T_WatchedFiles >; diff --git a/sync.cc b/sync.cc index e220d2b..30dc174 100644 --- a/sync.cc +++ b/sync.cc @@ -276,10 +276,6 @@ void T_SyncManager::curvesChanged_( ) bool missing; if ( !loadCurves_( missing ) && missing ) { watcher_.reset( ); - } else { - // XXX temp bullshit to work around the deletion problem - watcher_->clear( ); - watcher_->watch( "curves.json" ); } } diff --git a/utilities.cc b/utilities.cc index 2ab17c4..8a4f00a 100644 --- a/utilities.cc +++ b/utilities.cc @@ -40,6 +40,37 @@ void anglesToMatrix( matrix[8] = c[0]*c[1]; } +/*----------------------------------------------------------------------------*/ + +std::string GetAbsolutePath( + __rd__ std::string const& path ) +{ + char* const rp( realpath( path.c_str( ) , nullptr ) ); + if ( !rp ) { + return {}; + } + + const std::string rv( rp ); + free( rp ); + return rv; +} + +std::string GetParentPath( + __rd__ std::string const& path ) +{ + char buffer[ path.length( ) + 1 ]; + strcpy( buffer , path.c_str( ) ); + + char* const rp( realpath( dirname( buffer ) , nullptr ) ); + if ( !rp ) { + return {}; + } + + const std::string rv( rp ); + free( rp ); + return rv; +} + /*= JSON =====================================================================*/ diff --git a/utilities.hh b/utilities.hh index 0918e43..f687314 100644 --- a/utilities.hh +++ b/utilities.hh @@ -59,6 +59,24 @@ inline auto find( return std::find( collection.begin( ) , collection.end( ) , item ); } +template< typename T , typename I > +inline bool Contains( + __rd__ T const& collection , + __rd__ I const& item ) +{ + return std::find( collection.begin( ) , collection.end( ) , item ) != collection.end( ); +} + +/*----------------------------------------------------------------------------*/ + +// Get the absolute path +std::string GetAbsolutePath( + __rd__ std::string const& path ); + +// Get the absolute parent path for a (possibly relative) path +std::string GetParentPath( + __rd__ std::string const& path ); + /*= JSON utilities ===========================================================*/