1c540156cSNicolas Bonnefon #ifndef WATCHTOWERLIST_H 2c540156cSNicolas Bonnefon #define WATCHTOWERLIST_H 3c540156cSNicolas Bonnefon 4c540156cSNicolas Bonnefon // Utility classes for the WatchTower implementations 5c540156cSNicolas Bonnefon 6c540156cSNicolas Bonnefon #include <functional> 7c540156cSNicolas Bonnefon #include <string> 8c540156cSNicolas Bonnefon #include <vector> 9c540156cSNicolas Bonnefon #include <map> 10c540156cSNicolas Bonnefon #include <list> 11c540156cSNicolas Bonnefon #include <memory> 12c540156cSNicolas Bonnefon #include <algorithm> 13c540156cSNicolas Bonnefon 14*f09fa651SNicolas Bonnefon #include "log.h" 15b278d183SNicolas Bonnefon 16c540156cSNicolas Bonnefon // Utility classes 17c540156cSNicolas Bonnefon struct ProtocolInfo { 18c540156cSNicolas Bonnefon // Win32 notification variables 19c540156cSNicolas Bonnefon static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096; 20c540156cSNicolas Bonnefon 21c540156cSNicolas Bonnefon void* handle_; 22c540156cSNicolas Bonnefon static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE; 23c540156cSNicolas Bonnefon char buffer_[buffer_length_]; 24c540156cSNicolas Bonnefon }; 25c540156cSNicolas Bonnefon 26c540156cSNicolas Bonnefon // List of files and observers 27*f09fa651SNicolas Bonnefon template <typename Driver> 28c540156cSNicolas Bonnefon struct ObservedDir { 29c540156cSNicolas Bonnefon ObservedDir( const std::string this_path ) : path { this_path } {} 30c540156cSNicolas Bonnefon 31c540156cSNicolas Bonnefon // Returns the address of the protocol specific informations 32c540156cSNicolas Bonnefon ProtocolInfo* protocolInfo() { return &protocol_info_; } 33c540156cSNicolas Bonnefon 34c540156cSNicolas Bonnefon std::string path; 35*f09fa651SNicolas Bonnefon typename Driver::DirId dir_id_; 36c540156cSNicolas Bonnefon // Contains data specific to the protocol (inotify/Win32...) 37c540156cSNicolas Bonnefon ProtocolInfo protocol_info_; 38c540156cSNicolas Bonnefon }; 39c540156cSNicolas Bonnefon 40*f09fa651SNicolas Bonnefon template <typename Driver> 41c540156cSNicolas Bonnefon struct ObservedFile { 42c540156cSNicolas Bonnefon ObservedFile( 43c540156cSNicolas Bonnefon const std::string& file_name, 44c540156cSNicolas Bonnefon std::shared_ptr<void> callback, 45*f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 46*f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ) 47b278d183SNicolas Bonnefon : file_name_( file_name ) { 48c540156cSNicolas Bonnefon addCallback( callback ); 49c540156cSNicolas Bonnefon 50b278d183SNicolas Bonnefon file_id_ = file_id; 51b278d183SNicolas Bonnefon symlink_id_ = symlink_id; 52c540156cSNicolas Bonnefon dir_ = nullptr; 53c540156cSNicolas Bonnefon } 54c540156cSNicolas Bonnefon 55c540156cSNicolas Bonnefon void addCallback( std::shared_ptr<void> callback ) { 56c540156cSNicolas Bonnefon callbacks.push_back( callback ); 57c540156cSNicolas Bonnefon } 58c540156cSNicolas Bonnefon 59c540156cSNicolas Bonnefon std::string file_name_; 60c540156cSNicolas Bonnefon // List of callbacks for this file 61c540156cSNicolas Bonnefon std::vector<std::shared_ptr<void>> callbacks; 62c540156cSNicolas Bonnefon 63c540156cSNicolas Bonnefon // watch descriptor for the file itself 64*f09fa651SNicolas Bonnefon typename Driver::FileId file_id_; 65c540156cSNicolas Bonnefon // watch descriptor for the symlink (if file is a symlink) 66*f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id_; 67c540156cSNicolas Bonnefon 68c540156cSNicolas Bonnefon // link to the dir containing the file 69*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> dir_; 70c540156cSNicolas Bonnefon }; 71c540156cSNicolas Bonnefon 72c540156cSNicolas Bonnefon // A list of the observed files and directories 73c540156cSNicolas Bonnefon // This class is not thread safe 74*f09fa651SNicolas Bonnefon template<typename Driver> 75c540156cSNicolas Bonnefon class ObservedFileList { 76c540156cSNicolas Bonnefon public: 77c540156cSNicolas Bonnefon ObservedFileList() = default; 78c540156cSNicolas Bonnefon ~ObservedFileList() = default; 79c540156cSNicolas Bonnefon 80*f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByName( const std::string& file_name ); 81*f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByFileOrSymlinkWd( 82*f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 83*f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ); 84*f09fa651SNicolas Bonnefon ObservedFile<Driver>* searchByDirWdAndName( 85*f09fa651SNicolas Bonnefon typename Driver::DirId id, const char* name ); 86c540156cSNicolas Bonnefon 87*f09fa651SNicolas Bonnefon ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed ); 88c540156cSNicolas Bonnefon // Remove a callback, remove and returns the file object if 89c540156cSNicolas Bonnefon // it was the last callback on this object, nullptr if not. 90c540156cSNicolas Bonnefon // The caller has ownership of the object. 91*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> removeCallback( 92c540156cSNicolas Bonnefon std::shared_ptr<void> callback ); 93c540156cSNicolas Bonnefon 94c540156cSNicolas Bonnefon // Return the watched directory if it is watched, or nullptr 95*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name ); 96c540156cSNicolas Bonnefon // Create a new watched directory for dir_name 97*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory( const std::string& dir_name ); 98c540156cSNicolas Bonnefon 99*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name ); 100*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name ); 101c540156cSNicolas Bonnefon 102c540156cSNicolas Bonnefon private: 103c540156cSNicolas Bonnefon // List of observed files 104*f09fa651SNicolas Bonnefon std::list<ObservedFile<Driver>> observed_files_; 105c540156cSNicolas Bonnefon 106c540156cSNicolas Bonnefon // List of observed dirs, key-ed by name 107*f09fa651SNicolas Bonnefon std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_; 108c540156cSNicolas Bonnefon 109c540156cSNicolas Bonnefon // Map the inotify file (including symlinks) wds to the observed file 110*f09fa651SNicolas Bonnefon std::map<int, ObservedFile<Driver>*> by_file_wd_; 111c540156cSNicolas Bonnefon // Map the inotify directory wds to the observed files 112*f09fa651SNicolas Bonnefon std::map<int, ObservedFile<Driver>*> by_dir_wd_; 113c540156cSNicolas Bonnefon }; 114c540156cSNicolas Bonnefon 115*f09fa651SNicolas Bonnefon namespace { 116*f09fa651SNicolas Bonnefon std::string directory_path( const std::string& path ); 117*f09fa651SNicolas Bonnefon }; 118*f09fa651SNicolas Bonnefon 119*f09fa651SNicolas Bonnefon // ObservedFileList class 120*f09fa651SNicolas Bonnefon template <typename Driver> 121*f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByName( 122*f09fa651SNicolas Bonnefon const std::string& file_name ) 123*f09fa651SNicolas Bonnefon { 124*f09fa651SNicolas Bonnefon // Look for an existing observer on this file 125*f09fa651SNicolas Bonnefon auto existing_observer = observed_files_.begin(); 126*f09fa651SNicolas Bonnefon for ( ; existing_observer != observed_files_.end(); ++existing_observer ) 127*f09fa651SNicolas Bonnefon { 128*f09fa651SNicolas Bonnefon if ( existing_observer->file_name_ == file_name ) 129*f09fa651SNicolas Bonnefon { 130*f09fa651SNicolas Bonnefon LOG(logDEBUG) << "Found " << file_name; 131*f09fa651SNicolas Bonnefon break; 132*f09fa651SNicolas Bonnefon } 133*f09fa651SNicolas Bonnefon } 134*f09fa651SNicolas Bonnefon 135*f09fa651SNicolas Bonnefon if ( existing_observer != observed_files_.end() ) 136*f09fa651SNicolas Bonnefon return &( *existing_observer ); 137*f09fa651SNicolas Bonnefon else 138*f09fa651SNicolas Bonnefon return nullptr; 139*f09fa651SNicolas Bonnefon } 140*f09fa651SNicolas Bonnefon 141*f09fa651SNicolas Bonnefon template <typename Driver> 142*f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd( 143*f09fa651SNicolas Bonnefon typename Driver::FileId file_id, 144*f09fa651SNicolas Bonnefon typename Driver::SymlinkId symlink_id ) 145*f09fa651SNicolas Bonnefon { 146*f09fa651SNicolas Bonnefon auto result = find_if( observed_files_.begin(), observed_files_.end(), 147*f09fa651SNicolas Bonnefon [file_id, symlink_id] (ObservedFile<Driver> file) -> bool { 148*f09fa651SNicolas Bonnefon return ( file_id == file.file_id_ ) || 149*f09fa651SNicolas Bonnefon ( symlink_id == file.symlink_id_ ); 150*f09fa651SNicolas Bonnefon } ); 151*f09fa651SNicolas Bonnefon 152*f09fa651SNicolas Bonnefon if ( result != observed_files_.end() ) 153*f09fa651SNicolas Bonnefon return &( *result ); 154*f09fa651SNicolas Bonnefon else 155*f09fa651SNicolas Bonnefon return nullptr; 156*f09fa651SNicolas Bonnefon } 157*f09fa651SNicolas Bonnefon 158*f09fa651SNicolas Bonnefon template <typename Driver> 159*f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName( 160*f09fa651SNicolas Bonnefon typename Driver::DirId id, const char* name ) 161*f09fa651SNicolas Bonnefon { 162*f09fa651SNicolas Bonnefon auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(), 163*f09fa651SNicolas Bonnefon [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool { 164*f09fa651SNicolas Bonnefon if ( auto dir = d.second.lock() ) { 165*f09fa651SNicolas Bonnefon return ( id == dir->dir_id_ ); 166*f09fa651SNicolas Bonnefon } 167*f09fa651SNicolas Bonnefon else { 168*f09fa651SNicolas Bonnefon return false; } } ); 169*f09fa651SNicolas Bonnefon 170*f09fa651SNicolas Bonnefon if ( dir != observed_dirs_.end() ) { 171*f09fa651SNicolas Bonnefon std::string path = dir->first + "/" + name; 172*f09fa651SNicolas Bonnefon 173*f09fa651SNicolas Bonnefon // LOG(logDEBUG) << "Testing path: " << path; 174*f09fa651SNicolas Bonnefon 175*f09fa651SNicolas Bonnefon // Looking for the path in the files we are watching 176*f09fa651SNicolas Bonnefon return searchByName( path ); 177*f09fa651SNicolas Bonnefon } 178*f09fa651SNicolas Bonnefon else { 179*f09fa651SNicolas Bonnefon return nullptr; 180*f09fa651SNicolas Bonnefon } 181*f09fa651SNicolas Bonnefon } 182*f09fa651SNicolas Bonnefon 183*f09fa651SNicolas Bonnefon template <typename Driver> 184*f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile( 185*f09fa651SNicolas Bonnefon ObservedFile<Driver> new_observed ) 186*f09fa651SNicolas Bonnefon { 187*f09fa651SNicolas Bonnefon auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed ); 188*f09fa651SNicolas Bonnefon 189*f09fa651SNicolas Bonnefon return &( *new_file ); 190*f09fa651SNicolas Bonnefon } 191*f09fa651SNicolas Bonnefon 192*f09fa651SNicolas Bonnefon template <typename Driver> 193*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback( 194*f09fa651SNicolas Bonnefon std::shared_ptr<void> callback ) 195*f09fa651SNicolas Bonnefon { 196*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr; 197*f09fa651SNicolas Bonnefon 198*f09fa651SNicolas Bonnefon for ( auto observer = begin( observed_files_ ); 199*f09fa651SNicolas Bonnefon observer != end( observed_files_ ); ) 200*f09fa651SNicolas Bonnefon { 201*f09fa651SNicolas Bonnefon LOG(logDEBUG) << "Examining entry for " << observer->file_name_; 202*f09fa651SNicolas Bonnefon 203*f09fa651SNicolas Bonnefon std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks; 204*f09fa651SNicolas Bonnefon callbacks.erase( std::remove( 205*f09fa651SNicolas Bonnefon std::begin( callbacks ), std::end( callbacks ), callback ), 206*f09fa651SNicolas Bonnefon std::end( callbacks ) ); 207*f09fa651SNicolas Bonnefon 208*f09fa651SNicolas Bonnefon /* See if all notifications have been deleted for this file */ 209*f09fa651SNicolas Bonnefon if ( callbacks.empty() ) { 210*f09fa651SNicolas Bonnefon LOG(logDEBUG) << "Empty notification list, removing the watched file"; 211*f09fa651SNicolas Bonnefon returned_file = std::make_shared<ObservedFile<Driver>>( *observer ); 212*f09fa651SNicolas Bonnefon observer = observed_files_.erase( observer ); 213*f09fa651SNicolas Bonnefon } 214*f09fa651SNicolas Bonnefon else { 215*f09fa651SNicolas Bonnefon ++observer; 216*f09fa651SNicolas Bonnefon } 217*f09fa651SNicolas Bonnefon } 218*f09fa651SNicolas Bonnefon 219*f09fa651SNicolas Bonnefon return returned_file; 220*f09fa651SNicolas Bonnefon } 221*f09fa651SNicolas Bonnefon 222*f09fa651SNicolas Bonnefon template <typename Driver> 223*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory( 224*f09fa651SNicolas Bonnefon const std::string& dir_name ) 225*f09fa651SNicolas Bonnefon { 226*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> dir = nullptr; 227*f09fa651SNicolas Bonnefon 228*f09fa651SNicolas Bonnefon if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) ) 229*f09fa651SNicolas Bonnefon dir = observed_dirs_[ dir_name ].lock(); 230*f09fa651SNicolas Bonnefon 231*f09fa651SNicolas Bonnefon return dir; 232*f09fa651SNicolas Bonnefon } 233*f09fa651SNicolas Bonnefon 234*f09fa651SNicolas Bonnefon template <typename Driver> 235*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory( 236*f09fa651SNicolas Bonnefon const std::string& dir_name ) 237*f09fa651SNicolas Bonnefon { 238*f09fa651SNicolas Bonnefon auto dir = std::make_shared<ObservedDir<Driver>>( dir_name ); 239*f09fa651SNicolas Bonnefon 240*f09fa651SNicolas Bonnefon observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir ); 241*f09fa651SNicolas Bonnefon 242*f09fa651SNicolas Bonnefon return dir; 243*f09fa651SNicolas Bonnefon } 244*f09fa651SNicolas Bonnefon 245*f09fa651SNicolas Bonnefon template <typename Driver> 246*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile( 247*f09fa651SNicolas Bonnefon const std::string& file_name ) 248*f09fa651SNicolas Bonnefon { 249*f09fa651SNicolas Bonnefon return watchedDirectory( directory_path( file_name ) ); 250*f09fa651SNicolas Bonnefon } 251*f09fa651SNicolas Bonnefon 252*f09fa651SNicolas Bonnefon template <typename Driver> 253*f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile( 254*f09fa651SNicolas Bonnefon const std::string& file_name ) 255*f09fa651SNicolas Bonnefon { 256*f09fa651SNicolas Bonnefon return addWatchedDirectory( directory_path( file_name ) ); 257*f09fa651SNicolas Bonnefon } 258*f09fa651SNicolas Bonnefon 259*f09fa651SNicolas Bonnefon namespace { 260*f09fa651SNicolas Bonnefon std::string directory_path( const std::string& path ) 261*f09fa651SNicolas Bonnefon { 262*f09fa651SNicolas Bonnefon size_t slash_pos = path.rfind( '/' ); 263*f09fa651SNicolas Bonnefon 264*f09fa651SNicolas Bonnefon #ifdef _WIN32 265*f09fa651SNicolas Bonnefon if ( slash_pos == std::string::npos ) { 266*f09fa651SNicolas Bonnefon slash_pos = path.rfind( '\\' ); 267*f09fa651SNicolas Bonnefon } 268*f09fa651SNicolas Bonnefon 269*f09fa651SNicolas Bonnefon // We need to include the final slash on Windows 270*f09fa651SNicolas Bonnefon ++slash_pos; 271*f09fa651SNicolas Bonnefon LOG(logDEBUG) << "Pos = " << slash_pos; 272*f09fa651SNicolas Bonnefon #endif 273*f09fa651SNicolas Bonnefon 274*f09fa651SNicolas Bonnefon return std::string( path, 0, slash_pos ); 275*f09fa651SNicolas Bonnefon } 276*f09fa651SNicolas Bonnefon }; 277c540156cSNicolas Bonnefon #endif 278