xref: /glogg/src/watchtower.h (revision fcaa75578d41fbd1a1b35740bd5dc8176496a14b)
184b2179eSNicolas Bonnefon #ifndef WATCHTOWER_H
284b2179eSNicolas Bonnefon #define WATCHTOWER_H
384b2179eSNicolas Bonnefon 
4f09fa651SNicolas Bonnefon #include "config.h"
5f09fa651SNicolas Bonnefon 
687e05652SNicolas Bonnefon #include <memory>
7c540156cSNicolas Bonnefon #include <atomic>
8c540156cSNicolas Bonnefon #include <thread>
9c540156cSNicolas Bonnefon #include <mutex>
1087e05652SNicolas Bonnefon 
11c540156cSNicolas Bonnefon #include "watchtowerlist.h"
12b278d183SNicolas Bonnefon 
13c540156cSNicolas Bonnefon // Allow the client to register for notification on an arbitrary number
14c540156cSNicolas Bonnefon // of files. It will be notified to any change (creation/deletion/modification)
15c540156cSNicolas Bonnefon // on those files.
16c540156cSNicolas Bonnefon // It is passed a platform specific driver.
1796bde7d5SNicolas Bonnefon 
1896bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant
1987e05652SNicolas Bonnefon // Registration object to implement RAII
2058f443c7SNicolas Bonnefon #ifdef HAS_TEMPLATE_ALIASES
2187e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>;
2258f443c7SNicolas Bonnefon #else
2358f443c7SNicolas Bonnefon typedef std::shared_ptr<void> Registration;
24dc7f5916SNicolas Bonnefon #endif
2587e05652SNicolas Bonnefon 
2696bde7d5SNicolas Bonnefon template<typename Driver>
2796bde7d5SNicolas Bonnefon class WatchTower {
2896bde7d5SNicolas Bonnefon   public:
2987e05652SNicolas Bonnefon     // Create an empty watchtower
30b278d183SNicolas Bonnefon     WatchTower();
3187e05652SNicolas Bonnefon     // Destroy the object
32c540156cSNicolas Bonnefon     ~WatchTower();
3387e05652SNicolas Bonnefon 
34*fcaa7557SNicolas Bonnefon     // Set the polling interval (in ms)
35*fcaa7557SNicolas Bonnefon     // 0 disables polling and is the default
36*fcaa7557SNicolas Bonnefon     void setPollingInterval( int interval_ms );
37*fcaa7557SNicolas Bonnefon 
3887e05652SNicolas Bonnefon     // Add a file to the notification list. notification will be called when
3987e05652SNicolas Bonnefon     // the file is modified, moved or deleted.
4087e05652SNicolas Bonnefon     // Lifetime of the notification is tied to the Registration object returned.
4187e05652SNicolas Bonnefon     // Note the notification function is called with a mutex held and in a
4287e05652SNicolas Bonnefon     // third party thread, beware of races!
43c540156cSNicolas Bonnefon     Registration addFile( const std::string& file_name,
44c540156cSNicolas Bonnefon             std::function<void()> notification );
45a0936e1eSNicolas Bonnefon 
463104b268SNicolas Bonnefon     // Number of watched directories (for tests)
473104b268SNicolas Bonnefon     unsigned int numberWatchedDirectories() const;
483104b268SNicolas Bonnefon 
49a0936e1eSNicolas Bonnefon   private:
50b278d183SNicolas Bonnefon     // The driver (parametrised)
5196bde7d5SNicolas Bonnefon     Driver driver_;
52a0936e1eSNicolas Bonnefon 
53c540156cSNicolas Bonnefon     // List of files/dirs observed
54f09fa651SNicolas Bonnefon     ObservedFileList<Driver> observed_file_list_;
55a0936e1eSNicolas Bonnefon 
56c540156cSNicolas Bonnefon     // Protects the observed_file_list_
57c540156cSNicolas Bonnefon     std::mutex observers_mutex_;
58c540156cSNicolas Bonnefon 
59*fcaa7557SNicolas Bonnefon     // Polling interval (0 disables polling)
60*fcaa7557SNicolas Bonnefon     uint32_t polling_interval_ms_ = 0;
61*fcaa7557SNicolas Bonnefon 
62c540156cSNicolas Bonnefon     // Thread
63c540156cSNicolas Bonnefon     std::atomic_bool running_;
64c540156cSNicolas Bonnefon     std::thread thread_;
65c540156cSNicolas Bonnefon 
66c540156cSNicolas Bonnefon     // Exist as long as the onject exists, to ensure observers won't try to
67c540156cSNicolas Bonnefon     // call us if we are dead.
68c540156cSNicolas Bonnefon     std::shared_ptr<void> heartBeat_;
69c540156cSNicolas Bonnefon 
70c540156cSNicolas Bonnefon     // Private member functions
7191f7c705SNicolas Bonnefon     std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
7291f7c705SNicolas Bonnefon         addFileToDriver( const std::string& );
73c540156cSNicolas Bonnefon     static void removeNotification( WatchTower* watch_tower,
74c540156cSNicolas Bonnefon             std::shared_ptr<void> notification );
75c540156cSNicolas Bonnefon     void run();
7687e05652SNicolas Bonnefon };
7784b2179eSNicolas Bonnefon 
7896bde7d5SNicolas Bonnefon // Class template implementation
7996bde7d5SNicolas Bonnefon 
8096bde7d5SNicolas Bonnefon #include <algorithm>
8196bde7d5SNicolas Bonnefon 
8296bde7d5SNicolas Bonnefon #include <sys/types.h>
8396bde7d5SNicolas Bonnefon #include <sys/stat.h>
8496bde7d5SNicolas Bonnefon #include <unistd.h>
8596bde7d5SNicolas Bonnefon 
8696bde7d5SNicolas Bonnefon #include "log.h"
8796bde7d5SNicolas Bonnefon 
8896bde7d5SNicolas Bonnefon namespace {
8996bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name );
9096bde7d5SNicolas Bonnefon     std::string directory_path( const std::string& path );
9196bde7d5SNicolas Bonnefon };
9296bde7d5SNicolas Bonnefon 
9396bde7d5SNicolas Bonnefon template <typename Driver>
9496bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower()
953104b268SNicolas Bonnefon     : driver_(), thread_(),
9696bde7d5SNicolas Bonnefon     heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
9796bde7d5SNicolas Bonnefon {
9896bde7d5SNicolas Bonnefon     running_ = true;
9996bde7d5SNicolas Bonnefon     thread_ = std::thread( &WatchTower::run, this );
10096bde7d5SNicolas Bonnefon }
10196bde7d5SNicolas Bonnefon 
10296bde7d5SNicolas Bonnefon template <typename Driver>
10396bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower()
10496bde7d5SNicolas Bonnefon {
10596bde7d5SNicolas Bonnefon     running_ = false;
106b0345991SNicolas Bonnefon     driver_.interruptWait();
10796bde7d5SNicolas Bonnefon     thread_.join();
10896bde7d5SNicolas Bonnefon }
10996bde7d5SNicolas Bonnefon 
11096bde7d5SNicolas Bonnefon template <typename Driver>
111*fcaa7557SNicolas Bonnefon void WatchTower<Driver>::setPollingInterval( int interval_ms )
112*fcaa7557SNicolas Bonnefon {
113*fcaa7557SNicolas Bonnefon     polling_interval_ms_ = interval_ms;
114*fcaa7557SNicolas Bonnefon }
115*fcaa7557SNicolas Bonnefon 
116*fcaa7557SNicolas Bonnefon template <typename Driver>
11796bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile(
11896bde7d5SNicolas Bonnefon         const std::string& file_name,
11996bde7d5SNicolas Bonnefon         std::function<void()> notification )
12096bde7d5SNicolas Bonnefon {
121f09fa651SNicolas Bonnefon     // LOG(logDEBUG) << "WatchTower::addFile " << file_name;
12296bde7d5SNicolas Bonnefon 
1233104b268SNicolas Bonnefon     std::weak_ptr<void> weakHeartBeat(heartBeat_);
1243104b268SNicolas Bonnefon 
12596bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( observers_mutex_ );
12696bde7d5SNicolas Bonnefon 
127f09fa651SNicolas Bonnefon     auto existing_observed_file =
12896bde7d5SNicolas Bonnefon         observed_file_list_.searchByName( file_name );
12996bde7d5SNicolas Bonnefon 
13096bde7d5SNicolas Bonnefon     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) );
13196bde7d5SNicolas Bonnefon 
13296bde7d5SNicolas Bonnefon     if ( ! existing_observed_file )
13396bde7d5SNicolas Bonnefon     {
13491f7c705SNicolas Bonnefon         typename Driver::FileId file_id;
135f09fa651SNicolas Bonnefon         typename Driver::SymlinkId symlink_id;
13696bde7d5SNicolas Bonnefon 
13791f7c705SNicolas Bonnefon         std::tie( file_id, symlink_id ) = addFileToDriver( file_name );
13896bde7d5SNicolas Bonnefon         auto new_file = observed_file_list_.addNewObservedFile(
139f09fa651SNicolas Bonnefon                 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) );
14096bde7d5SNicolas Bonnefon 
14196bde7d5SNicolas Bonnefon         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
1428b11848fSNicolas Bonnefon         if ( ! dir ) {
143f09fa651SNicolas Bonnefon             LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name
14496bde7d5SNicolas Bonnefon                 << " not watched, adding...";
1458b11848fSNicolas Bonnefon 
1463104b268SNicolas Bonnefon             dir = observed_file_list_.addWatchedDirectoryForFile( file_name,
1473104b268SNicolas Bonnefon                     [this, weakHeartBeat] (ObservedDir<Driver>* dir) {
1483104b268SNicolas Bonnefon                         if ( auto heart_beat = weakHeartBeat.lock() ) {
1493104b268SNicolas Bonnefon                             driver_.removeDir( dir->dir_id_ );
1503104b268SNicolas Bonnefon                         } } );
15196bde7d5SNicolas Bonnefon 
15296bde7d5SNicolas Bonnefon             dir->dir_id_ = driver_.addDir( dir->path );
1538b11848fSNicolas Bonnefon 
1548b11848fSNicolas Bonnefon             if ( ! dir->dir_id_.valid() ) {
1558b11848fSNicolas Bonnefon                 LOG(logWARNING) << "WatchTower::addFile driver failed to add dir";
1568b11848fSNicolas Bonnefon                 dir = nullptr;
1578b11848fSNicolas Bonnefon             }
1588b11848fSNicolas Bonnefon         }
1598b11848fSNicolas Bonnefon         else {
1608b11848fSNicolas Bonnefon             LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name;
16196bde7d5SNicolas Bonnefon         }
16296bde7d5SNicolas Bonnefon 
163f09fa651SNicolas Bonnefon         // Associate the dir to the file
1648b11848fSNicolas Bonnefon         if ( dir )
16596bde7d5SNicolas Bonnefon             new_file->dir_ = dir;
16696bde7d5SNicolas Bonnefon     }
16796bde7d5SNicolas Bonnefon     else
16896bde7d5SNicolas Bonnefon     {
16996bde7d5SNicolas Bonnefon         existing_observed_file->addCallback( ptr );
17096bde7d5SNicolas Bonnefon     }
17196bde7d5SNicolas Bonnefon 
17296bde7d5SNicolas Bonnefon     // Returns a shared pointer that removes its own entry
17396bde7d5SNicolas Bonnefon     // from the list of watched stuff when it goes out of scope!
17496bde7d5SNicolas Bonnefon     // Uses a custom deleter to do the work.
17596bde7d5SNicolas Bonnefon     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
17696bde7d5SNicolas Bonnefon             if ( auto heart_beat = weakHeartBeat.lock() )
177dc7f5916SNicolas Bonnefon                 WatchTower<Driver>::removeNotification( this, ptr );
17896bde7d5SNicolas Bonnefon             } );
17996bde7d5SNicolas Bonnefon }
18096bde7d5SNicolas Bonnefon 
1813104b268SNicolas Bonnefon template <typename Driver>
1823104b268SNicolas Bonnefon unsigned int WatchTower<Driver>::numberWatchedDirectories() const
1833104b268SNicolas Bonnefon {
1843104b268SNicolas Bonnefon     return observed_file_list_.numberWatchedDirectories();
1853104b268SNicolas Bonnefon }
1863104b268SNicolas Bonnefon 
18796bde7d5SNicolas Bonnefon //
18896bde7d5SNicolas Bonnefon // Private functions
18996bde7d5SNicolas Bonnefon //
19096bde7d5SNicolas Bonnefon 
19191f7c705SNicolas Bonnefon // Add the passed file name to the driver, returning the file and symlink id
19291f7c705SNicolas Bonnefon template <typename Driver>
19391f7c705SNicolas Bonnefon std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
19491f7c705SNicolas Bonnefon WatchTower<Driver>::addFileToDriver( const std::string& file_name )
19591f7c705SNicolas Bonnefon {
19691f7c705SNicolas Bonnefon     typename Driver::SymlinkId symlink_id;
19791f7c705SNicolas Bonnefon     auto file_id = driver_.addFile( file_name );
19891f7c705SNicolas Bonnefon 
19991f7c705SNicolas Bonnefon     if ( isSymLink( file_name ) )
20091f7c705SNicolas Bonnefon     {
20191f7c705SNicolas Bonnefon         // We want to follow the name (as opposed to the inode)
20291f7c705SNicolas Bonnefon         // so we watch the symlink as well.
20391f7c705SNicolas Bonnefon         symlink_id = driver_.addSymlink( file_name );
20491f7c705SNicolas Bonnefon     }
20591f7c705SNicolas Bonnefon 
20691f7c705SNicolas Bonnefon     return std::make_tuple( file_id, symlink_id );
20791f7c705SNicolas Bonnefon }
20891f7c705SNicolas Bonnefon 
20996bde7d5SNicolas Bonnefon // Called by the dtor for a registration object
21096bde7d5SNicolas Bonnefon template <typename Driver>
21196bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification(
21296bde7d5SNicolas Bonnefon         WatchTower* watch_tower, std::shared_ptr<void> notification )
21396bde7d5SNicolas Bonnefon {
21496bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::removeNotification";
21596bde7d5SNicolas Bonnefon 
21696bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
21796bde7d5SNicolas Bonnefon 
21896bde7d5SNicolas Bonnefon     auto file =
21996bde7d5SNicolas Bonnefon         watch_tower->observed_file_list_.removeCallback( notification );
22096bde7d5SNicolas Bonnefon 
22196bde7d5SNicolas Bonnefon     if ( file )
22296bde7d5SNicolas Bonnefon     {
22396bde7d5SNicolas Bonnefon         watch_tower->driver_.removeFile( file->file_id_ );
22496bde7d5SNicolas Bonnefon         watch_tower->driver_.removeSymlink( file->symlink_id_ );
22596bde7d5SNicolas Bonnefon     }
22696bde7d5SNicolas Bonnefon }
22796bde7d5SNicolas Bonnefon 
22896bde7d5SNicolas Bonnefon // Run in its own thread
22996bde7d5SNicolas Bonnefon template <typename Driver>
23096bde7d5SNicolas Bonnefon void WatchTower<Driver>::run()
23196bde7d5SNicolas Bonnefon {
23296bde7d5SNicolas Bonnefon     while ( running_ ) {
2333104b268SNicolas Bonnefon         std::unique_lock<std::mutex> lock( observers_mutex_ );
2343104b268SNicolas Bonnefon 
23591f7c705SNicolas Bonnefon         std::vector<ObservedFile<Driver>*> files_needing_readding;
23691f7c705SNicolas Bonnefon 
23796bde7d5SNicolas Bonnefon         auto files = driver_.waitAndProcessEvents(
238*fcaa7557SNicolas Bonnefon                 &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ );
2393104b268SNicolas Bonnefon         LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned "
24091f7c705SNicolas Bonnefon             << files.size() << " files, " << files_needing_readding.size()
24191f7c705SNicolas Bonnefon             << " needing re-adding";
24291f7c705SNicolas Bonnefon 
24391f7c705SNicolas Bonnefon         for ( auto file: files_needing_readding ) {
24491f7c705SNicolas Bonnefon             // A file 'needing readding' has the same name,
24591f7c705SNicolas Bonnefon             // but probably a different inode, so it needs
24691f7c705SNicolas Bonnefon             // to be readded for some drivers that rely on the
24791f7c705SNicolas Bonnefon             // inode (e.g. inotify)
24891f7c705SNicolas Bonnefon             driver_.removeFile( file->file_id_ );
24991f7c705SNicolas Bonnefon             driver_.removeSymlink( file->symlink_id_ );
25091f7c705SNicolas Bonnefon 
25191f7c705SNicolas Bonnefon             std::tie( file->file_id_, file->symlink_id_ ) =
25291f7c705SNicolas Bonnefon                 addFileToDriver( file->file_name_ );
25391f7c705SNicolas Bonnefon         }
25496bde7d5SNicolas Bonnefon 
25596bde7d5SNicolas Bonnefon         for ( auto file: files ) {
25696bde7d5SNicolas Bonnefon             for ( auto observer: file->callbacks ) {
2573104b268SNicolas Bonnefon                 LOG(logDEBUG) << "WatchTower::run: notifying the client!";
25896bde7d5SNicolas Bonnefon                 // Here we have to cast our generic pointer back to
25996bde7d5SNicolas Bonnefon                 // the function pointer in order to perform the call
26096bde7d5SNicolas Bonnefon                 const std::shared_ptr<std::function<void()>> fptr =
26196bde7d5SNicolas Bonnefon                     std::static_pointer_cast<std::function<void()>>( observer );
26296bde7d5SNicolas Bonnefon                 // The observer is called with the mutex held,
26396bde7d5SNicolas Bonnefon                 // Let's hope it doesn't do anything too funky.
26496bde7d5SNicolas Bonnefon                 (*fptr)();
265*fcaa7557SNicolas Bonnefon 
266*fcaa7557SNicolas Bonnefon                 file->markAsChanged();
267*fcaa7557SNicolas Bonnefon             }
268*fcaa7557SNicolas Bonnefon         }
269*fcaa7557SNicolas Bonnefon 
270*fcaa7557SNicolas Bonnefon         if ( polling_interval_ms_ > 0 ) {
271*fcaa7557SNicolas Bonnefon             // Also call files that have not been called for a while
272*fcaa7557SNicolas Bonnefon             for ( auto file: observed_file_list_ ) {
273*fcaa7557SNicolas Bonnefon                 uint32_t ms_since_last_check =
274*fcaa7557SNicolas Bonnefon                     std::chrono::duration_cast<std::chrono::milliseconds>(
275*fcaa7557SNicolas Bonnefon                             std::chrono::steady_clock::now() - file->timeForLastCheck() ).count();
276*fcaa7557SNicolas Bonnefon                 if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) {
277*fcaa7557SNicolas Bonnefon                     LOG(logDEBUG) << "WatchTower::run: " << file->file_name_;
278*fcaa7557SNicolas Bonnefon                     for ( auto observer: file->callbacks ) {
279*fcaa7557SNicolas Bonnefon                         LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!";
280*fcaa7557SNicolas Bonnefon                         // Here we have to cast our generic pointer back to
281*fcaa7557SNicolas Bonnefon                         // the function pointer in order to perform the call
282*fcaa7557SNicolas Bonnefon                         const std::shared_ptr<std::function<void()>> fptr =
283*fcaa7557SNicolas Bonnefon                             std::static_pointer_cast<std::function<void()>>( observer );
284*fcaa7557SNicolas Bonnefon                         // The observer is called with the mutex held,
285*fcaa7557SNicolas Bonnefon                         // Let's hope it doesn't do anything too funky.
286*fcaa7557SNicolas Bonnefon                         (*fptr)();
287*fcaa7557SNicolas Bonnefon                     }
288*fcaa7557SNicolas Bonnefon                     file->markAsChanged();
289*fcaa7557SNicolas Bonnefon                 }
29096bde7d5SNicolas Bonnefon             }
29196bde7d5SNicolas Bonnefon         }
29296bde7d5SNicolas Bonnefon     }
29396bde7d5SNicolas Bonnefon }
29496bde7d5SNicolas Bonnefon 
29596bde7d5SNicolas Bonnefon namespace {
29696bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name )
29796bde7d5SNicolas Bonnefon     {
298f09fa651SNicolas Bonnefon #ifdef HAVE_SYMLINK
29996bde7d5SNicolas Bonnefon         struct stat buf;
30096bde7d5SNicolas Bonnefon 
30196bde7d5SNicolas Bonnefon         lstat( file_name.c_str(), &buf );
30296bde7d5SNicolas Bonnefon         return ( S_ISLNK(buf.st_mode) );
303f09fa651SNicolas Bonnefon #else
304f09fa651SNicolas Bonnefon         return false;
305f09fa651SNicolas Bonnefon #endif
30696bde7d5SNicolas Bonnefon     }
30796bde7d5SNicolas Bonnefon };
30896bde7d5SNicolas Bonnefon 
30984b2179eSNicolas Bonnefon #endif
310