/// // Copyright (C) 2002 - 2004, Fredrik Arnerup & Rasmus Kaj, See COPYING /// #include "filewatcher.h" #include #include "warning.h" #include "filesys.h" #include #include "defines.h" #ifdef HAVE_LIBFAM #include /// SGI File Alteration Monitor wrapper namespace FAM { class FAM; struct Error: public std::runtime_error { Error(const std::string& msg) : std::runtime_error(msg) {} }; /** Create a File object and listen to its signal for changes. */ class File { public: /* \param _filename Must be an absolute path. */ File(const std::string& _filename); ~File(); /// Signal emitted every time a change is detected. sigc::signal signal; private: std::string filename; FAMRequest fr; }; /** A singleton with no public methods. Used only by File. */ class Server: public sigc::trackable { friend class File; static Server *_instance; bool ok; FAMConnection fc; int fd; static Server &instance() { if(!_instance) _instance = new Server(); return *_instance; } Server() { ok = FAMOpen(&fc) == 0; if(!ok) { verbose << "Failed to contact FAM server" << std::endl; return; } verbose << "Contact with FAM server established" << std::endl; fd = FAMCONNECTION_GETFD(&fc); /// \todo don't poll Glib::signal_timeout().connect(sigc::mem_fun(*this, &Server::poll), 100); } ~Server() { if(ok) FAMClose(&fc); // might fail, but so what } bool poll() { int pending; while(pending = FAMPending(&fc)) { if(pending < 0) return true; /// \todo handle error somehow FAMEvent fe; if(FAMNextEvent(&fc, &fe) && (fe.code == FAMChanged || fe.code == FAMDeleted)) { File *file = static_cast(fe.userdata); file->signal(); debug << "modified (fam)" << std::endl; } } return true; } }; Server *Server::_instance = 0; File::File(const std::string& _filename) :filename(_filename) { debug << filename << " ..." << std::endl; if(filename.empty()) return; if(!Server::instance().ok) throw Error("No contact with fam"); if(FAMMonitorFile(&Server::instance().fc, filename.c_str(), &fr, this) < 0) throw Error("Could not monitor file \"" + filename + '"'); debug << filename << " on" << std::endl; } File::~File() { debug << filename << " off" << std::endl; if(!filename.empty() && Server::instance().ok && (FAMCancelMonitor(&Server::instance().fc, &fr) != 0)) warning << "Failed to stop watching \"" + filename + '"'; } } #else // dummy object to keep ifdefs out of the header namespace FAM { class File {}; } #endif using namespace std; FileWatcher::FileWatcher(const std::string &filename) { set_file(filename); } FileWatcher::~FileWatcher() {} void FileWatcher::set_file(const std::string &filename) { file = filename; #ifdef HAVE_LIBFAM try { famfile.reset(new FAM::File(file)); famfile->signal.connect(modified_signal.make_slot()); } catch(const std::exception&) { famfile.reset(); } #endif if(!famfile.get()) { // fam is not enabled or doesn't work try { last_time = modified(file); } catch(const std::exception&) {} if(!connection.connected()) { debug << "Connecting to timeout" << std::endl; connection = Glib::signal_timeout().connect (sigc::mem_fun(*this, &FileWatcher::check_file), 100); } } else connection.disconnect(); } bool FileWatcher::check_file() { try { time_t new_time = modified(file); if(new_time != last_time) { last_time = new_time; modified_signal(); debug << "modified" << std::endl; } } catch(const std::exception&) {} return true; // don't disconnect }