libmoost
|
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__