xref: /glogg/src/watchtower.h (revision b0345991179d693b315dfae1d66ad21aa3989113)
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     driver_.interruptWait();
96     thread_.join();
97 }
98 
99 template<typename Driver>
100 Registration WatchTower<Driver>::addFile(
101         const std::string& file_name,
102         std::function<void()> notification )
103 {
104     LOG(logDEBUG) << "WatchTower::addFile " << file_name;
105 
106     std::lock_guard<std::mutex> lock( observers_mutex_ );
107 
108     ObservedFile* existing_observed_file =
109         observed_file_list_.searchByName( file_name );
110 
111     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification ) ) );
112 
113     if ( ! existing_observed_file )
114     {
115         INotifyWatchTowerDriver::SymlinkId symlink_id;
116 
117         auto file_id = driver_.addFile( file_name );
118 
119         if ( isSymLink( file_name ) )
120         {
121             // We want to follow the name (as opposed to the inode)
122             // so we watch the symlink as well.
123             symlink_id = driver_.addSymlink( file_name );
124         }
125 
126         auto new_file = observed_file_list_.addNewObservedFile(
127                 ObservedFile( file_name, ptr, file_id, symlink_id ) );
128 
129         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
130         if ( ! dir )
131         {
132             LOG(logDEBUG) << "INotifyWatchTower::addFile dir for " << file_name
133                 << " not watched, adding...";
134             dir = observed_file_list_.addWatchedDirectoryForFile( file_name );
135 
136             dir->dir_id_ = driver_.addDir( dir->path );
137         }
138 
139         new_file->dir_ = dir;
140     }
141     else
142     {
143         existing_observed_file->addCallback( ptr );
144     }
145 
146     std::weak_ptr<void> weakHeartBeat(heartBeat_);
147 
148     // Returns a shared pointer that removes its own entry
149     // from the list of watched stuff when it goes out of scope!
150     // Uses a custom deleter to do the work.
151     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
152             if ( auto heart_beat = weakHeartBeat.lock() )
153                 WatchTower<Driver>::removeNotification( this, ptr );
154             } );
155 }
156 
157 //
158 // Private functions
159 //
160 
161 // Called by the dtor for a registration object
162 template<typename Driver>
163 void WatchTower<Driver>::removeNotification(
164         WatchTower* watch_tower, std::shared_ptr<void> notification )
165 {
166     LOG(logDEBUG) << "WatchTower::removeNotification";
167 
168     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
169 
170     auto file =
171         watch_tower->observed_file_list_.removeCallback( notification );
172 
173     if ( file )
174     {
175         watch_tower->driver_.removeFile( file->file_id_ );
176         watch_tower->driver_.removeSymlink( file->symlink_id_ );
177     }
178 }
179 
180 // Run in its own thread
181 template<typename Driver>
182 void WatchTower<Driver>::run()
183 {
184     while ( running_ ) {
185         auto files = driver_.waitAndProcessEvents(
186                 &observed_file_list_, &observers_mutex_ );
187 
188         for ( auto file: files ) {
189             for ( auto observer: file->callbacks ) {
190                 // Here we have to cast our generic pointer back to
191                 // the function pointer in order to perform the call
192                 const std::shared_ptr<std::function<void()>> fptr =
193                     std::static_pointer_cast<std::function<void()>>( observer );
194                 // The observer is called with the mutex held,
195                 // Let's hope it doesn't do anything too funky.
196                 (*fptr)();
197             }
198         }
199     }
200 }
201 
202 namespace {
203     bool isSymLink( const std::string& file_name )
204     {
205         struct stat buf;
206 
207         lstat( file_name.c_str(), &buf );
208         return ( S_ISLNK(buf.st_mode) );
209     }
210 };
211 
212 #endif
213