288 lines
7.4 KiB
C++
288 lines
7.4 KiB
C++
#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 <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
#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<string> 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<string>::iterator it = tdl.begin(); it != tdl.end(); it++) {
|
|
queue->getConfig()->setKeyDir(*it);
|
|
walker.clearSkippedNames();
|
|
string skipped;
|
|
if (queue->getConfig()->getConfParam("skippedNames", skipped)) {
|
|
list<string> 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 <fam.h>
|
|
#include <sys/select.h>
|
|
|
|
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<int,string> 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<int,string>::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
|