xref: /glogg/src/watchtower.h (revision 3104b26858f76d3848af58b8865d4e7d5735d2f8)
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 #ifdef HAS_TEMPLATE_ALIASES
21 using Registration = std::shared_ptr<void>;
22 #else
23 typedef std::shared_ptr<void> Registration;
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     // Number of watched directories (for tests)
43     unsigned int numberWatchedDirectories() const;
44 
45   private:
46     // The driver (parametrised)
47     Driver driver_;
48 
49     // List of files/dirs observed
50     ObservedFileList<Driver> observed_file_list_;
51 
52     // Protects the observed_file_list_
53     std::mutex observers_mutex_;
54 
55     // Thread
56     std::atomic_bool running_;
57     std::thread thread_;
58 
59     // Exist as long as the onject exists, to ensure observers won't try to
60     // call us if we are dead.
61     std::shared_ptr<void> heartBeat_;
62 
63     // Private member functions
64     static void removeNotification( WatchTower* watch_tower,
65             std::shared_ptr<void> notification );
66     void run();
67 };
68 
69 // Class template implementation
70 
71 #include <algorithm>
72 
73 #include <sys/types.h>
74 #include <sys/stat.h>
75 #include <unistd.h>
76 
77 #include "log.h"
78 
79 namespace {
80     bool isSymLink( const std::string& file_name );
81     std::string directory_path( const std::string& path );
82 };
83 
84 template <typename Driver>
85 WatchTower<Driver>::WatchTower()
86     : driver_(), thread_(),
87     heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
88 {
89     running_ = true;
90     thread_ = std::thread( &WatchTower::run, this );
91 }
92 
93 template <typename Driver>
94 WatchTower<Driver>::~WatchTower()
95 {
96     running_ = false;
97     driver_.interruptWait();
98     thread_.join();
99 }
100 
101 template <typename Driver>
102 Registration WatchTower<Driver>::addFile(
103         const std::string& file_name,
104         std::function<void()> notification )
105 {
106     // LOG(logDEBUG) << "WatchTower::addFile " << file_name;
107 
108     std::weak_ptr<void> weakHeartBeat(heartBeat_);
109 
110     std::lock_guard<std::mutex> lock( observers_mutex_ );
111 
112     auto existing_observed_file =
113         observed_file_list_.searchByName( file_name );
114 
115     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) );
116 
117     if ( ! existing_observed_file )
118     {
119         typename Driver::SymlinkId symlink_id;
120 
121         auto file_id = driver_.addFile( file_name );
122 
123         if ( isSymLink( file_name ) )
124         {
125             // We want to follow the name (as opposed to the inode)
126             // so we watch the symlink as well.
127             symlink_id = driver_.addSymlink( file_name );
128         }
129 
130         auto new_file = observed_file_list_.addNewObservedFile(
131                 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) );
132 
133         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
134         if ( ! dir )
135         {
136             LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name
137                 << " not watched, adding...";
138             dir = observed_file_list_.addWatchedDirectoryForFile( file_name,
139                     [this, weakHeartBeat] (ObservedDir<Driver>* dir) {
140                         if ( auto heart_beat = weakHeartBeat.lock() ) {
141                             driver_.removeDir( dir->dir_id_ );
142                         } } );
143 
144             dir->dir_id_ = driver_.addDir( dir->path );
145         }
146 
147         // Associate the dir to the file
148         new_file->dir_ = dir;
149 
150         LOG(logDEBUG) << "dir ref count is " << dir.use_count();
151     }
152     else
153     {
154         existing_observed_file->addCallback( ptr );
155     }
156 
157     // Returns a shared pointer that removes its own entry
158     // from the list of watched stuff when it goes out of scope!
159     // Uses a custom deleter to do the work.
160     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
161             if ( auto heart_beat = weakHeartBeat.lock() )
162                 WatchTower<Driver>::removeNotification( this, ptr );
163             } );
164 }
165 
166 template <typename Driver>
167 unsigned int WatchTower<Driver>::numberWatchedDirectories() const
168 {
169     return observed_file_list_.numberWatchedDirectories();
170 }
171 
172 //
173 // Private functions
174 //
175 
176 // Called by the dtor for a registration object
177 template <typename Driver>
178 void WatchTower<Driver>::removeNotification(
179         WatchTower* watch_tower, std::shared_ptr<void> notification )
180 {
181     LOG(logDEBUG) << "WatchTower::removeNotification";
182 
183     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
184 
185     auto file =
186         watch_tower->observed_file_list_.removeCallback( notification );
187 
188     if ( file )
189     {
190         watch_tower->driver_.removeFile( file->file_id_ );
191         watch_tower->driver_.removeSymlink( file->symlink_id_ );
192     }
193 }
194 
195 // Run in its own thread
196 template <typename Driver>
197 void WatchTower<Driver>::run()
198 {
199     while ( running_ ) {
200         std::unique_lock<std::mutex> lock( observers_mutex_ );
201 
202         auto files = driver_.waitAndProcessEvents(
203                 &observed_file_list_, &lock );
204         LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned "
205             << files.size() << " files.";
206 
207         for ( auto file: files ) {
208             for ( auto observer: file->callbacks ) {
209                 LOG(logDEBUG) << "WatchTower::run: notifying the client!";
210                 // Here we have to cast our generic pointer back to
211                 // the function pointer in order to perform the call
212                 const std::shared_ptr<std::function<void()>> fptr =
213                     std::static_pointer_cast<std::function<void()>>( observer );
214                 // The observer is called with the mutex held,
215                 // Let's hope it doesn't do anything too funky.
216                 (*fptr)();
217             }
218         }
219     }
220 }
221 
222 namespace {
223     bool isSymLink( const std::string& file_name )
224     {
225 #ifdef HAVE_SYMLINK
226         struct stat buf;
227 
228         lstat( file_name.c_str(), &buf );
229         return ( S_ISLNK(buf.st_mode) );
230 #else
231         return false;
232 #endif
233     }
234 };
235 
236 #endif
237