xref: /glogg/src/watchtower.h (revision b0345991179d693b315dfae1d66ad21aa3989113)
184b2179eSNicolas Bonnefon #ifndef WATCHTOWER_H
284b2179eSNicolas Bonnefon #define WATCHTOWER_H
384b2179eSNicolas Bonnefon 
487e05652SNicolas Bonnefon #include <memory>
5c540156cSNicolas Bonnefon #include <atomic>
6c540156cSNicolas Bonnefon #include <thread>
7c540156cSNicolas Bonnefon #include <mutex>
887e05652SNicolas Bonnefon 
9c540156cSNicolas Bonnefon #include "watchtowerlist.h"
10b278d183SNicolas Bonnefon 
11b278d183SNicolas Bonnefon // FIXME
12b278d183SNicolas Bonnefon #include "inotifywatchtowerdriver.h"
13c540156cSNicolas Bonnefon 
14c540156cSNicolas Bonnefon // Allow the client to register for notification on an arbitrary number
15c540156cSNicolas Bonnefon // of files. It will be notified to any change (creation/deletion/modification)
16c540156cSNicolas Bonnefon // on those files.
17c540156cSNicolas Bonnefon // It is passed a platform specific driver.
1896bde7d5SNicolas Bonnefon 
1996bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant
2087e05652SNicolas Bonnefon // Registration object to implement RAII
21dc7f5916SNicolas Bonnefon #if __GNUC_MINOR__ < 7
22dc7f5916SNicolas Bonnefon typedef std::shared_ptr<void> Registration;
23dc7f5916SNicolas Bonnefon #else
2487e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>;
25dc7f5916SNicolas Bonnefon #endif
2687e05652SNicolas Bonnefon 
2796bde7d5SNicolas Bonnefon template<typename Driver>
2896bde7d5SNicolas Bonnefon class WatchTower {
2996bde7d5SNicolas Bonnefon   public:
3087e05652SNicolas Bonnefon     // Create an empty watchtower
31b278d183SNicolas Bonnefon     WatchTower();
3287e05652SNicolas Bonnefon     // Destroy the object
33c540156cSNicolas Bonnefon     ~WatchTower();
3487e05652SNicolas Bonnefon 
3587e05652SNicolas Bonnefon     // Add a file to the notification list. notification will be called when
3687e05652SNicolas Bonnefon     // the file is modified, moved or deleted.
3787e05652SNicolas Bonnefon     // Lifetime of the notification is tied to the Registration object returned.
3887e05652SNicolas Bonnefon     // Note the notification function is called with a mutex held and in a
3987e05652SNicolas Bonnefon     // third party thread, beware of races!
40c540156cSNicolas Bonnefon     Registration addFile( const std::string& file_name,
41c540156cSNicolas Bonnefon             std::function<void()> notification );
42a0936e1eSNicolas Bonnefon 
43a0936e1eSNicolas Bonnefon   private:
44b278d183SNicolas Bonnefon     // The driver (parametrised)
4596bde7d5SNicolas Bonnefon     Driver driver_;
46a0936e1eSNicolas Bonnefon 
47c540156cSNicolas Bonnefon     // List of files/dirs observed
48c540156cSNicolas Bonnefon     ObservedFileList observed_file_list_;
49a0936e1eSNicolas Bonnefon 
50c540156cSNicolas Bonnefon     // Protects the observed_file_list_
51c540156cSNicolas Bonnefon     std::mutex observers_mutex_;
52c540156cSNicolas Bonnefon 
53c540156cSNicolas Bonnefon     // Thread
54c540156cSNicolas Bonnefon     std::atomic_bool running_;
55c540156cSNicolas Bonnefon     std::thread thread_;
56c540156cSNicolas Bonnefon 
57c540156cSNicolas Bonnefon     // Exist as long as the onject exists, to ensure observers won't try to
58c540156cSNicolas Bonnefon     // call us if we are dead.
59c540156cSNicolas Bonnefon     std::shared_ptr<void> heartBeat_;
60c540156cSNicolas Bonnefon 
61c540156cSNicolas Bonnefon     // Private member functions
62c540156cSNicolas Bonnefon     static void removeNotification( WatchTower* watch_tower,
63c540156cSNicolas Bonnefon             std::shared_ptr<void> notification );
64c540156cSNicolas Bonnefon     void run();
6587e05652SNicolas Bonnefon };
6684b2179eSNicolas Bonnefon 
6796bde7d5SNicolas Bonnefon // Class template implementation
6896bde7d5SNicolas Bonnefon 
6996bde7d5SNicolas Bonnefon #include <algorithm>
7096bde7d5SNicolas Bonnefon 
7196bde7d5SNicolas Bonnefon #include <sys/types.h>
7296bde7d5SNicolas Bonnefon #include <sys/stat.h>
7396bde7d5SNicolas Bonnefon #include <unistd.h>
7496bde7d5SNicolas Bonnefon 
7596bde7d5SNicolas Bonnefon #include "log.h"
7696bde7d5SNicolas Bonnefon 
7796bde7d5SNicolas Bonnefon namespace {
7896bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name );
7996bde7d5SNicolas Bonnefon     std::string directory_path( const std::string& path );
8096bde7d5SNicolas Bonnefon };
8196bde7d5SNicolas Bonnefon 
8296bde7d5SNicolas Bonnefon template<typename Driver>
8396bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower()
8496bde7d5SNicolas Bonnefon     : thread_(), driver_(),
8596bde7d5SNicolas Bonnefon     heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
8696bde7d5SNicolas Bonnefon {
8796bde7d5SNicolas Bonnefon     running_ = true;
8896bde7d5SNicolas Bonnefon     thread_ = std::thread( &WatchTower::run, this );
8996bde7d5SNicolas Bonnefon }
9096bde7d5SNicolas Bonnefon 
9196bde7d5SNicolas Bonnefon template<typename Driver>
9296bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower()
9396bde7d5SNicolas Bonnefon {
9496bde7d5SNicolas Bonnefon     running_ = false;
95*b0345991SNicolas Bonnefon     driver_.interruptWait();
9696bde7d5SNicolas Bonnefon     thread_.join();
9796bde7d5SNicolas Bonnefon }
9896bde7d5SNicolas Bonnefon 
9996bde7d5SNicolas Bonnefon template<typename Driver>
10096bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile(
10196bde7d5SNicolas Bonnefon         const std::string& file_name,
10296bde7d5SNicolas Bonnefon         std::function<void()> notification )
10396bde7d5SNicolas Bonnefon {
10496bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::addFile " << file_name;
10596bde7d5SNicolas Bonnefon 
10696bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( observers_mutex_ );
10796bde7d5SNicolas Bonnefon 
10896bde7d5SNicolas Bonnefon     ObservedFile* existing_observed_file =
10996bde7d5SNicolas Bonnefon         observed_file_list_.searchByName( file_name );
11096bde7d5SNicolas Bonnefon 
11196bde7d5SNicolas Bonnefon     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification ) ) );
11296bde7d5SNicolas Bonnefon 
11396bde7d5SNicolas Bonnefon     if ( ! existing_observed_file )
11496bde7d5SNicolas Bonnefon     {
11596bde7d5SNicolas Bonnefon         INotifyWatchTowerDriver::SymlinkId symlink_id;
11696bde7d5SNicolas Bonnefon 
11796bde7d5SNicolas Bonnefon         auto file_id = driver_.addFile( file_name );
11896bde7d5SNicolas Bonnefon 
11996bde7d5SNicolas Bonnefon         if ( isSymLink( file_name ) )
12096bde7d5SNicolas Bonnefon         {
12196bde7d5SNicolas Bonnefon             // We want to follow the name (as opposed to the inode)
12296bde7d5SNicolas Bonnefon             // so we watch the symlink as well.
12396bde7d5SNicolas Bonnefon             symlink_id = driver_.addSymlink( file_name );
12496bde7d5SNicolas Bonnefon         }
12596bde7d5SNicolas Bonnefon 
12696bde7d5SNicolas Bonnefon         auto new_file = observed_file_list_.addNewObservedFile(
12796bde7d5SNicolas Bonnefon                 ObservedFile( file_name, ptr, file_id, symlink_id ) );
12896bde7d5SNicolas Bonnefon 
12996bde7d5SNicolas Bonnefon         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
13096bde7d5SNicolas Bonnefon         if ( ! dir )
13196bde7d5SNicolas Bonnefon         {
13296bde7d5SNicolas Bonnefon             LOG(logDEBUG) << "INotifyWatchTower::addFile dir for " << file_name
13396bde7d5SNicolas Bonnefon                 << " not watched, adding...";
13496bde7d5SNicolas Bonnefon             dir = observed_file_list_.addWatchedDirectoryForFile( file_name );
13596bde7d5SNicolas Bonnefon 
13696bde7d5SNicolas Bonnefon             dir->dir_id_ = driver_.addDir( dir->path );
13796bde7d5SNicolas Bonnefon         }
13896bde7d5SNicolas Bonnefon 
13996bde7d5SNicolas Bonnefon         new_file->dir_ = dir;
14096bde7d5SNicolas Bonnefon     }
14196bde7d5SNicolas Bonnefon     else
14296bde7d5SNicolas Bonnefon     {
14396bde7d5SNicolas Bonnefon         existing_observed_file->addCallback( ptr );
14496bde7d5SNicolas Bonnefon     }
14596bde7d5SNicolas Bonnefon 
14696bde7d5SNicolas Bonnefon     std::weak_ptr<void> weakHeartBeat(heartBeat_);
14796bde7d5SNicolas Bonnefon 
14896bde7d5SNicolas Bonnefon     // Returns a shared pointer that removes its own entry
14996bde7d5SNicolas Bonnefon     // from the list of watched stuff when it goes out of scope!
15096bde7d5SNicolas Bonnefon     // Uses a custom deleter to do the work.
15196bde7d5SNicolas Bonnefon     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
15296bde7d5SNicolas Bonnefon             if ( auto heart_beat = weakHeartBeat.lock() )
153dc7f5916SNicolas Bonnefon                 WatchTower<Driver>::removeNotification( this, ptr );
15496bde7d5SNicolas Bonnefon             } );
15596bde7d5SNicolas Bonnefon }
15696bde7d5SNicolas Bonnefon 
15796bde7d5SNicolas Bonnefon //
15896bde7d5SNicolas Bonnefon // Private functions
15996bde7d5SNicolas Bonnefon //
16096bde7d5SNicolas Bonnefon 
16196bde7d5SNicolas Bonnefon // Called by the dtor for a registration object
16296bde7d5SNicolas Bonnefon template<typename Driver>
16396bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification(
16496bde7d5SNicolas Bonnefon         WatchTower* watch_tower, std::shared_ptr<void> notification )
16596bde7d5SNicolas Bonnefon {
16696bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::removeNotification";
16796bde7d5SNicolas Bonnefon 
16896bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
16996bde7d5SNicolas Bonnefon 
17096bde7d5SNicolas Bonnefon     auto file =
17196bde7d5SNicolas Bonnefon         watch_tower->observed_file_list_.removeCallback( notification );
17296bde7d5SNicolas Bonnefon 
17396bde7d5SNicolas Bonnefon     if ( file )
17496bde7d5SNicolas Bonnefon     {
17596bde7d5SNicolas Bonnefon         watch_tower->driver_.removeFile( file->file_id_ );
17696bde7d5SNicolas Bonnefon         watch_tower->driver_.removeSymlink( file->symlink_id_ );
17796bde7d5SNicolas Bonnefon     }
17896bde7d5SNicolas Bonnefon }
17996bde7d5SNicolas Bonnefon 
18096bde7d5SNicolas Bonnefon // Run in its own thread
18196bde7d5SNicolas Bonnefon template<typename Driver>
18296bde7d5SNicolas Bonnefon void WatchTower<Driver>::run()
18396bde7d5SNicolas Bonnefon {
18496bde7d5SNicolas Bonnefon     while ( running_ ) {
18596bde7d5SNicolas Bonnefon         auto files = driver_.waitAndProcessEvents(
18696bde7d5SNicolas Bonnefon                 &observed_file_list_, &observers_mutex_ );
18796bde7d5SNicolas Bonnefon 
18896bde7d5SNicolas Bonnefon         for ( auto file: files ) {
18996bde7d5SNicolas Bonnefon             for ( auto observer: file->callbacks ) {
19096bde7d5SNicolas Bonnefon                 // Here we have to cast our generic pointer back to
19196bde7d5SNicolas Bonnefon                 // the function pointer in order to perform the call
19296bde7d5SNicolas Bonnefon                 const std::shared_ptr<std::function<void()>> fptr =
19396bde7d5SNicolas Bonnefon                     std::static_pointer_cast<std::function<void()>>( observer );
19496bde7d5SNicolas Bonnefon                 // The observer is called with the mutex held,
19596bde7d5SNicolas Bonnefon                 // Let's hope it doesn't do anything too funky.
19696bde7d5SNicolas Bonnefon                 (*fptr)();
19796bde7d5SNicolas Bonnefon             }
19896bde7d5SNicolas Bonnefon         }
19996bde7d5SNicolas Bonnefon     }
20096bde7d5SNicolas Bonnefon }
20196bde7d5SNicolas Bonnefon 
20296bde7d5SNicolas Bonnefon namespace {
20396bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name )
20496bde7d5SNicolas Bonnefon     {
20596bde7d5SNicolas Bonnefon         struct stat buf;
20696bde7d5SNicolas Bonnefon 
20796bde7d5SNicolas Bonnefon         lstat( file_name.c_str(), &buf );
20896bde7d5SNicolas Bonnefon         return ( S_ISLNK(buf.st_mode) );
20996bde7d5SNicolas Bonnefon     }
21096bde7d5SNicolas Bonnefon };
21196bde7d5SNicolas Bonnefon 
21284b2179eSNicolas Bonnefon #endif
213