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