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