xref: /glogg/src/watchtower.h (revision 96bde7d5e1c8c71a3ad8ee8e2641c176dc312047)
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.
18*96bde7d5SNicolas Bonnefon 
19*96bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant
2087e05652SNicolas Bonnefon // Registration object to implement RAII
2187e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>;
2287e05652SNicolas Bonnefon 
23*96bde7d5SNicolas Bonnefon template<typename Driver>
24*96bde7d5SNicolas Bonnefon class WatchTower {
25*96bde7d5SNicolas Bonnefon   public:
2687e05652SNicolas Bonnefon     // Create an empty watchtower
27b278d183SNicolas Bonnefon     WatchTower();
2887e05652SNicolas Bonnefon     // Destroy the object
29c540156cSNicolas Bonnefon     ~WatchTower();
3087e05652SNicolas Bonnefon 
3187e05652SNicolas Bonnefon     // Add a file to the notification list. notification will be called when
3287e05652SNicolas Bonnefon     // the file is modified, moved or deleted.
3387e05652SNicolas Bonnefon     // Lifetime of the notification is tied to the Registration object returned.
3487e05652SNicolas Bonnefon     // Note the notification function is called with a mutex held and in a
3587e05652SNicolas Bonnefon     // third party thread, beware of races!
36c540156cSNicolas Bonnefon     Registration addFile( const std::string& file_name,
37c540156cSNicolas Bonnefon             std::function<void()> notification );
38a0936e1eSNicolas Bonnefon 
39a0936e1eSNicolas Bonnefon   private:
40b278d183SNicolas Bonnefon     // The driver (parametrised)
41*96bde7d5SNicolas Bonnefon     Driver driver_;
42a0936e1eSNicolas Bonnefon 
43c540156cSNicolas Bonnefon     // List of files/dirs observed
44c540156cSNicolas Bonnefon     ObservedFileList observed_file_list_;
45a0936e1eSNicolas Bonnefon 
46c540156cSNicolas Bonnefon     // Protects the observed_file_list_
47c540156cSNicolas Bonnefon     std::mutex observers_mutex_;
48c540156cSNicolas Bonnefon 
49c540156cSNicolas Bonnefon     // Thread
50c540156cSNicolas Bonnefon     std::atomic_bool running_;
51c540156cSNicolas Bonnefon     std::thread thread_;
52c540156cSNicolas Bonnefon 
53c540156cSNicolas Bonnefon     // Exist as long as the onject exists, to ensure observers won't try to
54c540156cSNicolas Bonnefon     // call us if we are dead.
55c540156cSNicolas Bonnefon     std::shared_ptr<void> heartBeat_;
56c540156cSNicolas Bonnefon 
57c540156cSNicolas Bonnefon     // Private member functions
58c540156cSNicolas Bonnefon     static void removeNotification( WatchTower* watch_tower,
59c540156cSNicolas Bonnefon             std::shared_ptr<void> notification );
60c540156cSNicolas Bonnefon     void run();
6187e05652SNicolas Bonnefon };
6284b2179eSNicolas Bonnefon 
63*96bde7d5SNicolas Bonnefon // Class template implementation
64*96bde7d5SNicolas Bonnefon 
65*96bde7d5SNicolas Bonnefon #include <algorithm>
66*96bde7d5SNicolas Bonnefon 
67*96bde7d5SNicolas Bonnefon #include <sys/types.h>
68*96bde7d5SNicolas Bonnefon #include <sys/stat.h>
69*96bde7d5SNicolas Bonnefon #include <unistd.h>
70*96bde7d5SNicolas Bonnefon 
71*96bde7d5SNicolas Bonnefon #include "log.h"
72*96bde7d5SNicolas Bonnefon 
73*96bde7d5SNicolas Bonnefon namespace {
74*96bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name );
75*96bde7d5SNicolas Bonnefon     std::string directory_path( const std::string& path );
76*96bde7d5SNicolas Bonnefon };
77*96bde7d5SNicolas Bonnefon 
78*96bde7d5SNicolas Bonnefon template<typename Driver>
79*96bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower()
80*96bde7d5SNicolas Bonnefon     : thread_(), driver_(),
81*96bde7d5SNicolas Bonnefon     heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
82*96bde7d5SNicolas Bonnefon {
83*96bde7d5SNicolas Bonnefon     running_ = true;
84*96bde7d5SNicolas Bonnefon     thread_ = std::thread( &WatchTower::run, this );
85*96bde7d5SNicolas Bonnefon }
86*96bde7d5SNicolas Bonnefon 
87*96bde7d5SNicolas Bonnefon template<typename Driver>
88*96bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower()
89*96bde7d5SNicolas Bonnefon {
90*96bde7d5SNicolas Bonnefon     running_ = false;
91*96bde7d5SNicolas Bonnefon     thread_.join();
92*96bde7d5SNicolas Bonnefon }
93*96bde7d5SNicolas Bonnefon 
94*96bde7d5SNicolas Bonnefon template<typename Driver>
95*96bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile(
96*96bde7d5SNicolas Bonnefon         const std::string& file_name,
97*96bde7d5SNicolas Bonnefon         std::function<void()> notification )
98*96bde7d5SNicolas Bonnefon {
99*96bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::addFile " << file_name;
100*96bde7d5SNicolas Bonnefon 
101*96bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( observers_mutex_ );
102*96bde7d5SNicolas Bonnefon 
103*96bde7d5SNicolas Bonnefon     ObservedFile* existing_observed_file =
104*96bde7d5SNicolas Bonnefon         observed_file_list_.searchByName( file_name );
105*96bde7d5SNicolas Bonnefon 
106*96bde7d5SNicolas Bonnefon     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification ) ) );
107*96bde7d5SNicolas Bonnefon 
108*96bde7d5SNicolas Bonnefon     if ( ! existing_observed_file )
109*96bde7d5SNicolas Bonnefon     {
110*96bde7d5SNicolas Bonnefon         INotifyWatchTowerDriver::SymlinkId symlink_id;
111*96bde7d5SNicolas Bonnefon 
112*96bde7d5SNicolas Bonnefon         auto file_id = driver_.addFile( file_name );
113*96bde7d5SNicolas Bonnefon 
114*96bde7d5SNicolas Bonnefon         if ( isSymLink( file_name ) )
115*96bde7d5SNicolas Bonnefon         {
116*96bde7d5SNicolas Bonnefon             // We want to follow the name (as opposed to the inode)
117*96bde7d5SNicolas Bonnefon             // so we watch the symlink as well.
118*96bde7d5SNicolas Bonnefon             symlink_id = driver_.addSymlink( file_name );
119*96bde7d5SNicolas Bonnefon         }
120*96bde7d5SNicolas Bonnefon 
121*96bde7d5SNicolas Bonnefon         auto new_file = observed_file_list_.addNewObservedFile(
122*96bde7d5SNicolas Bonnefon                 ObservedFile( file_name, ptr, file_id, symlink_id ) );
123*96bde7d5SNicolas Bonnefon 
124*96bde7d5SNicolas Bonnefon         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
125*96bde7d5SNicolas Bonnefon         if ( ! dir )
126*96bde7d5SNicolas Bonnefon         {
127*96bde7d5SNicolas Bonnefon             LOG(logDEBUG) << "INotifyWatchTower::addFile dir for " << file_name
128*96bde7d5SNicolas Bonnefon                 << " not watched, adding...";
129*96bde7d5SNicolas Bonnefon             dir = observed_file_list_.addWatchedDirectoryForFile( file_name );
130*96bde7d5SNicolas Bonnefon 
131*96bde7d5SNicolas Bonnefon             dir->dir_id_ = driver_.addDir( dir->path );
132*96bde7d5SNicolas Bonnefon         }
133*96bde7d5SNicolas Bonnefon 
134*96bde7d5SNicolas Bonnefon         new_file->dir_ = dir;
135*96bde7d5SNicolas Bonnefon     }
136*96bde7d5SNicolas Bonnefon     else
137*96bde7d5SNicolas Bonnefon     {
138*96bde7d5SNicolas Bonnefon         existing_observed_file->addCallback( ptr );
139*96bde7d5SNicolas Bonnefon     }
140*96bde7d5SNicolas Bonnefon 
141*96bde7d5SNicolas Bonnefon     std::weak_ptr<void> weakHeartBeat(heartBeat_);
142*96bde7d5SNicolas Bonnefon 
143*96bde7d5SNicolas Bonnefon     // Returns a shared pointer that removes its own entry
144*96bde7d5SNicolas Bonnefon     // from the list of watched stuff when it goes out of scope!
145*96bde7d5SNicolas Bonnefon     // Uses a custom deleter to do the work.
146*96bde7d5SNicolas Bonnefon     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
147*96bde7d5SNicolas Bonnefon             if ( auto heart_beat = weakHeartBeat.lock() )
148*96bde7d5SNicolas Bonnefon                 removeNotification( this, ptr );
149*96bde7d5SNicolas Bonnefon             } );
150*96bde7d5SNicolas Bonnefon }
151*96bde7d5SNicolas Bonnefon 
152*96bde7d5SNicolas Bonnefon //
153*96bde7d5SNicolas Bonnefon // Private functions
154*96bde7d5SNicolas Bonnefon //
155*96bde7d5SNicolas Bonnefon 
156*96bde7d5SNicolas Bonnefon // Called by the dtor for a registration object
157*96bde7d5SNicolas Bonnefon template<typename Driver>
158*96bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification(
159*96bde7d5SNicolas Bonnefon         WatchTower* watch_tower, std::shared_ptr<void> notification )
160*96bde7d5SNicolas Bonnefon {
161*96bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::removeNotification";
162*96bde7d5SNicolas Bonnefon 
163*96bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
164*96bde7d5SNicolas Bonnefon 
165*96bde7d5SNicolas Bonnefon     auto file =
166*96bde7d5SNicolas Bonnefon         watch_tower->observed_file_list_.removeCallback( notification );
167*96bde7d5SNicolas Bonnefon 
168*96bde7d5SNicolas Bonnefon     if ( file )
169*96bde7d5SNicolas Bonnefon     {
170*96bde7d5SNicolas Bonnefon         watch_tower->driver_.removeFile( file->file_id_ );
171*96bde7d5SNicolas Bonnefon         watch_tower->driver_.removeSymlink( file->symlink_id_ );
172*96bde7d5SNicolas Bonnefon     }
173*96bde7d5SNicolas Bonnefon }
174*96bde7d5SNicolas Bonnefon 
175*96bde7d5SNicolas Bonnefon // Run in its own thread
176*96bde7d5SNicolas Bonnefon template<typename Driver>
177*96bde7d5SNicolas Bonnefon void WatchTower<Driver>::run()
178*96bde7d5SNicolas Bonnefon {
179*96bde7d5SNicolas Bonnefon     while ( running_ ) {
180*96bde7d5SNicolas Bonnefon         auto files = driver_.waitAndProcessEvents(
181*96bde7d5SNicolas Bonnefon                 &observed_file_list_, &observers_mutex_ );
182*96bde7d5SNicolas Bonnefon 
183*96bde7d5SNicolas Bonnefon         for ( auto file: files ) {
184*96bde7d5SNicolas Bonnefon             for ( auto observer: file->callbacks ) {
185*96bde7d5SNicolas Bonnefon                 // Here we have to cast our generic pointer back to
186*96bde7d5SNicolas Bonnefon                 // the function pointer in order to perform the call
187*96bde7d5SNicolas Bonnefon                 const std::shared_ptr<std::function<void()>> fptr =
188*96bde7d5SNicolas Bonnefon                     std::static_pointer_cast<std::function<void()>>( observer );
189*96bde7d5SNicolas Bonnefon                 // The observer is called with the mutex held,
190*96bde7d5SNicolas Bonnefon                 // Let's hope it doesn't do anything too funky.
191*96bde7d5SNicolas Bonnefon                 (*fptr)();
192*96bde7d5SNicolas Bonnefon             }
193*96bde7d5SNicolas Bonnefon         }
194*96bde7d5SNicolas Bonnefon     }
195*96bde7d5SNicolas Bonnefon }
196*96bde7d5SNicolas Bonnefon 
197*96bde7d5SNicolas Bonnefon namespace {
198*96bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name )
199*96bde7d5SNicolas Bonnefon     {
200*96bde7d5SNicolas Bonnefon         struct stat buf;
201*96bde7d5SNicolas Bonnefon 
202*96bde7d5SNicolas Bonnefon         lstat( file_name.c_str(), &buf );
203*96bde7d5SNicolas Bonnefon         return ( S_ISLNK(buf.st_mode) );
204*96bde7d5SNicolas Bonnefon     }
205*96bde7d5SNicolas Bonnefon };
206*96bde7d5SNicolas Bonnefon 
20784b2179eSNicolas Bonnefon #endif
208