#include "autoconfig.h" #ifdef RCL_MONITOR #ifndef lint static char rcsid[] = "@(#$Id: rclmonrcv.cpp,v 1.2 2006-10-17 14:41:59 dockes Exp $ (C) 2006 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "debuglog.h" #include "rclmon.h" #include "fstreewalk.h" #include "indexer.h" #include "pathut.h" /** * Recoll real time monitor event receiver. This file has code to interface * to FAM and place events on the event queue. */ /** A small virtual interface for monitors. Suitable to let either of fam/gamin/ or raw imonitor hide behind */ class RclMonitor { public: RclMonitor(){} virtual ~RclMonitor() {} virtual bool addWatch(const string& path, const struct stat&) = 0; virtual bool getEvent(RclMonEvent& ev) = 0; virtual bool ok() = 0; }; // Monitor factory static RclMonitor *makeMonitor(); /** Class used to create the directory watches */ class WalkCB : public FsTreeWalkerCB { public: WalkCB(RclConfig *conf, RclMonitor *mon) : m_conf(conf), m_mon(mon) {} virtual ~WalkCB() {} virtual FsTreeWalker::Status processone(const string &fn, const struct stat *st, FsTreeWalker::CbFlag flg) { LOGDEB2(("rclMonRcvRun: processone %s m_mon %p m_mon->ok %d\n", fn.c_str(), m_mon, m_mon?m_mon->ok():0)); if (flg == FsTreeWalker::FtwDirEnter) { if (!m_mon || !m_mon->ok() || !m_mon->addWatch(fn, *st)) return FsTreeWalker::FtwError; } return FsTreeWalker::FtwOk; } private: RclConfig *m_conf; RclMonitor *m_mon; }; /** Main thread routine: create watches, then wait for events an queue them */ void *rclMonRcvRun(void *q) { RclMonEventQueue *queue = (RclMonEventQueue *)q; RclMonitor *mon; LOGDEB(("rclMonRcvRun: running\n")); if ((mon = makeMonitor()) == 0) { LOGERR(("rclMonRcvRun: makeMonitor failed\n")); rclEQ.setTerminate(); return 0; } // Get top directories from config and walk trees to add watches FsTreeWalker walker; WalkCB walkcb(queue->getConfig(), mon); list tdl = queue->getConfig()->getTopdirs(); if (tdl.empty()) { LOGERR(("rclMonRcvRun:: top directory list (topdirs param.) not" "found in config or Directory list parse error")); rclEQ.setTerminate(); return 0; } for (list::iterator it = tdl.begin(); it != tdl.end(); it++) { queue->getConfig()->setKeyDir(*it); walker.clearSkippedNames(); string skipped; if (queue->getConfig()->getConfParam("skippedNames", skipped)) { list skpl; stringToStrings(skipped, skpl); walker.setSkippedNames(skpl); } LOGDEB(("rclMonRcvRun: walking %s\n", it->c_str())); walker.walk(*it, walkcb); } // Forever wait for monitoring events and add them to queue: LOGDEB2(("rclMonRcvRun: waiting for events. rclEQ.ok() %d\n", rclEQ.ok())); while (rclEQ.ok()) { if (!mon->ok()) break; RclMonEvent ev; if (mon->getEvent(ev)) { rclEQ.pushEvent(ev); } if (!mon->ok()) break; } LOGDEB(("rclMonRcvRun: exiting\n")); rclEQ.setTerminate(); return 0; } ////////////////////////////////////////////////////////////////////////// /** Fam/gamin -based monitor class */ #include #include static const char *event_name(int code) { static const char *famevent[] = { "", "FAMChanged", "FAMDeleted", "FAMStartExecuting", "FAMStopExecuting", "FAMCreated", "FAMMoved", "FAMAcknowledge", "FAMExists", "FAMEndExist" }; static char unknown_event[20]; if (code < FAMChanged || code > FAMEndExist) { sprintf(unknown_event, "unknown (%d)", code); return unknown_event; } return famevent[code]; } // FAM based monitor class class RclFAM : public RclMonitor { public: RclFAM(); virtual ~RclFAM(); virtual bool addWatch(const string& path, const struct stat& st); virtual bool getEvent(RclMonEvent& ev); bool ok() {return m_ok;} private: bool m_ok; FAMConnection m_conn; void close() { FAMClose(&m_conn); m_ok = false; } map m_reqtodir; }; RclFAM::RclFAM() : m_ok(false) { if (FAMOpen2(&m_conn, "Recoll")) { LOGERR(("RclFAM::RclFAM: FAMOpen2 failed, errno %d\n", errno)); return; } m_ok = true; } RclFAM::~RclFAM() { if (ok()) FAMClose(&m_conn); } bool RclFAM::addWatch(const string& path, const struct stat& st) { if (!ok()) return false; LOGDEB(("RclFAM::addWatch: adding %s\n", path.c_str())); FAMRequest req; if (S_ISDIR(st.st_mode)) { if (FAMMonitorDirectory(&m_conn, path.c_str(), &req, 0) != 0) { LOGERR(("RclFAM::addWatch: FAMMonitorDirectory failed\n")); return false; } m_reqtodir[req.reqnum] = path; } else if (S_ISREG(st.st_mode)) { if (FAMMonitorFile(&m_conn, path.c_str(), &req, 0) != 0) { LOGERR(("RclFAM::addWatch: FAMMonitorFile failed\n")); return false; } } return true; } bool RclFAM::getEvent(RclMonEvent& ev) { if (!ok()) return false; LOGDEB2(("RclFAM::getEvent:\n")); fd_set readfds; int fam_fd = FAMCONNECTION_GETFD(&m_conn); FD_ZERO(&readfds); FD_SET(fam_fd, &readfds); // Note: can't see a reason to set a timeout. Only reason we might // want out is signal which will break the select call anyway (I // don't think that there is any system still using the old bsd-type // syscall re-entrance after signal). LOGDEB(("RclFAM::getEvent: select\n")); if (select(fam_fd + 1, &readfds, 0, 0, 0) < 0) { LOGERR(("RclFAM::getEvent: select failed, errno %d\n", errno)); close(); return false; } if (!FD_ISSET(fam_fd, &readfds)) return false; FAMEvent fe; if (FAMNextEvent(&m_conn, &fe) < 0) { LOGERR(("RclFAM::getEvent: FAMNextEvent failed, errno %d\n", errno)); close(); return false; } map::const_iterator it; if ((it = m_reqtodir.find(fe.fr.reqnum)) != m_reqtodir.end()) { ev.m_path = path_cat(it->second, fe.filename); } else { ev.m_path = fe.filename; } LOGDEB(("RclFAM::getEvent: %-12s %s\n", event_name(fe.code), ev.m_path.c_str())); switch (fe.code) { case FAMChanged: case FAMCreated: case FAMExists: // Let the other side sort out the status of this file vs the db ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; break; case FAMDeleted: ev.m_etyp = RclMonEvent::RCLEVT_DELETE; break; case FAMMoved: /* Never generated it seems */ LOGDEB(("RclFAM::getEvent: got move event !\n")); ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; break; case FAMStartExecuting: case FAMStopExecuting: case FAMAcknowledge: case FAMEndExist: default: return false; } return true; } // The monitor factory static RclMonitor *makeMonitor() { return new RclFAM; } #endif // RCL_MONITOR