mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
1918 lines
57 KiB
C++
1918 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
|
|
#ifdef HAVE_QFILESYSTEMWATCHER
|
|
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
|
|
#ifdef HAVE_QFILESYSTEMWATCHER
|
|
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
|
|
#ifdef HAVE_QFILESYSTEMWATCHER
|
|
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);
|
|
for (QFileInfoList::const_iterator iter = contents.constBegin();
|
|
iter != contents.constEnd(); ++iter)
|
|
{
|
|
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
|
|
#if defined(HAVE_QFILESYSTEMWATCHER)
|
|
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
|
|
#if defined(HAVE_QFILESYSTEMWATCHER)
|
|
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
|
|
#ifdef HAVE_QFILESYSTEMWATCHER
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_QFILESYSTEMWATCHER
|
|
// 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
|