xref: /glogg/src/watchtowerlist.h (revision 3104b26858f76d3848af58b8865d4e7d5735d2f8) !
1c540156cSNicolas Bonnefon #ifndef WATCHTOWERLIST_H
2c540156cSNicolas Bonnefon #define WATCHTOWERLIST_H
3c540156cSNicolas Bonnefon 
4c540156cSNicolas Bonnefon // Utility classes for the WatchTower implementations
5c540156cSNicolas Bonnefon 
6c540156cSNicolas Bonnefon #include <functional>
7c540156cSNicolas Bonnefon #include <string>
8c540156cSNicolas Bonnefon #include <vector>
9c540156cSNicolas Bonnefon #include <map>
10c540156cSNicolas Bonnefon #include <list>
11c540156cSNicolas Bonnefon #include <memory>
12c540156cSNicolas Bonnefon #include <algorithm>
13c540156cSNicolas Bonnefon 
14f09fa651SNicolas Bonnefon #include "log.h"
15b278d183SNicolas Bonnefon 
16c540156cSNicolas Bonnefon // Utility classes
17c540156cSNicolas Bonnefon struct ProtocolInfo {
18c540156cSNicolas Bonnefon     // Win32 notification variables
19c540156cSNicolas Bonnefon     static const int READ_DIR_CHANGE_BUFFER_SIZE = 4096;
20c540156cSNicolas Bonnefon 
21c540156cSNicolas Bonnefon     void* handle_;
22c540156cSNicolas Bonnefon     static const unsigned long buffer_length_ = READ_DIR_CHANGE_BUFFER_SIZE;
23c540156cSNicolas Bonnefon     char buffer_[buffer_length_];
24c540156cSNicolas Bonnefon };
25c540156cSNicolas Bonnefon 
26c540156cSNicolas Bonnefon // List of files and observers
27f09fa651SNicolas Bonnefon template <typename Driver>
28c540156cSNicolas Bonnefon struct ObservedDir {
29c540156cSNicolas Bonnefon     ObservedDir( const std::string this_path ) : path { this_path } {}
30c540156cSNicolas Bonnefon 
31c540156cSNicolas Bonnefon     // Returns the address of the protocol specific informations
32c540156cSNicolas Bonnefon     ProtocolInfo* protocolInfo() { return &protocol_info_; }
33c540156cSNicolas Bonnefon 
34c540156cSNicolas Bonnefon     std::string path;
35f09fa651SNicolas Bonnefon     typename Driver::DirId dir_id_;
36c540156cSNicolas Bonnefon     // Contains data specific to the protocol (inotify/Win32...)
37c540156cSNicolas Bonnefon     ProtocolInfo protocol_info_;
38c540156cSNicolas Bonnefon };
39c540156cSNicolas Bonnefon 
40f09fa651SNicolas Bonnefon template <typename Driver>
41c540156cSNicolas Bonnefon struct ObservedFile {
42c540156cSNicolas Bonnefon     ObservedFile(
43c540156cSNicolas Bonnefon             const std::string& file_name,
44c540156cSNicolas Bonnefon             std::shared_ptr<void> callback,
45f09fa651SNicolas Bonnefon             typename Driver::FileId file_id,
46f09fa651SNicolas Bonnefon             typename Driver::SymlinkId symlink_id )
47b278d183SNicolas Bonnefon         : file_name_( file_name ) {
48c540156cSNicolas Bonnefon         addCallback( callback );
49c540156cSNicolas Bonnefon 
50b278d183SNicolas Bonnefon         file_id_    = file_id;
51b278d183SNicolas Bonnefon         symlink_id_ = symlink_id;
52c540156cSNicolas Bonnefon         dir_        = nullptr;
53c540156cSNicolas Bonnefon     }
54c540156cSNicolas Bonnefon 
55c540156cSNicolas Bonnefon     void addCallback( std::shared_ptr<void> callback ) {
56c540156cSNicolas Bonnefon         callbacks.push_back( callback );
57c540156cSNicolas Bonnefon     }
58c540156cSNicolas Bonnefon 
59c540156cSNicolas Bonnefon     std::string file_name_;
60c540156cSNicolas Bonnefon     // List of callbacks for this file
61c540156cSNicolas Bonnefon     std::vector<std::shared_ptr<void>> callbacks;
62c540156cSNicolas Bonnefon 
63c540156cSNicolas Bonnefon     // watch descriptor for the file itself
64f09fa651SNicolas Bonnefon     typename Driver::FileId file_id_;
65c540156cSNicolas Bonnefon     // watch descriptor for the symlink (if file is a symlink)
66f09fa651SNicolas Bonnefon     typename Driver::SymlinkId symlink_id_;
67c540156cSNicolas Bonnefon 
68c540156cSNicolas Bonnefon     // link to the dir containing the file
69f09fa651SNicolas Bonnefon     std::shared_ptr<ObservedDir<Driver>> dir_;
70c540156cSNicolas Bonnefon };
71c540156cSNicolas Bonnefon 
72c540156cSNicolas Bonnefon // A list of the observed files and directories
73c540156cSNicolas Bonnefon // This class is not thread safe
74f09fa651SNicolas Bonnefon template<typename Driver>
75c540156cSNicolas Bonnefon class ObservedFileList {
76c540156cSNicolas Bonnefon     public:
77*3104b268SNicolas Bonnefon         ObservedFileList() :
78*3104b268SNicolas Bonnefon             heartBeat_ { std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}) }
79*3104b268SNicolas Bonnefon             { }
80c540156cSNicolas Bonnefon         ~ObservedFileList() = default;
81c540156cSNicolas Bonnefon 
82*3104b268SNicolas Bonnefon         // The functions return a pointer to the existing file (if exists)
83*3104b268SNicolas Bonnefon         // but keep ownership of the object.
84f09fa651SNicolas Bonnefon         ObservedFile<Driver>* searchByName( const std::string& file_name );
85f09fa651SNicolas Bonnefon         ObservedFile<Driver>* searchByFileOrSymlinkWd(
86f09fa651SNicolas Bonnefon                 typename Driver::FileId file_id,
87f09fa651SNicolas Bonnefon                 typename Driver::SymlinkId symlink_id );
88f09fa651SNicolas Bonnefon         ObservedFile<Driver>* searchByDirWdAndName(
89f09fa651SNicolas Bonnefon                 typename Driver::DirId id, const char* name );
90c540156cSNicolas Bonnefon 
91*3104b268SNicolas Bonnefon         // Add a new file, the list returns a pointer to the added file,
92*3104b268SNicolas Bonnefon         // but has ownership of the file.
93f09fa651SNicolas Bonnefon         ObservedFile<Driver>* addNewObservedFile( ObservedFile<Driver> new_observed );
94c540156cSNicolas Bonnefon         // Remove a callback, remove and returns the file object if
95c540156cSNicolas Bonnefon         // it was the last callback on this object, nullptr if not.
96c540156cSNicolas Bonnefon         // The caller has ownership of the object.
97f09fa651SNicolas Bonnefon         std::shared_ptr<ObservedFile<Driver>> removeCallback(
98c540156cSNicolas Bonnefon                 std::shared_ptr<void> callback );
99c540156cSNicolas Bonnefon 
100c540156cSNicolas Bonnefon         // Return the watched directory if it is watched, or nullptr
101f09fa651SNicolas Bonnefon         std::shared_ptr<ObservedDir<Driver>> watchedDirectory( const std::string& dir_name );
102*3104b268SNicolas Bonnefon         // Create a new watched directory for dir_name, the client is passed
103*3104b268SNicolas Bonnefon         // shared ownership and have to keep the shared_ptr (the list only
104*3104b268SNicolas Bonnefon         // maintain a weak link).
105*3104b268SNicolas Bonnefon         // The remove notification is called just before the reference to
106*3104b268SNicolas Bonnefon         // the directory is destroyed.
107*3104b268SNicolas Bonnefon         std::shared_ptr<ObservedDir<Driver>> addWatchedDirectory(
108*3104b268SNicolas Bonnefon             const std::string& dir_name,
109*3104b268SNicolas Bonnefon             std::function<void( ObservedDir<Driver>* )> remove_notification );
110c540156cSNicolas Bonnefon 
111*3104b268SNicolas Bonnefon         // Similar to previous functions but extract the name of the
112*3104b268SNicolas Bonnefon         // directory from the file name.
113f09fa651SNicolas Bonnefon         std::shared_ptr<ObservedDir<Driver>> watchedDirectoryForFile( const std::string& file_name );
114*3104b268SNicolas Bonnefon         std::shared_ptr<ObservedDir<Driver>> addWatchedDirectoryForFile( const std::string& file_name,
115*3104b268SNicolas Bonnefon                 std::function<void( ObservedDir<Driver>* )> remove_notification );
116*3104b268SNicolas Bonnefon 
117*3104b268SNicolas Bonnefon         // Removal of directories is done when there is no shared reference
118*3104b268SNicolas Bonnefon         // left (RAII)
119*3104b268SNicolas Bonnefon 
120*3104b268SNicolas Bonnefon         // Number of watched directories (for tests)
121*3104b268SNicolas Bonnefon         unsigned int numberWatchedDirectories() const;
122c540156cSNicolas Bonnefon 
123c540156cSNicolas Bonnefon     private:
124c540156cSNicolas Bonnefon         // List of observed files
125f09fa651SNicolas Bonnefon         std::list<ObservedFile<Driver>> observed_files_;
126c540156cSNicolas Bonnefon 
127c540156cSNicolas Bonnefon         // List of observed dirs, key-ed by name
128f09fa651SNicolas Bonnefon         std::map<std::string, std::weak_ptr<ObservedDir<Driver>>> observed_dirs_;
129c540156cSNicolas Bonnefon 
130c540156cSNicolas Bonnefon         // Map the inotify file (including symlinks) wds to the observed file
131f09fa651SNicolas Bonnefon         std::map<int, ObservedFile<Driver>*> by_file_wd_;
132c540156cSNicolas Bonnefon         // Map the inotify directory wds to the observed files
133f09fa651SNicolas Bonnefon         std::map<int, ObservedFile<Driver>*> by_dir_wd_;
134*3104b268SNicolas Bonnefon 
135*3104b268SNicolas Bonnefon         // Heartbeat
136*3104b268SNicolas Bonnefon         std::shared_ptr<void> heartBeat_;
137*3104b268SNicolas Bonnefon 
138*3104b268SNicolas Bonnefon         // Clean all reference to any expired directory
139*3104b268SNicolas Bonnefon         void cleanRefsToExpiredDirs();
140c540156cSNicolas Bonnefon };
141c540156cSNicolas Bonnefon 
142f09fa651SNicolas Bonnefon namespace {
143f09fa651SNicolas Bonnefon     std::string directory_path( const std::string& path );
144f09fa651SNicolas Bonnefon };
145f09fa651SNicolas Bonnefon 
146f09fa651SNicolas Bonnefon // ObservedFileList class
147f09fa651SNicolas Bonnefon template <typename Driver>
148f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByName(
149f09fa651SNicolas Bonnefon         const std::string& file_name )
150f09fa651SNicolas Bonnefon {
151f09fa651SNicolas Bonnefon     // Look for an existing observer on this file
152f09fa651SNicolas Bonnefon     auto existing_observer = observed_files_.begin();
153f09fa651SNicolas Bonnefon     for ( ; existing_observer != observed_files_.end(); ++existing_observer )
154f09fa651SNicolas Bonnefon     {
155f09fa651SNicolas Bonnefon         if ( existing_observer->file_name_ == file_name )
156f09fa651SNicolas Bonnefon         {
157f09fa651SNicolas Bonnefon             LOG(logDEBUG) << "Found " << file_name;
158f09fa651SNicolas Bonnefon             break;
159f09fa651SNicolas Bonnefon         }
160f09fa651SNicolas Bonnefon     }
161f09fa651SNicolas Bonnefon 
162f09fa651SNicolas Bonnefon     if ( existing_observer != observed_files_.end() )
163f09fa651SNicolas Bonnefon         return &( *existing_observer );
164f09fa651SNicolas Bonnefon     else
165f09fa651SNicolas Bonnefon         return nullptr;
166f09fa651SNicolas Bonnefon }
167f09fa651SNicolas Bonnefon 
168f09fa651SNicolas Bonnefon template <typename Driver>
169f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByFileOrSymlinkWd(
170f09fa651SNicolas Bonnefon         typename Driver::FileId file_id,
171f09fa651SNicolas Bonnefon         typename Driver::SymlinkId symlink_id )
172f09fa651SNicolas Bonnefon {
173f09fa651SNicolas Bonnefon     auto result = find_if( observed_files_.begin(), observed_files_.end(),
174f09fa651SNicolas Bonnefon             [file_id, symlink_id] (ObservedFile<Driver> file) -> bool {
175f09fa651SNicolas Bonnefon                 return ( file_id == file.file_id_ ) ||
176f09fa651SNicolas Bonnefon                     ( symlink_id == file.symlink_id_ );
177f09fa651SNicolas Bonnefon                 } );
178f09fa651SNicolas Bonnefon 
179f09fa651SNicolas Bonnefon     if ( result != observed_files_.end() )
180f09fa651SNicolas Bonnefon         return &( *result );
181f09fa651SNicolas Bonnefon     else
182f09fa651SNicolas Bonnefon         return nullptr;
183f09fa651SNicolas Bonnefon }
184f09fa651SNicolas Bonnefon 
185f09fa651SNicolas Bonnefon template <typename Driver>
186f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::searchByDirWdAndName(
187f09fa651SNicolas Bonnefon         typename Driver::DirId id, const char* name )
188f09fa651SNicolas Bonnefon {
189f09fa651SNicolas Bonnefon     auto dir = find_if( observed_dirs_.begin(), observed_dirs_.end(),
190f09fa651SNicolas Bonnefon             [id] (std::pair<std::string,std::weak_ptr<ObservedDir<Driver>>> d) -> bool {
191f09fa651SNicolas Bonnefon             if ( auto dir = d.second.lock() ) {
192f09fa651SNicolas Bonnefon                 return ( id == dir->dir_id_ );
193f09fa651SNicolas Bonnefon             }
194f09fa651SNicolas Bonnefon             else {
195f09fa651SNicolas Bonnefon                 return false; } } );
196f09fa651SNicolas Bonnefon 
197f09fa651SNicolas Bonnefon     if ( dir != observed_dirs_.end() ) {
198f09fa651SNicolas Bonnefon         std::string path = dir->first + "/" + name;
199f09fa651SNicolas Bonnefon 
200f09fa651SNicolas Bonnefon         // LOG(logDEBUG) << "Testing path: " << path;
201f09fa651SNicolas Bonnefon 
202f09fa651SNicolas Bonnefon         // Looking for the path in the files we are watching
203f09fa651SNicolas Bonnefon         return searchByName( path );
204f09fa651SNicolas Bonnefon     }
205f09fa651SNicolas Bonnefon     else {
206f09fa651SNicolas Bonnefon         return nullptr;
207f09fa651SNicolas Bonnefon     }
208f09fa651SNicolas Bonnefon }
209f09fa651SNicolas Bonnefon 
210f09fa651SNicolas Bonnefon template <typename Driver>
211f09fa651SNicolas Bonnefon ObservedFile<Driver>* ObservedFileList<Driver>::addNewObservedFile(
212f09fa651SNicolas Bonnefon         ObservedFile<Driver> new_observed )
213f09fa651SNicolas Bonnefon {
214f09fa651SNicolas Bonnefon     auto new_file = observed_files_.insert( std::begin( observed_files_ ), new_observed );
215f09fa651SNicolas Bonnefon 
216f09fa651SNicolas Bonnefon     return &( *new_file );
217f09fa651SNicolas Bonnefon }
218f09fa651SNicolas Bonnefon 
219f09fa651SNicolas Bonnefon template <typename Driver>
220f09fa651SNicolas Bonnefon std::shared_ptr<ObservedFile<Driver>> ObservedFileList<Driver>::removeCallback(
221f09fa651SNicolas Bonnefon         std::shared_ptr<void> callback )
222f09fa651SNicolas Bonnefon {
223f09fa651SNicolas Bonnefon     std::shared_ptr<ObservedFile<Driver>> returned_file = nullptr;
224f09fa651SNicolas Bonnefon 
225f09fa651SNicolas Bonnefon     for ( auto observer = begin( observed_files_ );
226f09fa651SNicolas Bonnefon             observer != end( observed_files_ ); )
227f09fa651SNicolas Bonnefon     {
228f09fa651SNicolas Bonnefon         std::vector<std::shared_ptr<void>>& callbacks = observer->callbacks;
229f09fa651SNicolas Bonnefon         callbacks.erase( std::remove(
230f09fa651SNicolas Bonnefon                     std::begin( callbacks ), std::end( callbacks ), callback ),
231f09fa651SNicolas Bonnefon                 std::end( callbacks ) );
232f09fa651SNicolas Bonnefon 
233f09fa651SNicolas Bonnefon         /* See if all notifications have been deleted for this file */
234f09fa651SNicolas Bonnefon         if ( callbacks.empty() ) {
235*3104b268SNicolas Bonnefon             LOG(logDEBUG) << "Empty notification list for " << observer->file_name_
236*3104b268SNicolas Bonnefon                 << ", removing the watched file";
237f09fa651SNicolas Bonnefon             returned_file = std::make_shared<ObservedFile<Driver>>( *observer );
238f09fa651SNicolas Bonnefon             observer = observed_files_.erase( observer );
239f09fa651SNicolas Bonnefon         }
240f09fa651SNicolas Bonnefon         else {
241f09fa651SNicolas Bonnefon             ++observer;
242f09fa651SNicolas Bonnefon         }
243f09fa651SNicolas Bonnefon     }
244f09fa651SNicolas Bonnefon 
245f09fa651SNicolas Bonnefon     return returned_file;
246f09fa651SNicolas Bonnefon }
247f09fa651SNicolas Bonnefon 
248f09fa651SNicolas Bonnefon template <typename Driver>
249f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectory(
250f09fa651SNicolas Bonnefon         const std::string& dir_name )
251f09fa651SNicolas Bonnefon {
252f09fa651SNicolas Bonnefon     std::shared_ptr<ObservedDir<Driver>> dir = nullptr;
253f09fa651SNicolas Bonnefon 
254f09fa651SNicolas Bonnefon     if ( observed_dirs_.find( dir_name ) != std::end( observed_dirs_ ) )
255f09fa651SNicolas Bonnefon         dir = observed_dirs_[ dir_name ].lock();
256f09fa651SNicolas Bonnefon 
257f09fa651SNicolas Bonnefon     return dir;
258f09fa651SNicolas Bonnefon }
259f09fa651SNicolas Bonnefon 
260f09fa651SNicolas Bonnefon template <typename Driver>
261f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectory(
262*3104b268SNicolas Bonnefon         const std::string& dir_name,
263*3104b268SNicolas Bonnefon         std::function<void( ObservedDir<Driver>* )> remove_notification )
264f09fa651SNicolas Bonnefon {
265*3104b268SNicolas Bonnefon     std::weak_ptr<void> weakHeartBeat(heartBeat_);
266*3104b268SNicolas Bonnefon 
267*3104b268SNicolas Bonnefon     std::shared_ptr<ObservedDir<Driver>> dir = {
268*3104b268SNicolas Bonnefon             new ObservedDir<Driver>( dir_name ),
269*3104b268SNicolas Bonnefon             [this, remove_notification, weakHeartBeat] (ObservedDir<Driver>* d) {
270*3104b268SNicolas Bonnefon                 if ( auto heart_beat = weakHeartBeat.lock() ) {
271*3104b268SNicolas Bonnefon                     remove_notification( d );
272*3104b268SNicolas Bonnefon                     cleanRefsToExpiredDirs();
273*3104b268SNicolas Bonnefon                 }
274*3104b268SNicolas Bonnefon                 delete d; } };
275f09fa651SNicolas Bonnefon 
276f09fa651SNicolas Bonnefon     observed_dirs_[ dir_name ] = std::weak_ptr<ObservedDir<Driver>>( dir );
277f09fa651SNicolas Bonnefon 
278f09fa651SNicolas Bonnefon     return dir;
279f09fa651SNicolas Bonnefon }
280f09fa651SNicolas Bonnefon 
281f09fa651SNicolas Bonnefon template <typename Driver>
282f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::watchedDirectoryForFile(
283f09fa651SNicolas Bonnefon         const std::string& file_name )
284f09fa651SNicolas Bonnefon {
285f09fa651SNicolas Bonnefon     return watchedDirectory( directory_path( file_name ) );
286f09fa651SNicolas Bonnefon }
287f09fa651SNicolas Bonnefon 
288f09fa651SNicolas Bonnefon template <typename Driver>
289f09fa651SNicolas Bonnefon std::shared_ptr<ObservedDir<Driver>> ObservedFileList<Driver>::addWatchedDirectoryForFile(
290*3104b268SNicolas Bonnefon         const std::string& file_name,
291*3104b268SNicolas Bonnefon         std::function<void( ObservedDir<Driver>* )> remove_notification )
292f09fa651SNicolas Bonnefon {
293*3104b268SNicolas Bonnefon     return addWatchedDirectory( directory_path( file_name ),
294*3104b268SNicolas Bonnefon             remove_notification );
295*3104b268SNicolas Bonnefon }
296*3104b268SNicolas Bonnefon 
297*3104b268SNicolas Bonnefon template <typename Driver>
298*3104b268SNicolas Bonnefon unsigned int ObservedFileList<Driver>::numberWatchedDirectories() const
299*3104b268SNicolas Bonnefon {
300*3104b268SNicolas Bonnefon     return observed_dirs_.size();
301*3104b268SNicolas Bonnefon }
302*3104b268SNicolas Bonnefon 
303*3104b268SNicolas Bonnefon // Private functions
304*3104b268SNicolas Bonnefon template <typename Driver>
305*3104b268SNicolas Bonnefon void ObservedFileList<Driver>::cleanRefsToExpiredDirs()
306*3104b268SNicolas Bonnefon {
307*3104b268SNicolas Bonnefon     for ( auto it = std::begin( observed_dirs_ );
308*3104b268SNicolas Bonnefon             it != std::end( observed_dirs_ ); )
309*3104b268SNicolas Bonnefon     {
310*3104b268SNicolas Bonnefon         if ( it->second.expired() ) {
311*3104b268SNicolas Bonnefon             LOG(logDEBUG) << "No lock ";
312*3104b268SNicolas Bonnefon             it = observed_dirs_.erase( it );
313*3104b268SNicolas Bonnefon         }
314*3104b268SNicolas Bonnefon         else {
315*3104b268SNicolas Bonnefon             ++it;
316*3104b268SNicolas Bonnefon         }
317*3104b268SNicolas Bonnefon     }
318f09fa651SNicolas Bonnefon }
319f09fa651SNicolas Bonnefon 
320f09fa651SNicolas Bonnefon namespace {
321f09fa651SNicolas Bonnefon     std::string directory_path( const std::string& path )
322f09fa651SNicolas Bonnefon     {
323f09fa651SNicolas Bonnefon         size_t slash_pos = path.rfind( '/' );
324f09fa651SNicolas Bonnefon 
325f09fa651SNicolas Bonnefon #ifdef _WIN32
326f09fa651SNicolas Bonnefon         if ( slash_pos == std::string::npos ) {
327f09fa651SNicolas Bonnefon             slash_pos = path.rfind( '\\' );
328f09fa651SNicolas Bonnefon         }
329f09fa651SNicolas Bonnefon 
330f09fa651SNicolas Bonnefon         // We need to include the final slash on Windows
331f09fa651SNicolas Bonnefon         ++slash_pos;
332f09fa651SNicolas Bonnefon #endif
333f09fa651SNicolas Bonnefon 
334f09fa651SNicolas Bonnefon         return std::string( path, 0, slash_pos );
335f09fa651SNicolas Bonnefon     }
336f09fa651SNicolas Bonnefon };
337c540156cSNicolas Bonnefon #endif
338