libmoost
/home/mhx/git/github/libmoost/include/moost/io/file_watcher.hpp
Go to the documentation of this file.
00001 /* vim:set ts=3 sw=3 sts=3 et: */
00028 #ifndef MOOST_IO_FILE_WATCHER_HPP__
00029 #define MOOST_IO_FILE_WATCHER_HPP__
00030 
00031 #include <map>
00032 #include <string>
00033 #include <limits>
00034 #include <functional>
00035 
00036 #include <boost/thread.hpp>
00037 #include <boost/thread/condition.hpp>
00038 #include <boost/shared_ptr.hpp>
00039 #include <boost/bind.hpp>
00040 #include <boost/function.hpp>
00041 
00042 // for last_write_time and exists
00043 #include <boost/filesystem/path.hpp>
00044 #include <boost/filesystem/operations.hpp>
00045 #include <boost/filesystem/exception.hpp>
00046 
00047 #include "../thread/xtime_util.hpp"
00048 
00053 namespace moost { namespace io {
00054 
00064 class file_watcher
00065 {
00066 public:
00067 
00069   enum file_action
00070   {
00071     CREATED = 0, 
00072     CHANGED = 1, 
00073     DELETED = 2  
00074   };
00075 
00076   typedef boost::function<void(file_action action, const std::string & path)> callback_t;
00077 
00078 private:
00079 
00081   std::map<std::string, callback_t > m_file_callback;
00083 
00093   std::map<std::string, std::pair<time_t, time_t> > m_file_modified;
00094 
00097   boost::mutex m_file_mutex;
00098 
00100   boost::shared_ptr< boost::thread> m_pthread;
00101 
00102   // m_run is thread-safe: it's mutex/conditioned to coordinate with the async watcher thread
00103   // when false, and the cond is signalled, the watcher thread will wake up and exit
00104 
00106   bool m_run;
00108 
00112   boost::mutex m_run_mutex;
00114   boost::condition m_run_cond;
00115 
00116   int m_sleep_ms;
00117 
00119   std::time_t last_write_time(const boost::filesystem::path & p)
00120   {
00121     try
00122     {
00123        if (!boost::filesystem::exists(p))
00124           return 0;
00125        return boost::filesystem::last_write_time(p);
00126     }
00127     catch (const boost::filesystem::filesystem_error &)
00128     {
00129        return 0; // we were very unlucky!
00130     }
00131     catch (const std::exception&)
00132     {
00133        return 0; // todo: handle differently
00134     }
00135   }
00136 
00137 public:
00138 
00142   file_watcher(int sleep_ms = 500) :
00143     m_run(false), m_sleep_ms(sleep_ms)
00144   {
00145   }
00146 
00148 
00153   ~file_watcher()
00154   {
00155     stop();
00156   }
00157 
00159 
00163   void insert(const std::string & path,
00164               const callback_t & callback,
00165               bool call_now = false )
00166   {
00167     boost::mutex::scoped_lock lock(m_file_mutex);
00168 
00169     m_file_callback[path] = callback;
00170 
00171     boost::filesystem::path p(path);
00172     std::time_t lw = last_write_time(p);
00173     if (lw != 0)
00174     {
00175       m_file_modified[path] = std::make_pair(lw, lw);
00176       if ( call_now )
00177         callback(CHANGED, path);
00178     }
00179     else
00180     {
00181       m_file_modified.erase(path);
00182       if ( call_now )
00183         callback(DELETED, path);
00184     }
00185   }
00186 
00188 
00191   void erase(const std::string & path)
00192   {
00193     boost::mutex::scoped_lock lock(m_file_mutex);
00194     m_file_callback.erase(path);
00195     m_file_modified.erase(path);
00196   }
00197 
00199 
00202   void start()
00203   {
00204     boost::mutex::scoped_lock lock(m_run_mutex);
00205     if (m_run)
00206       return;
00207     m_run = true;
00208     m_pthread.reset(new boost::thread(boost::bind(&file_watcher::run, this)));
00209   }
00210 
00212 
00215   void stop()
00216   {
00217     {
00218       boost::mutex::scoped_lock lock(m_run_mutex);
00219       if (!m_run)
00220         return;
00221       m_run = false;
00222       // Notify the asynchronous monitor thread that it should wake up
00223       // if it's currently waiting on m_run_mutex
00224       m_run_cond.notify_one();
00225     }
00226     // can only join once we've released the mutex, so run() loop can finish up
00227     m_pthread->join();
00228   }
00229 
00230 private:
00231 
00233   void run()
00234   {
00235     bool run = true;
00236     typedef std::pair< callback_t , std::pair< file_action, std::string> > notification;
00237     std::vector< notification> notifications;
00238 
00239     for (;;)
00240     {
00241       {
00242         // Lock and check m_run; if it was set to false, we must return ASAP
00243         boost::mutex::scoped_lock lock(m_run_mutex);
00244         run = m_run;
00245         if (!run)
00246           return;
00247         // We release the lock, block the thread until one second
00248         m_run_cond.timed_wait(lock, moost::thread::xtime_util::add_ms(moost::thread::xtime_util::now(), m_sleep_ms));
00249       }
00250       // If we are not running (e.g. when the destructor woke us up), return
00251       if (!run)
00252         return;
00253 
00254       // Clear the notifications vector where we will collect the events
00255       // that will be fired
00256       notifications.clear();
00257       {
00258         // Lock m_file_mutex while we are working on m_file_callback
00259         boost::mutex::scoped_lock lock(m_file_mutex);
00260         for (std::map<std::string, callback_t>::iterator it = m_file_callback.begin(); it != m_file_callback.end(); ++it)
00261         {
00262           boost::filesystem::path p(it->first);
00263 
00264           // Does the path exist?
00265           std::time_t lw = last_write_time(p);
00266           if (lw != 0)
00267           {
00268             // Check its last modification time and compare it with what we had earlier
00269             std::map< std::string, std::pair<time_t, time_t> >::iterator it_mod = m_file_modified.find(it->first);
00270 
00271             if (it_mod == m_file_modified.end())
00272             {
00273               // We haven't seen this file so far, so insert it into the
00274               // map and add a creation event that will be fired
00275               m_file_modified[it->first] = std::make_pair(lw, lw);
00276               notifications.push_back(std::make_pair(it->second, std::make_pair(CREATED, it->first)));
00277             }
00278             else
00279             {
00280               // only case we consider a real modification: prev prev mod != prev mod,
00281               // but this mod == prev mod
00282               // the idea is that we want to capture a write to a file,
00283               // but only notify when the write is finished
00284 
00290               if (lw == it_mod->second.second && it_mod->second.first != it_mod->second.second)
00291                 notifications.push_back(std::make_pair(it->second, std::make_pair(CHANGED, it->first)));
00292               it_mod->second.first = it_mod->second.second;
00293               it_mod->second.second = lw;
00294             }
00295           }
00296           else
00297           {
00298             // The path does not exist. Did we have it before? If so, fire
00299             // a deletion event.
00300             std::map< std::string, std::pair<time_t, time_t> >::iterator it_mod = m_file_modified.find(it->first);
00301             if (it_mod != m_file_modified.end())
00302             {
00303               m_file_modified.erase(it_mod);
00304               notifications.push_back(std::make_pair(it->second, std::make_pair(DELETED, it->first)));
00305             }
00306           }
00307         }
00308       }
00309 
00310       // okay!  we've released our lock on m_file_callback and m_file_modified
00311       // so it's time to send off our notifications
00312       for (std::vector<notification>::iterator it = notifications.begin(); it != notifications.end(); ++it)
00313       {
00314         try
00315         {
00316           it->first(it->second.first, it->second.second);
00317         }
00318         catch (...)
00319         {
00320           // \todo  can we do better here than silently ignoring the exception?
00321         }
00322       }
00323     }
00324   }
00325 };
00326 
00327 }} // moost::io
00328 
00329 #endif // MOOST_IO_FILE_WATCHER_HPP__