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