kdelibs/kdecore/io/kdirwatch.cpp
2015-09-28 15:50:22 +00:00

1917 lines
57 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
Copyright (C) 2006 Dirk Mueller <mueller@kde.org>
Copyright (C) 2007 Flavio Castelli <flavio.castelli@gmail.com>
Copyright (C) 2008 Rafal Rzepecki <divided.mind@gmail.com>
Copyright (C) 2010 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// CHANGES:
// Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal)
// Aug 6, 2007 - KDirWatch::WatchModes support complete, flags work fine also
// when using FAMD (Flavio Castelli)
// Aug 3, 2007 - Handled KDirWatch::WatchModes flags when using inotify, now
// recursive and file monitoring modes are implemented (Flavio Castelli)
// Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes
// flag (Flavio Castelli)
// Oct 4, 2005 - Inotify support (Dirk Mueller)
// Februar 2002 - Add file watching and remote mount check for STAT
// Mar 30, 2001 - Native support for Linux dir change notification.
// Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
// May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
// May 23. 1998 - Removed static pointer - you can have more instances.
// It was Needed for KRegistry. KDirWatch now emits signals and doesn't
// call (or need) KFM. No more URL's - just plain paths. (sven)
// Mar 29. 1998 - added docs, stop/restart for particular Dirs and
// deep copies for list of dirs. (sven)
// Mar 28. 1998 - Created. (sven)
#include "kdirwatch.h"
#include "kdirwatch_p.h"
#include "kfilesystemtype_p.h"
#include <io/config-kdirwatch.h>
#include <config.h>
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QSocketNotifier>
#include <QtCore/QTimer>
#include <QtCore/QCoreApplication>
#include <ksharedconfig.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kde_file.h>
#include <kconfiggroup.h>
#include <stdlib.h>
#include <string.h>
// debug
#include <sys/ioctl.h>
#include <sys/utsname.h>
// set this to true for much more verbose debug output
static const bool s_verboseDebug = false;
// The KDirWatchPrivate instance is refcounted, and deleted by the last KDirWatch instance
static KDirWatchPrivate* dwp_self = 0;
static KDirWatchPrivate* createPrivate() {
if (!dwp_self)
dwp_self = new KDirWatchPrivate;
return dwp_self;
}
// Convert a string into a watch Method
static KDirWatch::Method methodFromString(const QString& method) {
if (method == QLatin1String("Fam")) {
return KDirWatch::FAM;
} else if (method == QLatin1String("Stat")) {
return KDirWatch::Stat;
} else if (method == QLatin1String("QFSWatch")) {
return KDirWatch::QFSWatch;
} else {
#ifdef Q_OS_LINUX
// inotify supports delete+recreate+modify, which QFSWatch doesn't support
return KDirWatch::INotify;
#else
return KDirWatch::QFSWatch;
#endif
}
}
#ifndef NDEBUG
static const char* methodToString(KDirWatch::Method method)
{
switch (method) {
case KDirWatch::FAM:
return "Fam";
case KDirWatch::INotify:
return "INotify";
case KDirWatch::Stat:
return "Stat";
case KDirWatch::QFSWatch:
return "QFSWatch";
default:
return "ERROR!";
}
}
#endif
//
// Class KDirWatchPrivate (singleton)
//
/* All entries (files/directories) to be watched in the
* application (coming from multiple KDirWatch instances)
* are registered in a single KDirWatchPrivate instance.
*
* At the moment, the following methods for file watching
* are supported:
* - Polling: All files to be watched are polled regularly
* using stat (more precise: QFileInfo.lastModified()).
* The polling frequency is determined from global kconfig
* settings, defaulting to 500 ms for local directories
* and 5000 ms for remote mounts
* - FAM (File Alternation Monitor): first used on IRIX, SGI
* has ported this method to LINUX. It uses a kernel part
* (IMON, sending change events to /dev/imon) and a user
* level damon (fam), to which applications connect for
* notification of file changes. For NFS, the fam damon
* on the NFS server machine is used; if IMON is not built
* into the kernel, fam uses polling for local files.
* - INOTIFY: In LINUX 2.6.13, inode change notification was
* introduced. You're now able to watch arbitrary inode's
* for changes, and even get notification when they're
* unmounted.
*/
KDirWatchPrivate::KDirWatchPrivate()
: timer(),
freq( 3600000 ), // 1 hour as upper bound
statEntries( 0 ),
m_ref( 0 ),
delayRemove( false ),
rescan_all( false ),
rescan_timer()
{
timer.setObjectName(QLatin1String("KDirWatchPrivate::timer"));
connect (&timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
KConfigGroup config(KGlobal::config(), "DirWatch");
m_nfsPollInterval = config.readEntry("NFSPollInterval", 5000);
m_PollInterval = config.readEntry("PollInterval", 500);
QString method = config.readEntry("PreferredMethod", "inotify");
m_preferredMethod = methodFromString(method);
// The nfs method defaults to the normal (local) method
m_nfsPreferredMethod = methodFromString(config.readEntry("nfsPreferredMethod", "Fam"));
QList<QByteArray> availableMethods;
availableMethods << "Stat";
// used for FAM and inotify
rescan_timer.setObjectName(QString::fromLatin1("KDirWatchPrivate::rescan_timer"));
rescan_timer.setSingleShot( true );
connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
#ifdef HAVE_FAM
// It's possible that FAM server can't be started
if (FAMOpen(&fc) ==0) {
availableMethods << "FAM";
use_fam=true;
sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
QSocketNotifier::Read, this);
connect( sn, SIGNAL(activated(int)),
this, SLOT(famEventReceived()) );
}
else {
kDebug(7001) << "Can't use FAM (fam daemon not running?)";
use_fam=false;
}
#endif
#ifdef HAVE_SYS_INOTIFY_H
supports_inotify = true;
m_inotify_fd = inotify_init();
if ( m_inotify_fd <= 0 ) {
kDebug(7001) << "Can't use Inotify, kernel doesn't support it";
supports_inotify = false;
}
{
struct utsname uts;
int major, minor, patch;
if (uname(&uts) < 0) {
supports_inotify = false;
kDebug(7001) << "Unable to get uname";
} else if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
supports_inotify = false;
kDebug(7001) << "The version is malformed: " << uts.release;
} else if(major == 2 && minor == 6) { // If it is 2.6 check further...
if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) {
supports_inotify = false;
kDebug() << "Detected 2.6 kernel but can't know more: " << uts.release;
} else if (major * 1000000 + minor * 1000 + patch < 2006014 ){
supports_inotify = false;
kDebug(7001) << "Can't use INotify, Linux kernel too old " << uts.release;
}
}
}
kDebug(7001) << "INotify available: " << supports_inotify;
if ( supports_inotify ) {
availableMethods << "INotify";
(void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
mSn = new QSocketNotifier( m_inotify_fd, QSocketNotifier::Read, this );
connect( mSn, SIGNAL(activated(int)),
this, SLOT(inotifyEventReceived()) );
}
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
availableMethods << "QFileSystemWatcher";
fsWatcher = 0;
#endif
#ifndef NDEBUG
kDebug(7001) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod);
#endif
}
// This is called on app exit (when K_GLOBAL_STATIC deletes KDirWatch::self)
KDirWatchPrivate::~KDirWatchPrivate()
{
timer.stop();
/* remove all entries being watched */
removeEntries(0);
#ifdef HAVE_FAM
if (use_fam) {
FAMClose(&fc);
}
#endif
#ifdef HAVE_SYS_INOTIFY_H
if ( supports_inotify )
::close( m_inotify_fd );
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
delete fsWatcher;
#endif
}
void KDirWatchPrivate::inotifyEventReceived()
{
//kDebug(7001);
#ifdef HAVE_SYS_INOTIFY_H
if ( !supports_inotify )
return;
int pending = -1;
int offsetStartRead = 0; // where we read into buffer
char buf[8192];
assert( m_inotify_fd > -1 );
ioctl( m_inotify_fd, FIONREAD, &pending );
while ( pending > 0 ) {
const int bytesToRead = qMin( pending, (int)sizeof( buf ) - offsetStartRead );
int bytesAvailable = read( m_inotify_fd, &buf[offsetStartRead], bytesToRead );
pending -= bytesAvailable;
bytesAvailable += offsetStartRead;
offsetStartRead = 0;
int offsetCurrent = 0;
while ( bytesAvailable >= (int)sizeof( struct inotify_event ) ) {
const struct inotify_event * const event = (struct inotify_event *) &buf[offsetCurrent];
const int eventSize = sizeof( struct inotify_event ) + event->len;
if ( bytesAvailable < eventSize ) {
break;
}
bytesAvailable -= eventSize;
offsetCurrent += eventSize;
QString path;
QByteArray cpath(event->name, event->len);
if(event->len)
path = QFile::decodeName ( cpath );
if ( path.length() && isNoisyFile( cpath ) )
continue;
// Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir
const bool isDir = (event->mask & (IN_ISDIR));
// now we're in deep trouble of finding the
// associated entries
// for now, we suck and iterate
for ( EntryMap::Iterator it = m_mapEntries.begin();
it != m_mapEntries.end(); ) {
Entry* e = &( *it );
++it;
if ( e->wd == event->wd ) {
const bool wasDirty = e->dirty;
e->dirty = true;
const QString tpath = e->path + QLatin1Char('/') + path;
//if (s_verboseDebug) {
// kDebug(7001) << "got event" << "0x"+QString::number(event->mask, 16) << "for" << e->path;
//}
if( event->mask & IN_DELETE_SELF) {
if (s_verboseDebug) {
kDebug(7001) << "-->got deleteself signal for" << e->path;
}
e->m_status = NonExistent;
e->wd = -1;
e->m_ctime = invalid_ctime;
emitEvent(e, Deleted, e->path);
// If the parent dir was already watched, tell it something changed
Entry* parentEntry = entry(e->parentDirectory());
if (parentEntry)
parentEntry->dirty = true;
// Add entry to parent dir to notice if the entry gets recreated
addEntry(0, e->parentDirectory(), e, true /*isDir*/);
}
if ( event->mask & IN_IGNORED ) {
// Causes bug #207361 with kernels 2.6.31 and 2.6.32!
//e->wd = -1;
}
if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
Entry* sub_entry = e->findSubEntry(tpath);
if (s_verboseDebug) {
kDebug(7001) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry;
kDebug(7001) << *e;
}
// The code below is very similar to the one in checkFAMEvent...
if (sub_entry) {
// We were waiting for this new file/dir to be created
sub_entry->dirty = true;
rescan_timer.start(0); // process this asap, to start watching that dir
} else if (e->isDir && !e->m_clients.empty()) {
const QList<Client *> clients = e->inotifyClientsForFileOrDir(isDir);
Q_FOREACH(Client *client, clients) {
// See discussion in addEntry for why we don't addEntry for individual
// files in WatchFiles mode with inotify.
if (isDir) {
addEntry(client->instance, tpath, 0, isDir,
isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
}
}
if (!clients.isEmpty()) {
emitEvent(e, Created, tpath);
kDebug(7001).nospace() << clients.count() << " instance(s) monitoring the new "
<< (isDir ? "dir " : "file ") << tpath;
}
e->m_pendingFileChanges.append(e->path);
if (!rescan_timer.isActive())
rescan_timer.start(m_PollInterval); // singleshot
}
}
if (event->mask & (IN_DELETE|IN_MOVED_FROM)) {
if (s_verboseDebug) {
kDebug(7001) << "-->got DELETE signal for" << tpath;
}
if ((e->isDir) && (!e->m_clients.empty())) {
Client* client = 0;
// A file in this directory has been removed. It wasn't an explicitly
// watched file as it would have its own watch descriptor, so
// no addEntry/ removeEntry bookkeeping should be required. Emit
// the event immediately if any clients are interested.
KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
int counter = 0;
Q_FOREACH(client, e->m_clients) { // krazy:exclude=foreach
if (client->m_watchModes & flag) {
counter++;
}
}
if (counter != 0) {
emitEvent(e, Deleted, tpath);
}
}
}
if (event->mask & (IN_MODIFY|IN_ATTRIB)) {
if ((e->isDir) && (!e->m_clients.empty())) {
if (s_verboseDebug) {
kDebug(7001) << "-->got MODIFY signal for" << (tpath);
}
// A file in this directory has been changed. No
// addEntry/ removeEntry bookkeeping should be required.
// Add the path to the list of pending file changes if
// there are any interested clients.
//KDE_struct_stat stat_buf;
//QByteArray tpath = QFile::encodeName(e->path+'/'+path);
//KDE_stat(tpath, &stat_buf);
//bool isDir = S_ISDIR(stat_buf.st_mode);
// The API doc is somewhat vague as to whether we should emit
// dirty() for implicitly watched files when WatchFiles has
// not been specified - we'll assume they are always interested,
// regardless.
// Don't worry about duplicates for the time
// being; this is handled in slotRescan.
e->m_pendingFileChanges.append(tpath);
// Avoid stat'ing the directory if only an entry inside it changed.
e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB)));
}
}
if (!rescan_timer.isActive())
rescan_timer.start(m_PollInterval); // singleshot
break;
}
}
}
if (bytesAvailable > 0) {
// copy partial event to beginning of buffer
memmove(buf, &buf[offsetCurrent], bytesAvailable);
offsetStartRead = bytesAvailable;
}
}
#endif
}
/* In FAM mode, only entries which are marked dirty are scanned.
* We first need to mark all yet nonexistent, but possible created
* entries as dirty...
*/
void KDirWatchPrivate::Entry::propagate_dirty()
{
foreach(Entry *sub_entry, m_entries)
{
if (!sub_entry->dirty)
{
sub_entry->dirty = true;
sub_entry->propagate_dirty();
}
}
}
/* A KDirWatch instance is interested in getting events for
* this file/Dir entry.
*/
void KDirWatchPrivate::Entry::addClient(KDirWatch* instance,
KDirWatch::WatchModes watchModes)
{
if (instance == 0)
return;
foreach(Client* client, m_clients) {
if (client->instance == instance) {
client->count++;
client->m_watchModes = watchModes;
return;
}
}
Client* client = new Client;
client->instance = instance;
client->count = 1;
client->watchingStopped = instance->isStopped();
client->pending = NoChange;
client->m_watchModes = watchModes;
m_clients.append(client);
}
void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
{
QList<Client *>::iterator it = m_clients.begin();
const QList<Client *>::iterator end = m_clients.end();
for ( ; it != end ; ++it ) {
Client* client = *it;
if (client->instance == instance) {
client->count--;
if (client->count == 0) {
m_clients.erase(it);
delete client;
}
return;
}
}
}
/* get number of clients */
int KDirWatchPrivate::Entry::clientCount() const
{
int clients = 0;
foreach(Client* client, m_clients)
clients += client->count;
return clients;
}
QString KDirWatchPrivate::Entry::parentDirectory() const
{
return QDir::cleanPath(path + QLatin1String("/.."));
}
QList<KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString& tpath, bool* isDir) const
{
QList<Client *> ret;
KDE_struct_stat stat_buf;
if (KDE::stat(tpath, &stat_buf) == 0) {
*isDir = S_ISDIR(stat_buf.st_mode);
const KDirWatch::WatchModes flag =
*isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
Q_FOREACH(Client *client, this->m_clients) {
if (client->m_watchModes & flag) {
ret.append(client);
}
}
} else {
// Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp"
//kDebug(7001) << "ERROR: couldn't stat" << tpath;
}
// If KDE_stat fails then isDir is not set, but ret is empty anyway
// so isDir won't be used.
return ret;
}
// inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder.
// isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived
QList<KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const
{
QList<Client *> ret;
const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
Q_FOREACH(Client *client, this->m_clients) {
if (client->m_watchModes & flag) {
ret.append(client);
}
}
return ret;
}
QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry)
{
debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file");
if (entry.m_status == KDirWatchPrivate::NonExistent)
debug << ", non-existent";
debug << ", using " << ((entry.m_mode == KDirWatchPrivate::FAMMode) ? "FAM" :
(entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" :
(entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" :
(entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" : "Unknown Method");
#ifdef HAVE_SYS_INOTIFY_H
if (entry.m_mode == KDirWatchPrivate::INotifyMode)
debug << " inotify_wd=" << entry.wd;
#endif
debug << ", has " << entry.m_clients.count() << " clients";
debug.space();
if (!entry.m_entries.isEmpty()) {
debug << ", nonexistent subentries:";
Q_FOREACH(KDirWatchPrivate::Entry* subEntry, entry.m_entries)
debug << subEntry << subEntry->path;
}
debug << ']';
return debug;
}
KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
{
// we only support absolute paths
if (_path.isEmpty() || QDir::isRelativePath(_path)) {
return 0;
}
QString path (_path);
if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
path.truncate( path.length() - 1 );
EntryMap::Iterator it = m_mapEntries.find( path );
if ( it == m_mapEntries.end() )
return 0;
else
return &(*it);
}
// set polling frequency for a entry and adjust global freq if needed
void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
{
e->freq = newFreq;
// a reasonable frequency for the global polling timer
if (e->freq < freq) {
freq = e->freq;
if (timer.isActive()) timer.start(freq);
kDebug(7001) << "Global Poll Freq is now" << freq << "msec";
}
}
#if defined(HAVE_FAM)
// setup FAM notification, returns false if not possible
bool KDirWatchPrivate::useFAM(Entry* e)
{
if (!use_fam) return false;
// handle FAM events to avoid deadlock
// (FAM sends back all files in a directory when monitoring)
famEventReceived();
e->m_mode = FAMMode;
e->dirty = false;
if (e->isDir) {
if (e->m_status == NonExistent) {
// If the directory does not exist we watch the parent directory
addEntry(0, e->parentDirectory(), e, true);
}
else {
int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
&(e->fr), e);
if (res<0) {
e->m_mode = UnknownMode;
use_fam=false;
delete sn; sn = 0;
return false;
}
kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
<< ") for " << e->path;
}
}
else {
if (e->m_status == NonExistent) {
// If the file does not exist we watch the directory
addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
}
else {
int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
&(e->fr), e);
if (res<0) {
e->m_mode = UnknownMode;
use_fam=false;
delete sn; sn = 0;
return false;
}
kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
<< ") for " << e->path;
}
}
// handle FAM events to avoid deadlock
// (FAM sends back all files in a directory when monitoring)
famEventReceived();
return true;
}
#endif
#ifdef HAVE_SYS_INOTIFY_H
// setup INotify notification, returns false if not possible
bool KDirWatchPrivate::useINotify( Entry* e )
{
//kDebug (7001) << "trying to use inotify for monitoring";
e->wd = -1;
e->dirty = false;
if (!supports_inotify) return false;
e->m_mode = INotifyMode;
if ( e->m_status == NonExistent ) {
addEntry(0, e->parentDirectory(), e, true);
return true;
}
// May as well register for almost everything - it's free!
int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW|IN_MOVED_FROM|IN_MODIFY|IN_ATTRIB;
if ( ( e->wd = inotify_add_watch( m_inotify_fd,
QFile::encodeName( e->path ), mask) ) >= 0)
{
if (s_verboseDebug) {
kDebug(7001) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd;
}
return true;
}
kDebug(7001) << "inotify failed for monitoring" << e->path << ":" << strerror(errno);
return false;
}
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
bool KDirWatchPrivate::useQFSWatch(Entry* e)
{
e->m_mode = QFSWatchMode;
e->dirty = false;
if ( e->m_status == NonExistent ) {
addEntry(0, e->parentDirectory(), e, true /*isDir*/);
return true;
}
kDebug(7001) << "fsWatcher->addPath" << e->path;
if (!fsWatcher) {
fsWatcher = new KFileSystemWatcher();
connect(fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(fswEventReceived(QString)));
connect(fsWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fswEventReceived(QString)));
}
fsWatcher->addPath( e->path );
return true;
}
#endif
bool KDirWatchPrivate::useStat(Entry* e)
{
if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) // TODO: or Smbfs?
useFreq(e, m_nfsPollInterval);
else
useFreq(e, m_PollInterval);
if (e->m_mode != StatMode) {
e->m_mode = StatMode;
statEntries++;
if ( statEntries == 1 ) {
// if this was first STAT entry (=timer was stopped)
timer.start(freq); // then start the timer
kDebug(7001) << " Started Polling Timer, freq " << freq;
}
}
kDebug(7001) << " Setup Stat (freq " << e->freq << ") for " << e->path;
return true;
}
/* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
* providing in <isDir> the type of the entry to be watched.
* Sometimes, entries are dependant on each other: if <sub_entry> !=0,
* this entry needs another entry to watch himself (when notExistent).
*/
void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
Entry* sub_entry, bool isDir, KDirWatch::WatchModes watchModes)
{
QString path (_path);
if (path.isEmpty()
|| path == QLatin1String("/dev")
|| (path.startsWith(QLatin1String("/dev/")) && !path.startsWith(QLatin1String("/dev/."))
&& !path.startsWith(QLatin1String("/dev/shm")))
)
return; // Don't even go there.
if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
path.truncate( path.length() - 1 );
EntryMap::Iterator it = m_mapEntries.find( path );
if ( it != m_mapEntries.end() )
{
if (sub_entry) {
(*it).m_entries.append(sub_entry);
if (s_verboseDebug) {
kDebug(7001) << "Added already watched Entry" << path
<< "(for" << sub_entry->path << ")";
}
#ifdef HAVE_SYS_INOTIFY_H
Entry* e = &(*it);
if( (e->m_mode == INotifyMode) && (e->wd >= 0) ) {
int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
if(!e->isDir)
mask |= IN_MODIFY|IN_ATTRIB;
else
mask |= IN_ONLYDIR;
inotify_rm_watch (m_inotify_fd, e->wd);
e->wd = inotify_add_watch( m_inotify_fd, QFile::encodeName( e->path ),
mask);
//Q_ASSERT(e->wd >= 0); // fails in KDirListerTest::testDeleteCurrentDir
}
#endif
}
else {
(*it).addClient(instance, watchModes);
if (s_verboseDebug) {
kDebug(7001) << "Added already watched Entry" << path
<< "(now" << (*it).clientCount() << "clients)"
<< QString::fromLatin1("[%1]").arg(instance->objectName());
}
}
return;
}
// we have a new path to watch
KDE_struct_stat stat_buf;
bool exists = (KDE::stat(path, &stat_buf) == 0);
EntryMap::iterator newIt = m_mapEntries.insert( path, Entry() );
// the insert does a copy, so we have to use <e> now
Entry* e = &(*newIt);
if (exists) {
e->isDir = S_ISDIR(stat_buf.st_mode);
if (e->isDir && !isDir) {
if (KDE::lstat(path, &stat_buf) == 0) {
if (S_ISLNK(stat_buf.st_mode))
// if it's a symlink, don't follow it
e->isDir = false;
else
qWarning() << "KDirWatch:" << path << "is a directory. Use addDir!";
}
} else if (!e->isDir && isDir)
qWarning("KDirWatch: %s is a file. Use addFile!", qPrintable(path));
if (!e->isDir && ( watchModes != KDirWatch::WatchDirOnly)) {
qWarning() << "KDirWatch:" << path << "is a file. You can't use recursive or "
"watchFiles options";
watchModes = KDirWatch::WatchDirOnly;
}
e->m_ctime = stat_buf.st_ctime;
e->m_status = Normal;
e->m_nlink = stat_buf.st_nlink;
e->m_ino = stat_buf.st_ino;
}
else {
e->isDir = isDir;
e->m_ctime = invalid_ctime;
e->m_status = NonExistent;
e->m_nlink = 0;
e->m_ino = 0;
}
e->path = path;
if (sub_entry)
e->m_entries.append(sub_entry);
else
e->addClient(instance, watchModes);
kDebug(7001).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path
<< (e->m_status == NonExistent ? " NotExisting" : "")
<< " for " << (sub_entry ? sub_entry->path : QString())
<< " [" << (instance ? instance->objectName() : QString()) << "]";
// now setup the notification method
e->m_mode = UnknownMode;
e->msecLeft = 0;
if ( isNoisyFile( QFile::encodeName( path ) ) )
return;
if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot;
if ((watchModes & KDirWatch::WatchSubDirs) &&
(watchModes & KDirWatch::WatchFiles)) {
filters |= (QDir::Dirs|QDir::Files);
} else if (watchModes & KDirWatch::WatchSubDirs) {
filters |= QDir::Dirs;
} else if (watchModes & KDirWatch::WatchFiles) {
filters |= QDir::Files;
}
#if defined(HAVE_SYS_INOTIFY_H)
if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == KDirWatch::INotify) )
{
//kDebug(7001) << "Ignoring WatchFiles directive - this is implicit with inotify";
// Placing a watch on individual files is redundant with inotify
// (inotify gives us WatchFiles functionality "for free") and indeed
// actively harmful, so prevent it. WatchSubDirs is necessary, though.
filters &= ~QDir::Files;
}
#endif
QDir basedir (e->path);
const QFileInfoList contents = basedir.entryInfoList(filters);
foreach(const QFileInfo iter, contents)
{
const QFileInfo &fileInfo = iter;
// treat symlinks as files--don't follow them.
bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
addEntry (instance, fileInfo.absoluteFilePath(), 0, isDir,
isDir ? watchModes : KDirWatch::WatchDirOnly);
}
}
addWatch(e);
}
void KDirWatchPrivate::addWatch(Entry* e)
{
// If the watch is on a network filesystem use the nfsPreferredMethod as the
// default, otherwise use preferredMethod as the default, if the methods are
// the same we can skip the mountpoint check
// This allows to configure a different method for NFS mounts, since inotify
// cannot detect changes made by other machines. However as a default inotify
// is fine, since the most common case is a NFS-mounted home, where all changes
// are made locally. #177892.
KDirWatch::Method preferredMethod = m_preferredMethod;
if (m_nfsPreferredMethod != m_preferredMethod) {
if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) {
preferredMethod = m_nfsPreferredMethod;
}
}
// Try the appropriate preferred method from the config first
bool entryAdded = false;
switch (preferredMethod) {
#if defined(HAVE_FAM)
case KDirWatch::FAM: entryAdded = useFAM(e); break;
#endif
#if defined(HAVE_SYS_INOTIFY_H)
case KDirWatch::INotify: entryAdded = useINotify(e); break;
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
case KDirWatch::QFSWatch: entryAdded = useQFSWatch(e); break;
#endif
case KDirWatch::Stat: entryAdded = useStat(e); break;
default: break;
}
// Failing that try in order INotify, FAM, QFSWatch, Stat
if (!entryAdded) {
#if defined(HAVE_SYS_INOTIFY_H)
if (useINotify(e)) return;
#endif
#if defined(HAVE_FAM)
if (useFAM(e)) return;
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
if (useQFSWatch(e)) return;
#endif
useStat(e);
}
}
void KDirWatchPrivate::removeWatch(Entry* e)
{
#ifdef HAVE_FAM
if (e->m_mode == FAMMode) {
FAMCancelMonitor(&fc, &(e->fr) );
kDebug(7001).nospace() << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
<< ") for " << e->path;
}
#endif
#ifdef HAVE_SYS_INOTIFY_H
if (e->m_mode == INotifyMode) {
(void) inotify_rm_watch( m_inotify_fd, e->wd );
if (s_verboseDebug) {
kDebug(7001).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", "
<< e->wd << ") for " << e->path;
}
}
#endif
#ifndef QT_NO_FILESYSTEMWATCHER
if (e->m_mode == QFSWatchMode && fsWatcher) {
if (s_verboseDebug)
kDebug(7001) << "fsWatcher->removePath" << e->path;
fsWatcher->removePath(e->path);
}
#endif
}
bool KDirWatchPrivate::removeEntry(KDirWatch* instance,
const QString& _path,
Entry* sub_entry)
{
if (s_verboseDebug) {
kDebug(7001) << "path=" << _path << "sub_entry:" << sub_entry;
}
Entry* e = entry(_path);
if (!e) {
return false;
}
removeEntry(instance, e, sub_entry);
return true;
}
void KDirWatchPrivate::removeEntry(KDirWatch* instance,
Entry* e,
Entry* sub_entry)
{
removeList.remove(e);
if (sub_entry)
e->m_entries.removeAll(sub_entry);
else
e->removeClient(instance);
if (e->m_clients.count() || e->m_entries.count())
return;
if (delayRemove) {
removeList.insert(e);
// now e->isValid() is false
return;
}
if ( e->m_status == Normal) {
removeWatch(e);
} else {
// Removed a NonExistent entry - we just remove it from the parent
if (e->isDir)
removeEntry(0, e->parentDirectory(), e);
else
removeEntry(0, QFileInfo(e->path).absolutePath(), e);
}
if (e->m_mode == StatMode) {
statEntries--;
if ( statEntries == 0 ) {
timer.stop(); // stop timer if lists are empty
kDebug(7001) << " Stopped Polling Timer";
}
}
if (s_verboseDebug) {
kDebug(7001).nospace() << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
<< " for " << (sub_entry ? sub_entry->path : QString())
<< " [" << (instance ? instance->objectName() : QString()) << "]";
}
m_mapEntries.remove( e->path ); // <e> not valid any more
}
/* Called from KDirWatch destructor:
* remove <instance> as client from all entries
*/
void KDirWatchPrivate::removeEntries( KDirWatch* instance )
{
int minfreq = 3600000;
QStringList pathList;
// put all entries where instance is a client in list
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it ) {
Client* c = 0;
foreach(Client* client, (*it).m_clients) {
if (client->instance == instance) {
c = client;
break;
}
}
if (c) {
c->count = 1; // forces deletion of instance as client
pathList.append((*it).path);
}
else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
minfreq = (*it).freq;
}
foreach(const QString &path, pathList)
removeEntry(instance, path, 0);
if (minfreq > freq) {
// we can decrease the global polling frequency
freq = minfreq;
if (timer.isActive()) timer.start(freq);
kDebug(7001) << "Poll Freq now" << freq << "msec";
}
}
// instance ==0: stop scanning for all instances
bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
{
int stillWatching = 0;
foreach(Client* client, e->m_clients) {
if (!instance || instance == client->instance)
client->watchingStopped = true;
else if (!client->watchingStopped)
stillWatching += client->count;
}
kDebug(7001) << (instance ? instance->objectName() : QString::fromLatin1("all"))
<< "stopped scanning" << e->path << "(now"
<< stillWatching << "watchers)";
if (stillWatching == 0) {
// if nobody is interested, we don't watch
if ( e->m_mode != INotifyMode ) {
e->m_ctime = invalid_ctime; // invalid
e->m_status = NonExistent;
}
// e->m_status = Normal;
}
return true;
}
// instance ==0: start scanning for all instances
bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
bool notify)
{
int wasWatching = 0, newWatching = 0;
foreach(Client* client, e->m_clients) {
if (!client->watchingStopped)
wasWatching += client->count;
else if (!instance || instance == client->instance) {
client->watchingStopped = false;
newWatching += client->count;
}
}
if (newWatching == 0)
return false;
kDebug(7001) << (instance ? instance->objectName() : QString::fromLatin1("all"))
<< "restarted scanning" << e->path
<< "(now" << wasWatching+newWatching << "watchers)";
// restart watching and emit pending events
int ev = NoChange;
if (wasWatching == 0) {
if (!notify) {
KDE_struct_stat stat_buf;
bool exists = (KDE::stat(e->path, &stat_buf) == 0);
if (exists) {
e->m_ctime = stat_buf.st_ctime;
e->m_status = Normal;
if (s_verboseDebug) {
kDebug(7001) << "Setting status to Normal for" << e << e->path;
}
e->m_nlink = stat_buf.st_nlink;
e->m_ino = stat_buf.st_ino;
// Same as in scanEntry: ensure no subentry in parent dir
removeEntry(0, e->parentDirectory(), e);
}
else {
e->m_ctime = invalid_ctime;
e->m_status = NonExistent;
e->m_nlink = 0;
if (s_verboseDebug) {
kDebug(7001) << "Setting status to NonExistent for" << e << e->path;
}
}
}
e->msecLeft = 0;
ev = scanEntry(e);
}
emitEvent(e,ev);
return true;
}
// instance ==0: stop scanning for all instances
void KDirWatchPrivate::stopScan(KDirWatch* instance)
{
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
stopEntryScan(instance, &(*it));
}
void KDirWatchPrivate::startScan(KDirWatch* instance,
bool notify, bool skippedToo )
{
if (!notify)
resetList(instance,skippedToo);
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
restartEntryScan(instance, &(*it), notify);
// timer should still be running when in polling mode
}
// clear all pending events, also from stopped
void KDirWatchPrivate::resetList( KDirWatch* /*instance*/, bool skippedToo )
{
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it ) {
foreach(Client* client, (*it).m_clients) {
if (!client->watchingStopped || skippedToo)
client->pending = NoChange;
}
}
}
// Return event happened on <e>
//
int KDirWatchPrivate::scanEntry(Entry* e)
{
// Shouldn't happen: Ignore "unknown" notification method
if (e->m_mode == UnknownMode) return NoChange;
if (e->m_mode == FAMMode || e->m_mode == INotifyMode) {
// we know nothing has changed, no need to stat
if(!e->dirty) return NoChange;
e->dirty = false;
}
if (e->m_mode == StatMode) {
// only scan if timeout on entry timer happens;
// e.g. when using 500msec global timer, a entry
// with freq=5000 is only watched every 10th time
e->msecLeft -= freq;
if (e->msecLeft>0) return NoChange;
e->msecLeft += e->freq;
}
KDE_struct_stat stat_buf;
const bool exists = (KDE::stat(e->path, &stat_buf) == 0);
if (exists) {
if (e->m_status == NonExistent) {
// ctime is the 'creation time' on windows, but with qMax
// we get the latest change of any kind, on any platform.
e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
e->m_status = Normal;
e->m_ino = stat_buf.st_ino;
if (s_verboseDebug) {
kDebug(7001) << "Setting status to Normal for just created" << e << e->path;
}
// We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo)
removeEntry(0, e->parentDirectory(), e);
return Created;
}
#if 1 // for debugging the if() below
if (s_verboseDebug) {
struct tm* tmp = localtime(&e->m_ctime);
char outstr[200];
strftime(outstr, sizeof(outstr), "%T", tmp);
kDebug(7001) << "e->m_ctime=" << e->m_ctime << outstr
<< "stat_buf.st_ctime=" << stat_buf.st_ctime
<< "e->m_nlink=" << e->m_nlink
<< "stat_buf.st_nlink=" << stat_buf.st_nlink
<< "e->m_ino=" << e->m_ino
<< "stat_buf.st_ino=" << stat_buf.st_ino;
}
#endif
if ( ((e->m_ctime != invalid_ctime) &&
(qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime ||
stat_buf.st_ino != e->m_ino ||
stat_buf.st_nlink != nlink_t(e->m_nlink)))
// we trust QFSW to get it right, the ctime comparisons above
// fail for example when adding files to directories on Windows
// which doesn't change the mtime of the directory
|| e->m_mode == QFSWatchMode
) {
e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
e->m_nlink = stat_buf.st_nlink;
if (e->m_ino != stat_buf.st_ino) {
// The file got deleted and recreated. We need to watch it again.
removeWatch(e);
addWatch(e);
}
e->m_ino = stat_buf.st_ino;
return Changed;
}
return NoChange;
}
// dir/file doesn't exist
e->m_nlink = 0;
e->m_ino = 0;
e->m_status = NonExistent;
if (e->m_ctime == invalid_ctime) {
return NoChange;
}
e->m_ctime = invalid_ctime;
return Deleted;
}
/* Notify all interested KDirWatch instances about a given event on an entry
* and stored pending events. When watching is stopped, the event is
* added to the pending events.
*/
void KDirWatchPrivate::emitEvent(const Entry* e, int event, const QString &fileName)
{
QString path (e->path);
if (!fileName.isEmpty()) {
if (!QDir::isRelativePath(fileName))
path = fileName;
else {
#ifdef Q_OS_UNIX
path += QLatin1Char('/') + fileName;
#endif
}
}
if (s_verboseDebug) {
kDebug(7001) << event << path << e->m_clients.count() << "clients";
}
foreach(Client* c, e->m_clients)
{
if (c->instance==0 || c->count==0) continue;
if (c->watchingStopped) {
// add event to pending...
if (event == Changed)
c->pending |= event;
else if (event == Created || event == Deleted)
c->pending = event;
continue;
}
// not stopped
if (event == NoChange || event == Changed)
event |= c->pending;
c->pending = NoChange;
if (event == NoChange) continue;
// Emit the signals delayed, to avoid unexpected re-entrancy from the slots (#220153)
if (event & Deleted) {
QMetaObject::invokeMethod(c->instance, "setDeleted", Qt::QueuedConnection, Q_ARG(QString, path));
// emit only Deleted event...
continue;
}
if (event & Created) {
QMetaObject::invokeMethod(c->instance, "setCreated", Qt::QueuedConnection, Q_ARG(QString, path));
// possible emit Change event after creation
}
if (event & Changed) {
QMetaObject::invokeMethod(c->instance, "setDirty", Qt::QueuedConnection, Q_ARG(QString, path));
}
}
}
// Remove entries which were marked to be removed
void KDirWatchPrivate::slotRemoveDelayed()
{
delayRemove = false;
// Removing an entry could also take care of removing its parent
// (e.g. in FAM or inotify mode), which would remove other entries in removeList,
// so don't use foreach or iterators here...
while (!removeList.isEmpty()) {
Entry* entry = *removeList.begin();
removeEntry(0, entry, 0); // this will remove entry from removeList
}
}
/* Scan all entries to be watched for changes. This is done regularly
* when polling. FAM and inotify use a single-shot timer to call this slot delayed.
*/
void KDirWatchPrivate::slotRescan()
{
if (s_verboseDebug)
kDebug(7001);
EntryMap::Iterator it;
// People can do very long things in the slot connected to dirty(),
// like showing a message box. We don't want to keep polling during
// that time, otherwise the value of 'delayRemove' will be reset.
// ### TODO: now the emitEvent delays emission, this can be cleaned up
bool timerRunning = timer.isActive();
if ( timerRunning )
timer.stop();
// We delay deletions of entries this way.
// removeDir(), when called in slotDirty(), can cause a crash otherwise
// ### TODO: now the emitEvent delays emission, this can be cleaned up
delayRemove = true;
if (rescan_all)
{
// mark all as dirty
it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
(*it).dirty = true;
rescan_all = false;
}
else
{
// progate dirty flag to dependant entries (e.g. file watches)
it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty )
(*it).propagate_dirty();
}
#ifdef HAVE_SYS_INOTIFY_H
QList<Entry*> cList;
#endif
it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it ) {
// we don't check invalid entries (i.e. remove delayed)
Entry* entry = &(*it);
if (!entry->isValid()) continue;
const int ev = scanEntry(entry);
if (s_verboseDebug)
kDebug(7001) << "scanEntry for" << entry->path << "says" << ev;
switch(entry->m_mode) {
#ifdef HAVE_SYS_INOTIFY_H
case INotifyMode:
if ( ev == Deleted ) {
if (s_verboseDebug)
kDebug(7001) << "scanEntry says" << entry->path << "was deleted";
addEntry(0, entry->parentDirectory(), entry, true);
} else if (ev == Created) {
if (s_verboseDebug)
kDebug(7001) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd;
if (entry->wd < 0) {
cList.append(entry);
addWatch(entry);
}
}
break;
#endif
case FAMMode:
case QFSWatchMode:
if (ev == Created) {
addWatch(entry);
}
break;
default:
// dunno about StatMode...
break;
}
#ifdef HAVE_SYS_INOTIFY_H
if (entry->isDir) {
// Report and clear the the list of files that have changed in this directory.
// Remove duplicates by changing to set and back again:
// we don't really care about preserving the order of the
// original changes.
QStringList pendingFileChanges = entry->m_pendingFileChanges;
pendingFileChanges.removeDuplicates();
Q_FOREACH(const QString &changedFilename, pendingFileChanges) {
if (s_verboseDebug) {
kDebug(7001) << "processing pending file change for" << changedFilename;
}
emitEvent(entry, Changed, changedFilename);
}
entry->m_pendingFileChanges.clear();
}
#endif
if ( ev != NoChange ) {
emitEvent(entry, ev);
}
}
if ( timerRunning )
timer.start(freq);
#ifdef HAVE_SYS_INOTIFY_H
// Remove watch of parent of new created directories
Q_FOREACH(Entry* e, cList)
removeEntry(0, e->parentDirectory(), e);
#endif
QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
}
bool KDirWatchPrivate::isNoisyFile( const char * filename )
{
// $HOME/.X.err grows with debug output, so don't notify change
if ( *filename == '.') {
if (strncmp(filename, ".X.err", 6) == 0) return true;
if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
// fontconfig updates the cache on every KDE app start
// (inclusive kio_thumbnail slaves)
if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
}
return false;
}
#ifdef HAVE_FAM
void KDirWatchPrivate::famEventReceived()
{
static FAMEvent fe;
delayRemove = true;
//kDebug(7001) << "Fam event received";
while(use_fam && FAMPending(&fc)) {
if (FAMNextEvent(&fc, &fe) == -1) {
kWarning(7001) << "FAM connection problem, switching to polling.";
use_fam = false;
delete sn; sn = 0;
// Replace all FAMMode entries with INotify/Stat
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
Entry* e = &(*it);
addWatch(e);
}
}
else
checkFAMEvent(&fe);
}
QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
}
void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
{
//kDebug(7001);
// Don't be too verbose ;-)
if ((fe->code == FAMExists) ||
(fe->code == FAMEndExist) ||
(fe->code == FAMAcknowledge)) return;
if ( isNoisyFile( fe->filename ) )
return;
Entry* e = 0;
EntryMap::Iterator it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it )
if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
e = &(*it);
break;
}
// Entry* e = static_cast<Entry*>(fe->userdata);
if (s_verboseDebug) { // don't enable this except when debugging, see #88538
kDebug(7001) << "Processing FAM event ("
<< ((fe->code == FAMChanged) ? "FAMChanged" :
(fe->code == FAMDeleted) ? "FAMDeleted" :
(fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
(fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
(fe->code == FAMCreated) ? "FAMCreated" :
(fe->code == FAMMoved) ? "FAMMoved" :
(fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
(fe->code == FAMExists) ? "FAMExists" :
(fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
<< ", " << fe->filename
<< ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ") e=" << e;
}
if (!e) {
// this happens e.g. for FAMAcknowledge after deleting a dir...
// kDebug(7001) << "No entry for FAM event ?!";
return;
}
if (e->m_status == NonExistent) {
kDebug(7001) << "FAM event for nonExistent entry " << e->path;
return;
}
// Delayed handling. This rechecks changes with own stat calls.
e->dirty = true;
if (!rescan_timer.isActive())
rescan_timer.start(m_PollInterval); // singleshot
// needed FAM control actions on FAM events
switch (fe->code) {
case FAMDeleted:
// fe->filename is an absolute path when a watched file-or-dir is deleted
if (!QDir::isRelativePath(QFile::decodeName(fe->filename))) {
FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
kDebug(7001) << "Cancelled FAMReq"
<< FAMREQUEST_GETREQNUM(&(e->fr))
<< "for" << e->path;
e->m_status = NonExistent;
e->m_ctime = invalid_ctime;
emitEvent(e, Deleted, e->path);
// If the parent dir was already watched, tell it something changed
Entry* parentEntry = entry(e->parentDirectory());
if (parentEntry)
parentEntry->dirty = true;
// Add entry to parent dir to notice if the entry gets recreated
addEntry(0, e->parentDirectory(), e, true /*isDir*/);
} else {
// A file in this directory has been removed, and wasn't explicitly watched.
// We could still inform clients, like inotify does? But stat can't.
// For now we just marked e dirty and slotRescan will emit the dir as dirty.
//kDebug(7001) << "Got FAMDeleted for" << QFile::decodeName(fe->filename) << "in" << e->path << ". Absolute path -> NOOP!";
}
break;
case FAMCreated: {
// check for creation of a directory we have to watch
QString tpath(e->path + QLatin1Char('/') + QFile::decodeName(fe->filename));
// This code is very similar to the one in inotifyEventReceived...
Entry* sub_entry = e->findSubEntry(tpath);
if (sub_entry /*&& sub_entry->isDir*/) {
// We were waiting for this new file/dir to be created
emitEvent(sub_entry, Created);
sub_entry->dirty = true;
rescan_timer.start(0); // process this asap, to start watching that dir
} else if (e->isDir && !e->m_clients.empty()) {
bool isDir = false;
const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
Q_FOREACH(Client *client, clients) {
addEntry (client->instance, tpath, 0, isDir,
isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
}
if (!clients.isEmpty()) {
emitEvent(e, Created, tpath);
kDebug(7001).nospace() << clients.count() << " instance(s) monitoring the new "
<< (isDir ? "dir " : "file ") << tpath;
}
}
}
break;
default:
break;
}
}
#else
void KDirWatchPrivate::famEventReceived()
{
kWarning (7001) << "Fam event received but FAM is not supported";
}
#endif
void KDirWatchPrivate::statistics()
{
EntryMap::Iterator it;
kDebug(7001) << "Entries watched:";
if (m_mapEntries.count()==0) {
kDebug(7001) << " None.";
}
else {
it = m_mapEntries.begin();
for( ; it != m_mapEntries.end(); ++it ) {
Entry* e = &(*it);
kDebug(7001) << " " << *e;
foreach(Client* c, e->m_clients) {
QByteArray pending;
if (c->watchingStopped) {
if (c->pending & Deleted) pending += "deleted ";
if (c->pending & Created) pending += "created ";
if (c->pending & Changed) pending += "changed ";
if (!pending.isEmpty()) pending = " (pending: " + pending + ')';
pending = ", stopped" + pending;
}
kDebug(7001) << " by " << c->instance->objectName()
<< " (" << c->count << " times)" << pending;
}
if (e->m_entries.count()>0) {
kDebug(7001) << " dependent entries:";
foreach(Entry *d, e->m_entries) {
kDebug(7001) << " " << d << d->path << (d->m_status == NonExistent ? "NonExistent" : "EXISTS!!! ERROR!");
if (s_verboseDebug) {
Q_ASSERT(d->m_status == NonExistent); // it doesn't belong here otherwise
}
}
}
}
}
}
#ifndef QT_NO_FILESYSTEMWATCHER
// Slot for QFileSystemWatcher
void KDirWatchPrivate::fswEventReceived(const QString &path)
{
if (s_verboseDebug)
kDebug(7001) << path;
EntryMap::Iterator it = m_mapEntries.find(path);
if(it != m_mapEntries.end()) {
Entry* e = &(*it);
e->dirty = true;
const int ev = scanEntry(e);
if (s_verboseDebug)
kDebug(7001) << "scanEntry for" << e->path << "says" << ev;
if (ev != NoChange)
emitEvent(e, ev);
if(ev == Deleted) {
if (e->isDir)
addEntry(0, e->parentDirectory(), e, true);
else
addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
} else if (ev == Created) {
// We were waiting for it to appear; now watch it
addWatch(e);
} else if (e->isDir) {
// Check if any file or dir was created under this directory, that we were waiting for
Q_FOREACH(Entry* sub_entry, e->m_entries) {
fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed
}
}
}
}
#else
void KDirWatchPrivate::fswEventReceived(const QString &path)
{
Q_UNUSED(path);
kWarning (7001) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported";
}
#endif // HAVE_QFILESYSTEMWATCHER
//
// Class KDirWatch
//
K_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf)
KDirWatch* KDirWatch::self()
{
return s_pKDirWatchSelf;
}
// TODO KDE5: is this used anywhere?
bool KDirWatch::exists()
{
return s_pKDirWatchSelf.exists();
}
static void cleanupQFSWatcher()
{
s_pKDirWatchSelf->deleteQFSWatcher();
}
KDirWatch::KDirWatch (QObject* parent)
: QObject(parent), d(createPrivate())
{
static int nameCounter = 0;
nameCounter++;
setObjectName(QString::fromLatin1("KDirWatch-%1").arg(nameCounter) );
d->ref();
d->_isStopped = false;
static bool cleanupRegistered = false;
if (!cleanupRegistered) {
cleanupRegistered = true;
// Must delete QFileSystemWatcher before qApp is gone - bug 261541
qAddPostRoutine(cleanupQFSWatcher);
}
}
KDirWatch::~KDirWatch()
{
d->removeEntries(this);
if ( d->deref() )
{
// delete it if it's the last one
delete d;
dwp_self = 0;
}
}
void KDirWatch::addDir( const QString& _path, WatchModes watchModes)
{
if (d) d->addEntry(this, _path, 0, true, watchModes);
}
void KDirWatch::addFile( const QString& _path )
{
if ( !d )
return;
d->addEntry(this, _path, 0, false);
}
QDateTime KDirWatch::ctime( const QString &_path ) const
{
KDirWatchPrivate::Entry* e = d->entry(_path);
if (!e)
return QDateTime();
return QDateTime::fromTime_t(e->m_ctime);
}
void KDirWatch::removeDir( const QString& _path )
{
if (d) {
if (!d->removeEntry(this, _path, 0)) {
kWarning() << "doesn't know" << _path;
}
}
}
void KDirWatch::removeFile( const QString& _path )
{
if (d) {
if (!d->removeEntry(this, _path, 0)) {
kWarning() << "doesn't know" << _path;
}
}
}
bool KDirWatch::stopDirScan( const QString& _path )
{
if (d) {
KDirWatchPrivate::Entry *e = d->entry(_path);
if (e && e->isDir) return d->stopEntryScan(this, e);
}
return false;
}
bool KDirWatch::restartDirScan( const QString& _path )
{
if (d) {
KDirWatchPrivate::Entry *e = d->entry(_path);
if (e && e->isDir)
// restart without notifying pending events
return d->restartEntryScan(this, e, false);
}
return false;
}
void KDirWatch::stopScan()
{
if (d) {
d->stopScan(this);
d->_isStopped = true;
}
}
bool KDirWatch::isStopped()
{
return d->_isStopped;
}
void KDirWatch::startScan( bool notify, bool skippedToo )
{
if (d) {
d->_isStopped = false;
d->startScan(this, notify, skippedToo);
}
}
bool KDirWatch::contains( const QString& _path ) const
{
KDirWatchPrivate::Entry* e = d->entry(_path);
if (!e)
return false;
foreach(KDirWatchPrivate::Client* client, e->m_clients) {
if (client->instance == this)
return true;
}
return false;
}
void KDirWatch::deleteQFSWatcher()
{
delete d->fsWatcher;
d->fsWatcher = 0;
}
void KDirWatch::statistics()
{
if (!dwp_self) {
kDebug(7001) << "KDirWatch not used";
return;
}
dwp_self->statistics();
}
void KDirWatch::setCreated( const QString & _file )
{
kDebug(7001) << objectName() << "emitting created" << _file;
emit created( _file );
}
void KDirWatch::setDirty( const QString & _file )
{
//kDebug(7001) << objectName() << "emitting dirty" << _file;
emit dirty( _file );
}
void KDirWatch::setDeleted( const QString & _file )
{
kDebug(7001) << objectName() << "emitting deleted" << _file;
emit deleted( _file );
}
KDirWatch::Method KDirWatch::internalMethod()
{
return d->m_preferredMethod;
}
#include "moc_kdirwatch.cpp"
#include "moc_kdirwatch_p.cpp"
//sven