xref: /glogg/src/watchtower.h (revision f869e41d2c129cd0f2f3eccb5e9d0d80a5998201)
1aceb79b2SNicolas Bonnefon /*
2aceb79b2SNicolas Bonnefon  * Copyright (C) 2015 Nicolas Bonnefon and other contributors
3aceb79b2SNicolas Bonnefon  *
4aceb79b2SNicolas Bonnefon  * This file is part of glogg.
5aceb79b2SNicolas Bonnefon  *
6aceb79b2SNicolas Bonnefon  * glogg is free software: you can redistribute it and/or modify
7aceb79b2SNicolas Bonnefon  * it under the terms of the GNU General Public License as published by
8aceb79b2SNicolas Bonnefon  * the Free Software Foundation, either version 3 of the License, or
9aceb79b2SNicolas Bonnefon  * (at your option) any later version.
10aceb79b2SNicolas Bonnefon  *
11aceb79b2SNicolas Bonnefon  * glogg is distributed in the hope that it will be useful,
12aceb79b2SNicolas Bonnefon  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13aceb79b2SNicolas Bonnefon  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14aceb79b2SNicolas Bonnefon  * GNU General Public License for more details.
15aceb79b2SNicolas Bonnefon  *
16aceb79b2SNicolas Bonnefon  * You should have received a copy of the GNU General Public License
17aceb79b2SNicolas Bonnefon  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
18aceb79b2SNicolas Bonnefon  */
19aceb79b2SNicolas Bonnefon 
2084b2179eSNicolas Bonnefon #ifndef WATCHTOWER_H
2184b2179eSNicolas Bonnefon #define WATCHTOWER_H
2284b2179eSNicolas Bonnefon 
23f09fa651SNicolas Bonnefon #include "config.h"
24f09fa651SNicolas Bonnefon 
2587e05652SNicolas Bonnefon #include <memory>
26c540156cSNicolas Bonnefon #include <atomic>
27c540156cSNicolas Bonnefon #include <thread>
28c540156cSNicolas Bonnefon #include <mutex>
2987e05652SNicolas Bonnefon 
30c540156cSNicolas Bonnefon #include "watchtowerlist.h"
31b278d183SNicolas Bonnefon 
32c540156cSNicolas Bonnefon // Allow the client to register for notification on an arbitrary number
33c540156cSNicolas Bonnefon // of files. It will be notified to any change (creation/deletion/modification)
34c540156cSNicolas Bonnefon // on those files.
35c540156cSNicolas Bonnefon // It is passed a platform specific driver.
3696bde7d5SNicolas Bonnefon 
3796bde7d5SNicolas Bonnefon // FIXME: Where to put that so it is not dependant
3887e05652SNicolas Bonnefon // Registration object to implement RAII
3958f443c7SNicolas Bonnefon #ifdef HAS_TEMPLATE_ALIASES
4087e05652SNicolas Bonnefon using Registration = std::shared_ptr<void>;
4158f443c7SNicolas Bonnefon #else
4258f443c7SNicolas Bonnefon typedef std::shared_ptr<void> Registration;
43dc7f5916SNicolas Bonnefon #endif
4487e05652SNicolas Bonnefon 
4596bde7d5SNicolas Bonnefon template<typename Driver>
4696bde7d5SNicolas Bonnefon class WatchTower {
4796bde7d5SNicolas Bonnefon   public:
4887e05652SNicolas Bonnefon     // Create an empty watchtower
49b278d183SNicolas Bonnefon     WatchTower();
5087e05652SNicolas Bonnefon     // Destroy the object
51c540156cSNicolas Bonnefon     ~WatchTower();
5287e05652SNicolas Bonnefon 
53fcaa7557SNicolas Bonnefon     // Set the polling interval (in ms)
54fcaa7557SNicolas Bonnefon     // 0 disables polling and is the default
55481c483cSSergei Dyshel     void setPollingInterval( uint32_t interval_ms );
56fcaa7557SNicolas Bonnefon 
5787e05652SNicolas Bonnefon     // Add a file to the notification list. notification will be called when
5887e05652SNicolas Bonnefon     // the file is modified, moved or deleted.
5987e05652SNicolas Bonnefon     // Lifetime of the notification is tied to the Registration object returned.
6087e05652SNicolas Bonnefon     // Note the notification function is called with a mutex held and in a
6187e05652SNicolas Bonnefon     // third party thread, beware of races!
62c540156cSNicolas Bonnefon     Registration addFile( const std::string& file_name,
63c540156cSNicolas Bonnefon             std::function<void()> notification );
64a0936e1eSNicolas Bonnefon 
653104b268SNicolas Bonnefon     // Number of watched directories (for tests)
663104b268SNicolas Bonnefon     unsigned int numberWatchedDirectories() const;
673104b268SNicolas Bonnefon 
68a0936e1eSNicolas Bonnefon   private:
69b278d183SNicolas Bonnefon     // The driver (parametrised)
7096bde7d5SNicolas Bonnefon     Driver driver_;
71a0936e1eSNicolas Bonnefon 
72c540156cSNicolas Bonnefon     // List of files/dirs observed
73f09fa651SNicolas Bonnefon     ObservedFileList<Driver> observed_file_list_;
74a0936e1eSNicolas Bonnefon 
75c540156cSNicolas Bonnefon     // Protects the observed_file_list_
76c540156cSNicolas Bonnefon     std::mutex observers_mutex_;
77c540156cSNicolas Bonnefon 
78fcaa7557SNicolas Bonnefon     // Polling interval (0 disables polling)
79fcaa7557SNicolas Bonnefon     uint32_t polling_interval_ms_ = 0;
80fcaa7557SNicolas Bonnefon 
81c540156cSNicolas Bonnefon     // Thread
82c540156cSNicolas Bonnefon     std::atomic_bool running_;
83c540156cSNicolas Bonnefon     std::thread thread_;
84c540156cSNicolas Bonnefon 
85c540156cSNicolas Bonnefon     // Exist as long as the onject exists, to ensure observers won't try to
86c540156cSNicolas Bonnefon     // call us if we are dead.
87c540156cSNicolas Bonnefon     std::shared_ptr<void> heartBeat_;
88c540156cSNicolas Bonnefon 
89c540156cSNicolas Bonnefon     // Private member functions
9091f7c705SNicolas Bonnefon     std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
9191f7c705SNicolas Bonnefon         addFileToDriver( const std::string& );
92c540156cSNicolas Bonnefon     static void removeNotification( WatchTower* watch_tower,
93c540156cSNicolas Bonnefon             std::shared_ptr<void> notification );
94c540156cSNicolas Bonnefon     void run();
9587e05652SNicolas Bonnefon };
9684b2179eSNicolas Bonnefon 
9796bde7d5SNicolas Bonnefon // Class template implementation
9896bde7d5SNicolas Bonnefon 
9996bde7d5SNicolas Bonnefon #include <algorithm>
10096bde7d5SNicolas Bonnefon 
10196bde7d5SNicolas Bonnefon #include <sys/types.h>
10296bde7d5SNicolas Bonnefon #include <sys/stat.h>
10396bde7d5SNicolas Bonnefon #include <unistd.h>
10496bde7d5SNicolas Bonnefon 
10596bde7d5SNicolas Bonnefon #include "log.h"
10696bde7d5SNicolas Bonnefon 
10796bde7d5SNicolas Bonnefon namespace {
10896bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name );
10996bde7d5SNicolas Bonnefon     std::string directory_path( const std::string& path );
11096bde7d5SNicolas Bonnefon };
11196bde7d5SNicolas Bonnefon 
11296bde7d5SNicolas Bonnefon template <typename Driver>
WatchTower()11396bde7d5SNicolas Bonnefon WatchTower<Driver>::WatchTower()
1143104b268SNicolas Bonnefon     : driver_(), thread_(),
11596bde7d5SNicolas Bonnefon     heartBeat_(std::shared_ptr<void>((void*) 0xDEADC0DE, [] (void*) {}))
11696bde7d5SNicolas Bonnefon {
11796bde7d5SNicolas Bonnefon     running_ = true;
11896bde7d5SNicolas Bonnefon     thread_ = std::thread( &WatchTower::run, this );
11996bde7d5SNicolas Bonnefon }
12096bde7d5SNicolas Bonnefon 
12196bde7d5SNicolas Bonnefon template <typename Driver>
~WatchTower()12296bde7d5SNicolas Bonnefon WatchTower<Driver>::~WatchTower()
12396bde7d5SNicolas Bonnefon {
12496bde7d5SNicolas Bonnefon     running_ = false;
125b0345991SNicolas Bonnefon     driver_.interruptWait();
12696bde7d5SNicolas Bonnefon     thread_.join();
12796bde7d5SNicolas Bonnefon }
12896bde7d5SNicolas Bonnefon 
12996bde7d5SNicolas Bonnefon template <typename Driver>
setPollingInterval(uint32_t interval_ms)130481c483cSSergei Dyshel void WatchTower<Driver>::setPollingInterval( uint32_t interval_ms )
131fcaa7557SNicolas Bonnefon {
132aceb79b2SNicolas Bonnefon     if ( polling_interval_ms_ != interval_ms ) {
133fcaa7557SNicolas Bonnefon         polling_interval_ms_ = interval_ms;
134aceb79b2SNicolas Bonnefon         // Break out of the wait
135aceb79b2SNicolas Bonnefon         if ( polling_interval_ms_ > 0 )
136aceb79b2SNicolas Bonnefon             driver_.interruptWait();
137aceb79b2SNicolas Bonnefon     }
138fcaa7557SNicolas Bonnefon }
139fcaa7557SNicolas Bonnefon 
140fcaa7557SNicolas Bonnefon template <typename Driver>
addFile(const std::string & file_name,std::function<void ()> notification)14196bde7d5SNicolas Bonnefon Registration WatchTower<Driver>::addFile(
14296bde7d5SNicolas Bonnefon         const std::string& file_name,
14396bde7d5SNicolas Bonnefon         std::function<void()> notification )
14496bde7d5SNicolas Bonnefon {
145*f869e41dSNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::addFile " << file_name;
14696bde7d5SNicolas Bonnefon 
1473104b268SNicolas Bonnefon     std::weak_ptr<void> weakHeartBeat(heartBeat_);
1483104b268SNicolas Bonnefon 
14996bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( observers_mutex_ );
15096bde7d5SNicolas Bonnefon 
151f09fa651SNicolas Bonnefon     auto existing_observed_file =
15296bde7d5SNicolas Bonnefon         observed_file_list_.searchByName( file_name );
15396bde7d5SNicolas Bonnefon 
15496bde7d5SNicolas Bonnefon     std::shared_ptr<std::function<void()>> ptr( new std::function<void()>(std::move( notification )) );
15596bde7d5SNicolas Bonnefon 
15696bde7d5SNicolas Bonnefon     if ( ! existing_observed_file )
15796bde7d5SNicolas Bonnefon     {
15891f7c705SNicolas Bonnefon         typename Driver::FileId file_id;
159f09fa651SNicolas Bonnefon         typename Driver::SymlinkId symlink_id;
16096bde7d5SNicolas Bonnefon 
16191f7c705SNicolas Bonnefon         std::tie( file_id, symlink_id ) = addFileToDriver( file_name );
16296bde7d5SNicolas Bonnefon         auto new_file = observed_file_list_.addNewObservedFile(
163f09fa651SNicolas Bonnefon                 ObservedFile<Driver>( file_name, ptr, file_id, symlink_id ) );
16496bde7d5SNicolas Bonnefon 
16596bde7d5SNicolas Bonnefon         auto dir = observed_file_list_.watchedDirectoryForFile( file_name );
1668b11848fSNicolas Bonnefon         if ( ! dir ) {
167f09fa651SNicolas Bonnefon             LOG(logDEBUG) << "WatchTower::addFile dir for " << file_name
16896bde7d5SNicolas Bonnefon                 << " not watched, adding...";
1698b11848fSNicolas Bonnefon 
1703104b268SNicolas Bonnefon             dir = observed_file_list_.addWatchedDirectoryForFile( file_name,
1713104b268SNicolas Bonnefon                     [this, weakHeartBeat] (ObservedDir<Driver>* dir) {
1723104b268SNicolas Bonnefon                         if ( auto heart_beat = weakHeartBeat.lock() ) {
1733104b268SNicolas Bonnefon                             driver_.removeDir( dir->dir_id_ );
1743104b268SNicolas Bonnefon                         } } );
17596bde7d5SNicolas Bonnefon 
17696bde7d5SNicolas Bonnefon             dir->dir_id_ = driver_.addDir( dir->path );
1778b11848fSNicolas Bonnefon 
1788b11848fSNicolas Bonnefon             if ( ! dir->dir_id_.valid() ) {
1798b11848fSNicolas Bonnefon                 LOG(logWARNING) << "WatchTower::addFile driver failed to add dir";
1808b11848fSNicolas Bonnefon                 dir = nullptr;
1818b11848fSNicolas Bonnefon             }
1828b11848fSNicolas Bonnefon         }
1838b11848fSNicolas Bonnefon         else {
1848b11848fSNicolas Bonnefon             LOG(logDEBUG) << "WatchTower::addFile Found exisiting watch for dir " << file_name;
18596bde7d5SNicolas Bonnefon         }
18696bde7d5SNicolas Bonnefon 
187f09fa651SNicolas Bonnefon         // Associate the dir to the file
1888b11848fSNicolas Bonnefon         if ( dir )
18996bde7d5SNicolas Bonnefon             new_file->dir_ = dir;
19096bde7d5SNicolas Bonnefon     }
19196bde7d5SNicolas Bonnefon     else
19296bde7d5SNicolas Bonnefon     {
193*f869e41dSNicolas Bonnefon         LOG(logDEBUG) << "WatchTower::addFile add extra callback for already monitored " << file_name;
19496bde7d5SNicolas Bonnefon         existing_observed_file->addCallback( ptr );
19596bde7d5SNicolas Bonnefon     }
19696bde7d5SNicolas Bonnefon 
19796bde7d5SNicolas Bonnefon     // Returns a shared pointer that removes its own entry
19896bde7d5SNicolas Bonnefon     // from the list of watched stuff when it goes out of scope!
19996bde7d5SNicolas Bonnefon     // Uses a custom deleter to do the work.
20096bde7d5SNicolas Bonnefon     return std::shared_ptr<void>( 0x0, [this, ptr, weakHeartBeat] (void*) {
20196bde7d5SNicolas Bonnefon             if ( auto heart_beat = weakHeartBeat.lock() )
202dc7f5916SNicolas Bonnefon                 WatchTower<Driver>::removeNotification( this, ptr );
20396bde7d5SNicolas Bonnefon             } );
20496bde7d5SNicolas Bonnefon }
20596bde7d5SNicolas Bonnefon 
2063104b268SNicolas Bonnefon template <typename Driver>
numberWatchedDirectories()2073104b268SNicolas Bonnefon unsigned int WatchTower<Driver>::numberWatchedDirectories() const
2083104b268SNicolas Bonnefon {
2093104b268SNicolas Bonnefon     return observed_file_list_.numberWatchedDirectories();
2103104b268SNicolas Bonnefon }
2113104b268SNicolas Bonnefon 
21296bde7d5SNicolas Bonnefon //
21396bde7d5SNicolas Bonnefon // Private functions
21496bde7d5SNicolas Bonnefon //
21596bde7d5SNicolas Bonnefon 
21691f7c705SNicolas Bonnefon // Add the passed file name to the driver, returning the file and symlink id
21791f7c705SNicolas Bonnefon template <typename Driver>
21891f7c705SNicolas Bonnefon std::tuple<typename Driver::FileId, typename Driver::SymlinkId>
addFileToDriver(const std::string & file_name)21991f7c705SNicolas Bonnefon WatchTower<Driver>::addFileToDriver( const std::string& file_name )
22091f7c705SNicolas Bonnefon {
22191f7c705SNicolas Bonnefon     typename Driver::SymlinkId symlink_id;
22291f7c705SNicolas Bonnefon     auto file_id = driver_.addFile( file_name );
22391f7c705SNicolas Bonnefon 
22491f7c705SNicolas Bonnefon     if ( isSymLink( file_name ) )
22591f7c705SNicolas Bonnefon     {
22691f7c705SNicolas Bonnefon         // We want to follow the name (as opposed to the inode)
22791f7c705SNicolas Bonnefon         // so we watch the symlink as well.
22891f7c705SNicolas Bonnefon         symlink_id = driver_.addSymlink( file_name );
22991f7c705SNicolas Bonnefon     }
23091f7c705SNicolas Bonnefon 
23191f7c705SNicolas Bonnefon     return std::make_tuple( file_id, symlink_id );
23291f7c705SNicolas Bonnefon }
23391f7c705SNicolas Bonnefon 
23496bde7d5SNicolas Bonnefon // Called by the dtor for a registration object
23596bde7d5SNicolas Bonnefon template <typename Driver>
removeNotification(WatchTower * watch_tower,std::shared_ptr<void> notification)23696bde7d5SNicolas Bonnefon void WatchTower<Driver>::removeNotification(
23796bde7d5SNicolas Bonnefon         WatchTower* watch_tower, std::shared_ptr<void> notification )
23896bde7d5SNicolas Bonnefon {
23996bde7d5SNicolas Bonnefon     LOG(logDEBUG) << "WatchTower::removeNotification";
24096bde7d5SNicolas Bonnefon 
24196bde7d5SNicolas Bonnefon     std::lock_guard<std::mutex> lock( watch_tower->observers_mutex_ );
24296bde7d5SNicolas Bonnefon 
24396bde7d5SNicolas Bonnefon     auto file =
24496bde7d5SNicolas Bonnefon         watch_tower->observed_file_list_.removeCallback( notification );
24596bde7d5SNicolas Bonnefon 
24696bde7d5SNicolas Bonnefon     if ( file )
24796bde7d5SNicolas Bonnefon     {
248*f869e41dSNicolas Bonnefon         LOG(logDEBUG) << "WatchTower::removeNotification - remove the file";
24996bde7d5SNicolas Bonnefon         watch_tower->driver_.removeFile( file->file_id_ );
25096bde7d5SNicolas Bonnefon         watch_tower->driver_.removeSymlink( file->symlink_id_ );
25196bde7d5SNicolas Bonnefon     }
25296bde7d5SNicolas Bonnefon }
25396bde7d5SNicolas Bonnefon 
25496bde7d5SNicolas Bonnefon // Run in its own thread
25596bde7d5SNicolas Bonnefon template <typename Driver>
run()25696bde7d5SNicolas Bonnefon void WatchTower<Driver>::run()
25796bde7d5SNicolas Bonnefon {
25896bde7d5SNicolas Bonnefon     while ( running_ ) {
2593104b268SNicolas Bonnefon         std::unique_lock<std::mutex> lock( observers_mutex_ );
2603104b268SNicolas Bonnefon 
26191f7c705SNicolas Bonnefon         std::vector<ObservedFile<Driver>*> files_needing_readding;
26291f7c705SNicolas Bonnefon 
26396bde7d5SNicolas Bonnefon         auto files = driver_.waitAndProcessEvents(
264fcaa7557SNicolas Bonnefon                 &observed_file_list_, &lock, &files_needing_readding, polling_interval_ms_ );
2653104b268SNicolas Bonnefon         LOG(logDEBUG) << "WatchTower::run: waitAndProcessEvents returned "
26691f7c705SNicolas Bonnefon             << files.size() << " files, " << files_needing_readding.size()
26791f7c705SNicolas Bonnefon             << " needing re-adding";
26891f7c705SNicolas Bonnefon 
26991f7c705SNicolas Bonnefon         for ( auto file: files_needing_readding ) {
27091f7c705SNicolas Bonnefon             // A file 'needing readding' has the same name,
27191f7c705SNicolas Bonnefon             // but probably a different inode, so it needs
27291f7c705SNicolas Bonnefon             // to be readded for some drivers that rely on the
27391f7c705SNicolas Bonnefon             // inode (e.g. inotify)
27491f7c705SNicolas Bonnefon             driver_.removeFile( file->file_id_ );
27591f7c705SNicolas Bonnefon             driver_.removeSymlink( file->symlink_id_ );
27691f7c705SNicolas Bonnefon 
27791f7c705SNicolas Bonnefon             std::tie( file->file_id_, file->symlink_id_ ) =
27891f7c705SNicolas Bonnefon                 addFileToDriver( file->file_name_ );
27991f7c705SNicolas Bonnefon         }
28096bde7d5SNicolas Bonnefon 
28196bde7d5SNicolas Bonnefon         for ( auto file: files ) {
28296bde7d5SNicolas Bonnefon             for ( auto observer: file->callbacks ) {
2833104b268SNicolas Bonnefon                 LOG(logDEBUG) << "WatchTower::run: notifying the client!";
28496bde7d5SNicolas Bonnefon                 // Here we have to cast our generic pointer back to
28596bde7d5SNicolas Bonnefon                 // the function pointer in order to perform the call
28696bde7d5SNicolas Bonnefon                 const std::shared_ptr<std::function<void()>> fptr =
28796bde7d5SNicolas Bonnefon                     std::static_pointer_cast<std::function<void()>>( observer );
28896bde7d5SNicolas Bonnefon                 // The observer is called with the mutex held,
28996bde7d5SNicolas Bonnefon                 // Let's hope it doesn't do anything too funky.
29096bde7d5SNicolas Bonnefon                 (*fptr)();
291fcaa7557SNicolas Bonnefon 
292fcaa7557SNicolas Bonnefon                 file->markAsChanged();
293fcaa7557SNicolas Bonnefon             }
294fcaa7557SNicolas Bonnefon         }
295fcaa7557SNicolas Bonnefon 
296fcaa7557SNicolas Bonnefon         if ( polling_interval_ms_ > 0 ) {
297fcaa7557SNicolas Bonnefon             // Also call files that have not been called for a while
298fcaa7557SNicolas Bonnefon             for ( auto file: observed_file_list_ ) {
299fcaa7557SNicolas Bonnefon                 uint32_t ms_since_last_check =
300fcaa7557SNicolas Bonnefon                     std::chrono::duration_cast<std::chrono::milliseconds>(
301fcaa7557SNicolas Bonnefon                             std::chrono::steady_clock::now() - file->timeForLastCheck() ).count();
302fcaa7557SNicolas Bonnefon                 if ( ( ms_since_last_check > polling_interval_ms_ ) && file->hasChanged() ) {
303fcaa7557SNicolas Bonnefon                     LOG(logDEBUG) << "WatchTower::run: " << file->file_name_;
304fcaa7557SNicolas Bonnefon                     for ( auto observer: file->callbacks ) {
305fcaa7557SNicolas Bonnefon                         LOG(logDEBUG) << "WatchTower::run: notifying the client because of a timeout!";
306fcaa7557SNicolas Bonnefon                         // Here we have to cast our generic pointer back to
307fcaa7557SNicolas Bonnefon                         // the function pointer in order to perform the call
308fcaa7557SNicolas Bonnefon                         const std::shared_ptr<std::function<void()>> fptr =
309fcaa7557SNicolas Bonnefon                             std::static_pointer_cast<std::function<void()>>( observer );
310fcaa7557SNicolas Bonnefon                         // The observer is called with the mutex held,
311fcaa7557SNicolas Bonnefon                         // Let's hope it doesn't do anything too funky.
312fcaa7557SNicolas Bonnefon                         (*fptr)();
313fcaa7557SNicolas Bonnefon                     }
314fcaa7557SNicolas Bonnefon                     file->markAsChanged();
315fcaa7557SNicolas Bonnefon                 }
31696bde7d5SNicolas Bonnefon             }
31796bde7d5SNicolas Bonnefon         }
31896bde7d5SNicolas Bonnefon     }
31996bde7d5SNicolas Bonnefon }
32096bde7d5SNicolas Bonnefon 
32196bde7d5SNicolas Bonnefon namespace {
isSymLink(const std::string & file_name)32296bde7d5SNicolas Bonnefon     bool isSymLink( const std::string& file_name )
32396bde7d5SNicolas Bonnefon     {
324f09fa651SNicolas Bonnefon #ifdef HAVE_SYMLINK
32596bde7d5SNicolas Bonnefon         struct stat buf;
32696bde7d5SNicolas Bonnefon 
32796bde7d5SNicolas Bonnefon         lstat( file_name.c_str(), &buf );
32896bde7d5SNicolas Bonnefon         return ( S_ISLNK(buf.st_mode) );
329f09fa651SNicolas Bonnefon #else
330f09fa651SNicolas Bonnefon         return false;
331f09fa651SNicolas Bonnefon #endif
33296bde7d5SNicolas Bonnefon     }
33396bde7d5SNicolas Bonnefon };
33496bde7d5SNicolas Bonnefon 
33584b2179eSNicolas Bonnefon #endif
336