summaryrefslogtreecommitdiffabout
authorulf69 <ulf69>2004-07-14 14:42:00 (UTC)
committer ulf69 <ulf69>2004-07-14 14:42:00 (UTC)
commit80b35ebb50cac5007c074a4900f518f48f704eac (patch) (side-by-side diff)
tree4286c44a7d789b91ab4651873481b03937bb36af
parent02380b50374340283fe9dd0f804254ee17374347 (diff)
downloadkdepimpi-80b35ebb50cac5007c074a4900f518f48f704eac.zip
kdepimpi-80b35ebb50cac5007c074a4900f518f48f704eac.tar.gz
kdepimpi-80b35ebb50cac5007c074a4900f518f48f704eac.tar.bz2
added the real kdirwatch implementation from kde. Now we can monitor if 3rd parties modified
files or directories
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--microkde/kdirwatch.h14
-rw-r--r--microkde/kio/kio/kdirwatch.cpp1442
-rw-r--r--microkde/kio/kio/kdirwatch.h288
-rw-r--r--microkde/kio/kio/kdirwatch_p.h153
4 files changed, 1883 insertions, 14 deletions
diff --git a/microkde/kdirwatch.h b/microkde/kdirwatch.h
deleted file mode 100644
index 22b4dec..0000000
--- a/microkde/kdirwatch.h
+++ b/dev/null
@@ -1,14 +0,0 @@
-#ifndef MICROKDE_KDIRWATCH_H
-#define MICROKDE_KDIRWATCH_H
-
-#include <qobject.h>
-
-class KDirWatch : public QObject
-{
- public:
- KDirWatch() {}
-
- void addDir( const QString & ) {}
-};
-
-#endif
diff --git a/microkde/kio/kio/kdirwatch.cpp b/microkde/kio/kio/kdirwatch.cpp
new file mode 100644
index 0000000..98d24e0
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch.cpp
@@ -0,0 +1,1442 @@
+// -*- c-basic-offset: 2 -*-
+/* This file is part of the KDE libraries
+ Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/*
+Enhanced Version of the file for platform independent KDE tools.
+Copyright (c) 2004 Ulf Schenk
+
+$Id$
+*/
+
+
+// CHANGES:
+// 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)
+
+
+//US #include <config.h>
+
+#ifdef HAVE_DNOTIFY
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#endif
+
+#include <sys/stat.h>
+#include <assert.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qintdict.h>
+#include <qptrlist.h>
+#include <qsocketnotifier.h>
+#include <qstringlist.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kconfigbase.h>
+#include <kglobal.h>
+#include <kstaticdeleter.h>
+
+#include "kdirwatch.h"
+#include "kdirwatch_p.h"
+//US #include "global.h" // KIO::probably_slow_mounted
+
+#define NO_NOTIFY (time_t) 0
+
+static KDirWatchPrivate* dwp_self = 0;
+
+#ifdef HAVE_DNOTIFY
+
+#include <sys/utsname.h>
+
+static int dnotify_signal = 0;
+
+/* DNOTIFY signal handler
+ *
+ * As this is called asynchronously, only a flag is set and
+ * a rescan is requested.
+ * This is done by writing into a pipe to trigger a QSocketNotifier
+ * watching on this pipe: a timer is started and after a timeout,
+ * the rescan is done.
+ */
+void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
+{
+ // write might change errno, we have to save it and restore it
+ // (Richard Stevens, Advanced programming in the Unix Environment)
+ int saved_errno = errno;
+
+ Entry* e = (dwp_self) ? dwp_self->fd_Entry.find(si->si_fd) :0;
+
+// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
+// << QString(e ? e->path:"unknown") << endl;
+
+ if(!e || e->dn_fd != si->si_fd) {
+ qDebug("fatal error in KDirWatch");
+ } else
+ e->dn_dirty = true;
+
+ char c = 0;
+ write(dwp_self->mPipe[1], &c, 1);
+ errno = saved_errno;
+}
+
+static struct sigaction old_sigio_act;
+/* DNOTIFY SIGIO signal handler
+ *
+ * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
+ */
+void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
+{
+ // write might change errno, we have to save it and restore it
+ // (Richard Stevens, Advanced programming in the Unix Environment)
+ int saved_errno = errno;
+
+ if (dwp_self)
+ dwp_self->rescan_all = true;
+
+ char c = 0;
+ write(dwp_self->mPipe[1], &c, 1);
+
+ errno = saved_errno;
+
+ // Call previous signal handler
+ if (old_sigio_act.sa_flags & SA_SIGINFO)
+ {
+ if (old_sigio_act.sa_sigaction)
+ (*old_sigio_act.sa_sigaction)(sig, si, p);
+ }
+ else
+ {
+ if ((old_sigio_act.sa_handler != SIG_DFL) &&
+ (old_sigio_act.sa_handler != SIG_IGN))
+ (*old_sigio_act.sa_handler)(sig);
+ }
+}
+#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.
+ * - DNOTIFY: In late LINUX 2.3.x, directory notification was
+ * introduced. By opening a directory, you can request for
+ * UNIX signals to be sent to the process when a directory
+ * is changed.
+ */
+
+KDirWatchPrivate::KDirWatchPrivate()
+{
+ timer = new QTimer(this);
+ connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
+ freq = 3600000; // 1 hour as upper bound
+ statEntries = 0;
+ delayRemove = false;
+ m_ref = 0;
+
+//US KConfigGroup config(KGlobal::config(), QCString("DirWatch"));
+//US m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
+//US m_PollInterval = config.readNumEntry("PollInterval", 500);
+ KConfig *config = KGlobal::config();
+ KConfigGroupSaver saver( config, QCString("DirWatch") );
+
+ m_nfsPollInterval = config->readNumEntry("NFSPollInterval", 5000);
+ m_PollInterval = config->readNumEntry("PollInterval", 500);
+
+
+ QString available("Stat");
+
+#ifdef HAVE_FAM
+ // It's possible that FAM server can't be started
+ if (FAMOpen(&fc) ==0) {
+ available += ", FAM";
+ use_fam=true;
+ sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
+ QSocketNotifier::Read, this);
+ connect( sn, SIGNAL(activated(int)),
+ this, SLOT(famEventReceived()) );
+ }
+ else {
+ kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
+ use_fam=false;
+ }
+#endif
+
+#ifdef HAVE_DNOTIFY
+ supports_dnotify = true; // not guilty until proven guilty
+ rescan_all = false;
+ struct utsname uts;
+ int major, minor, patch;
+ if (uname(&uts) < 0)
+ supports_dnotify = false; // *shrug*
+ else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
+ supports_dnotify = false; // *shrug*
+ else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
+ kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
+ supports_dnotify = false;
+ }
+
+ if( supports_dnotify ) {
+ available += ", DNotify";
+
+ pipe(mPipe);
+ fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
+ fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
+ mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
+ connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
+ connect(&mTimer, SIGNAL(timeout()), this, SLOT(slotRescan()));
+ struct sigaction act;
+ act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_SIGINFO;
+#ifdef SA_RESTART
+ act.sa_flags |= SA_RESTART;
+#endif
+ if( dnotify_signal == 0 )
+ dnotify_signal = SIGRTMIN + 8;
+ sigaction(dnotify_signal, &act, NULL);
+
+ act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
+ sigaction(SIGIO, &act, &old_sigio_act);
+ }
+#endif
+
+ kdDebug(7001) << "Available methods: " << available << endl;
+}
+
+/* This should never be called, but doesn't harm */
+KDirWatchPrivate::~KDirWatchPrivate()
+{
+ timer->stop();
+
+ /* remove all entries being watched */
+ removeEntries(0);
+
+#ifdef HAVE_FAM
+ if (use_fam) {
+ FAMClose(&fc);
+ kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
+ }
+#endif
+
+}
+
+#ifdef HAVE_DNOTIFY
+void KDirWatchPrivate::slotActivated()
+{
+ char dummy_buf[100];
+ read(mPipe[0], &dummy_buf, 100);
+
+ if (!mTimer.isActive())
+ mTimer.start(200, true);
+}
+
+/* In DNOTIFY mode, only entries which are marked dirty are scanned.
+ * We first need to mark all yet nonexistant, but possible created
+ * entries as dirty...
+ */
+void KDirWatchPrivate::Entry::propagate_dirty()
+{
+ Entry* sub_entry;
+ for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next())
+ {
+ if (!sub_entry->dn_dirty)
+ {
+ sub_entry->dn_dirty = true;
+ sub_entry->propagate_dirty();
+ }
+ }
+}
+
+#else // !HAVE_DNOTIFY
+// slots always have to be defined...
+void KDirWatchPrivate::slotActivated() {}
+#endif
+
+/* A KDirWatch instance is interested in getting events for
+ * this file/Dir entry.
+ */
+void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
+{
+ Client* client = m_clients.first();
+ for(;client; client = m_clients.next())
+ if (client->instance == instance) break;
+
+ if (client) {
+ client->count++;
+ return;
+ }
+
+ client = new Client;
+ client->instance = instance;
+ client->count = 1;
+ client->watchingStopped = instance->isStopped();
+ client->pending = NoChange;
+
+ m_clients.append(client);
+}
+
+void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
+{
+ Client* client = m_clients.first();
+ for(;client; client = m_clients.next())
+ if (client->instance == instance) break;
+
+ if (client) {
+ client->count--;
+ if (client->count == 0) {
+ m_clients.removeRef(client);
+ delete client;
+ }
+ }
+}
+
+/* get number of clients */
+int KDirWatchPrivate::Entry::clients()
+{
+ int clients = 0;
+ Client* client = m_clients.first();
+ for(;client; client = m_clients.next())
+ clients += client->count;
+
+ return clients;
+}
+
+
+KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
+{
+// we only support absolute paths
+ if (_path.left(1) != "/") {
+ return 0;
+ }
+
+ QString path = _path;
+
+ if ( path.length() > 1 && path.right(1) == "/" )
+ 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->changeInterval(freq);
+ kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
+ }
+}
+
+
+#if defined(HAVE_FAM)
+// setup FAM notification, returns false if not possible
+bool KDirWatchPrivate::useFAM(Entry* e)
+{
+ if (!use_fam) return false;
+
+ e->m_mode = FAMMode;
+
+ if (e->isDir) {
+ if (e->m_status == NonExistent) {
+ // If the directory does not exist we watch the parent directory
+ addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
+ }
+ else {
+ int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
+ &(e->fr), e);
+ if (res<0) {
+ e->m_mode = UnknownMode;
+ use_fam=false;
+ return false;
+ }
+ kdDebug(7001) << " Setup FAM (Req "
+ << FAMREQUEST_GETREQNUM(&(e->fr))
+ << ") for " << e->path << endl;
+ }
+ }
+ else {
+ if (e->m_status == NonExistent) {
+ // If the file does not exist we watch the directory
+ addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
+ }
+ else {
+ int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
+ &(e->fr), e);
+ if (res<0) {
+ e->m_mode = UnknownMode;
+ use_fam=false;
+ return false;
+ }
+
+ kdDebug(7001) << " Setup FAM (Req "
+ << FAMREQUEST_GETREQNUM(&(e->fr))
+ << ") for " << e->path << endl;
+ }
+ }
+
+ // handle FAM events to avoid deadlock
+ // (FAM sends back all files in a directory when monitoring)
+ famEventReceived();
+
+ return true;
+}
+#endif
+
+
+#ifdef HAVE_DNOTIFY
+// setup DNotify notification, returns false if not possible
+bool KDirWatchPrivate::useDNotify(Entry* e)
+{
+ e->dn_fd = 0;
+ if (!supports_dnotify) return false;
+
+ e->m_mode = DNotifyMode;
+
+ if (e->isDir) {
+ e->dn_dirty = false;
+ if (e->m_status == Normal) {
+ int fd = open(QFile::encodeName(e->path).data(), O_RDONLY);
+ // Migrate fd to somewhere above 128. Some libraries have
+ // constructs like:
+ // fd = socket(...)
+ // if (fd > ARBITRARY_LIMIT)
+ // return error;
+ //
+ // Since programs might end up using a lot of KDirWatch objects
+ // for a rather long time the above braindamage could get
+ // triggered.
+ //
+ // By moving the kdirwatch fd's to > 128, calls like socket() will keep
+ // returning fd's < ARBITRARY_LIMIT for a bit longer.
+ int fd2 = fcntl(fd, F_DUPFD, 128);
+ if (fd2 >= 0)
+ {
+ close(fd);
+ fd = fd2;
+ }
+ if (fd<0) {
+ e->m_mode = UnknownMode;
+ return false;
+ }
+
+ int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
+ // if dependant is a file watch, we check for MODIFY & ATTRIB too
+ for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
+ if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
+
+ if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
+ fcntl(fd, F_NOTIFY, mask) < 0) {
+
+ kdDebug(7001) << "Not using Linux Directory Notifications."
+ << endl;
+ supports_dnotify = false;
+ ::close(fd);
+ e->m_mode = UnknownMode;
+ return false;
+ }
+
+ fd_Entry.replace(fd, e);
+ e->dn_fd = fd;
+
+ kdDebug(7001) << " Setup DNotify (fd " << fd
+ << ") for " << e->path << endl;
+ }
+ else { // NotExisting
+ addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
+ }
+ }
+ else { // File
+ // we always watch the directory (DNOTIFY can't watch files alone)
+ // this notifies us about changes of files therein
+ addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
+ }
+
+ return true;
+}
+#endif
+
+
+bool KDirWatchPrivate::useStat(Entry* e)
+{
+//US we have no KIO::probably_slow_mounted. So disable this part
+//US if (KIO::probably_slow_mounted(e->path))
+//US useFreq(e, m_nfsPollInterval);
+//US 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
+ kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
+ }
+ }
+
+ kdDebug(7001) << " Setup Stat (freq " << e->freq
+ << ") for " << e->path << endl;
+
+ 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)
+{
+ QString path = _path;
+ if (path.startsWith("/dev/") || (path == "/dev"))
+ return; // Don't even go there.
+
+ if ( path.length() > 1 && path.right(1) == "/" )
+ 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);
+ kdDebug(7001) << "Added already watched Entry " << path
+ << " (for " << sub_entry->path << ")" << endl;
+#ifdef HAVE_DNOTIFY
+ Entry* e = &(*it);
+ if( e->dn_fd > 0 ) {
+ int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
+ // if dependant is a file watch, we check for MODIFY & ATTRIB too
+ for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
+ if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
+ if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
+ ::close(e->dn_fd);
+ e->m_mode = UnknownMode;
+ fd_Entry.remove(e->dn_fd);
+ e->dn_fd = 0;
+ useStat( e );
+ }
+ }
+#endif
+ }
+ else {
+ (*it).addClient(instance);
+ kdDebug(7001) << "Added already watched Entry " << path
+ << " (now " << (*it).clients() << " clients)"
+ << QString(" [%1]").arg(instance->name()) << endl;
+ }
+ return;
+ }
+
+ // we have a new path to watch
+
+ struct stat stat_buf;
+ bool exists = (stat(QFile::encodeName(path), &stat_buf) == 0);
+
+ Entry newEntry;
+ m_mapEntries.insert( path, newEntry );
+ // the insert does a copy, so we have to use <e> now
+ Entry* e = &(m_mapEntries[path]);
+
+ if (exists) {
+ e->isDir = S_ISDIR(stat_buf.st_mode);
+
+ if (e->isDir && !isDir)
+ qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
+ else if (!e->isDir && isDir)
+ qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
+
+ e->m_ctime = stat_buf.st_ctime;
+ e->m_status = Normal;
+ e->m_nlink = stat_buf.st_nlink;
+ }
+ else {
+ e->isDir = isDir;
+ e->m_ctime = invalid_ctime;
+ e->m_status = NonExistent;
+ e->m_nlink = 0;
+ }
+
+ e->path = path;
+ if (sub_entry)
+ e->m_entries.append(sub_entry);
+ else
+ e->addClient(instance);
+
+ kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
+ << (e->m_status == NonExistent ? " NotExisting" : "")
+ << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
+ << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
+ << endl;
+
+
+ // now setup the notification method
+ e->m_mode = UnknownMode;
+ e->msecLeft = 0;
+
+#if defined(HAVE_FAM)
+ if (useFAM(e)) return;
+#endif
+
+#ifdef HAVE_DNOTIFY
+ if (useDNotify(e)) return;
+#endif
+
+ useStat(e);
+}
+
+
+void KDirWatchPrivate::removeEntry( KDirWatch* instance,
+ const QString& _path, Entry* sub_entry )
+{
+ Entry* e = entry(_path);
+ if (!e) {
+ kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
+ return;
+ }
+
+ if (sub_entry)
+ e->m_entries.removeRef(sub_entry);
+ else
+ e->removeClient(instance);
+
+ if (e->m_clients.count() || e->m_entries.count())
+ return;
+
+ if (delayRemove) {
+ // removeList is allowed to contain any entry at most once
+ if (removeList.findRef(e)==-1)
+ removeList.append(e);
+ // now e->isValid() is false
+ return;
+ }
+
+#ifdef HAVE_FAM
+ if (e->m_mode == FAMMode) {
+ if ( e->m_status == Normal) {
+ FAMCancelMonitor(&fc, &(e->fr) );
+ kdDebug(7001) << "Cancelled FAM (Req "
+ << FAMREQUEST_GETREQNUM(&(e->fr))
+ << ") for " << e->path << endl;
+ }
+ else {
+ if (e->isDir)
+ removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
+ else
+ removeEntry(0, QFileInfo(e->path).dirPath(true), e);
+ }
+ }
+#endif
+
+#ifdef HAVE_DNOTIFY
+ if (e->m_mode == DNotifyMode) {
+ if (!e->isDir) {
+ removeEntry(0, QFileInfo(e->path).dirPath(true), e);
+ }
+ else { // isDir
+ // must close the FD.
+ if ( e->m_status == Normal) {
+ if (e->dn_fd) {
+ ::close(e->dn_fd);
+ fd_Entry.remove(e->dn_fd);
+
+ kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
+ << ") for " << e->path << endl;
+ e->dn_fd = 0;
+
+ }
+ }
+ else {
+ removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
+ }
+ }
+ }
+#endif
+
+ if (e->m_mode == StatMode) {
+ statEntries--;
+ if ( statEntries == 0 ) {
+ timer->stop(); // stop timer if lists are empty
+ kdDebug(7001) << " Stopped Polling Timer" << endl;
+ }
+ }
+
+ kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
+ << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
+ << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
+ << endl;
+ 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 )
+{
+ QPtrList<Entry> list;
+ int minfreq = 3600000;
+
+ // put all entries where instance is a client in list
+ EntryMap::Iterator it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it ) {
+ Client* c = (*it).m_clients.first();
+ for(;c;c=(*it).m_clients.next())
+ if (c->instance == instance) break;
+ if (c) {
+ c->count = 1; // forces deletion of instance as client
+ list.append(&(*it));
+ }
+ else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
+ minfreq = (*it).freq;
+ }
+
+ for(Entry* e=list.first();e;e=list.next())
+ removeEntry(instance, e->path, 0);
+
+ if (minfreq > freq) {
+ // we can decrease the global polling frequency
+ freq = minfreq;
+ if (timer->isActive()) timer->changeInterval(freq);
+ kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
+ }
+}
+
+// instance ==0: stop scanning for all instances
+bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
+{
+ int stillWatching = 0;
+ Client* c = e->m_clients.first();
+ for(;c;c=e->m_clients.next()) {
+ if (!instance || instance == c->instance)
+ c->watchingStopped = true;
+ else if (!c->watchingStopped)
+ stillWatching += c->count;
+ }
+
+ kdDebug(7001) << instance->name() << " stopped scanning " << e->path
+ << " (now " << stillWatching << " watchers)" << endl;
+
+ if (stillWatching == 0) {
+ // if nobody is interested, we don't watch
+ e->m_ctime = invalid_ctime; // invalid
+ // 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;
+ Client* c = e->m_clients.first();
+ for(;c;c=e->m_clients.next()) {
+ if (!c->watchingStopped)
+ wasWatching += c->count;
+ else if (!instance || instance == c->instance) {
+ c->watchingStopped = false;
+ newWatching += c->count;
+ }
+ }
+ if (newWatching == 0)
+ return false;
+
+ kdDebug(7001) << instance->name() << " restarted scanning " << e->path
+ << " (now " << wasWatching+newWatching << " watchers)" << endl;
+
+ // restart watching and emit pending events
+
+ int ev = NoChange;
+ if (wasWatching == 0) {
+ if (!notify) {
+ struct stat stat_buf;
+ bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
+ if (exists) {
+ e->m_ctime = stat_buf.st_ctime;
+ e->m_status = Normal;
+ e->m_nlink = stat_buf.st_nlink;
+ }
+ else {
+ e->m_ctime = invalid_ctime;
+ e->m_status = NonExistent;
+ e->m_nlink = 0;
+ }
+ }
+ 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 ) {
+
+ Client* c = (*it).m_clients.first();
+ for(;c;c=(*it).m_clients.next())
+ if (!c->watchingStopped || skippedToo)
+ c->pending = NoChange;
+ }
+}
+
+// Return event happened on <e>
+//
+int KDirWatchPrivate::scanEntry(Entry* e)
+{
+#ifdef HAVE_FAM
+ // we do not stat entries using FAM
+ if (e->m_mode == FAMMode) return NoChange;
+#endif
+
+ // Shouldn't happen: Ignore "unknown" notification method
+ if (e->m_mode == UnknownMode) return NoChange;
+
+#ifdef HAVE_DNOTIFY
+ if (e->m_mode == DNotifyMode) {
+ // we know nothing has changed, no need to stat
+ if(!e->dn_dirty) return NoChange;
+ e->dn_dirty = false;
+ }
+#endif
+
+ 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;
+ }
+
+ struct stat stat_buf;
+ bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
+ if (exists) {
+
+ if (e->m_status == NonExistent) {
+ e->m_ctime = stat_buf.st_ctime;
+ e->m_status = Normal;
+ e->m_nlink = stat_buf.st_nlink;
+ return Created;
+ }
+
+ if ( (e->m_ctime != invalid_ctime) &&
+ ((stat_buf.st_ctime != e->m_ctime) ||
+// (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
+ (stat_buf.st_nlink != e->m_nlink)) ) {
+ e->m_ctime = stat_buf.st_ctime;
+ e->m_nlink = stat_buf.st_nlink;
+ return Changed;
+ }
+
+ return NoChange;
+ }
+
+ // dir/file doesn't exist
+
+ if (e->m_ctime == invalid_ctime)
+ return NoChange;
+
+ e->m_ctime = invalid_ctime;
+ e->m_nlink = 0;
+ e->m_status = NonExistent;
+
+ 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(Entry* e, int event, const QString &fileName)
+{
+ QString path = e->path;
+ if (!fileName.isEmpty()) {
+ if (fileName[0] == '/')
+ path = fileName;
+ else
+ path += "/" + fileName;
+ }
+
+ Client* c = e->m_clients.first();
+ for(;c;c=e->m_clients.next()) {
+ 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;
+
+ if (event & Deleted) {
+ c->instance->setDeleted(path);
+ // emit only Deleted event...
+ continue;
+ }
+
+ if (event & Created) {
+ c->instance->setCreated(path);
+ // possible emit Change event after creation
+ }
+
+ if (event & Changed)
+ c->instance->setDirty(path);
+ }
+}
+
+// Remove entries which were marked to be removed
+void KDirWatchPrivate::slotRemoveDelayed()
+{
+ Entry* e;
+ delayRemove = false;
+ for(e=removeList.first();e;e=removeList.next())
+ removeEntry(0, e->path, 0);
+ removeList.clear();
+}
+
+/* Scan all entries to be watched for changes. This is done regularly
+ * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
+ */
+void KDirWatchPrivate::slotRescan()
+{
+ 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.
+ 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
+ delayRemove = true;
+
+#ifdef HAVE_DNOTIFY
+ QPtrList<Entry> dList, cList;
+
+ // for DNotify method,
+ if (rescan_all)
+ {
+ // mark all as dirty
+ it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it )
+ (*it).dn_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 == DNotifyMode) && (*it).dn_dirty )
+ (*it).propagate_dirty();
+ }
+
+#endif
+
+ it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it ) {
+ // we don't check invalid entries (i.e. remove delayed)
+ if (!(*it).isValid()) continue;
+
+ int ev = scanEntry( &(*it) );
+
+#ifdef HAVE_DNOTIFY
+ if ((*it).m_mode == DNotifyMode) {
+ if ((*it).isDir && (ev == Deleted)) {
+ dList.append( &(*it) );
+
+ // must close the FD.
+ if ((*it).dn_fd) {
+ ::close((*it).dn_fd);
+ fd_Entry.remove((*it).dn_fd);
+ (*it).dn_fd = 0;
+ }
+ }
+
+ else if ((*it).isDir && (ev == Created)) {
+ // For created, but yet without DNOTIFYing ...
+ if ( (*it).dn_fd == 0) {
+ cList.append( &(*it) );
+ if (! useDNotify( &(*it) )) {
+ // if DNotify setup fails...
+ useStat( &(*it) );
+ }
+ }
+ }
+ }
+#endif
+
+ if ( ev != NoChange )
+ emitEvent( &(*it), ev);
+ }
+
+
+#ifdef HAVE_DNOTIFY
+ // Scan parent of deleted directories for new creation
+ Entry* e;
+ for(e=dList.first();e;e=dList.next())
+ addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
+
+ // Remove watch of parent of new created directories
+ for(e=cList.first();e;e=cList.next())
+ removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
+#endif
+
+ if ( timerRunning )
+ timer->start(freq);
+
+ QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
+}
+
+#ifdef HAVE_FAM
+void KDirWatchPrivate::famEventReceived()
+{
+ static FAMEvent fe;
+
+ delayRemove = true;
+
+ while(use_fam && FAMPending(&fc)) {
+ if (FAMNextEvent(&fc, &fe) == -1) {
+ kdWarning(7001) << "FAM connection problem, switching to polling."
+ << endl;
+ use_fam = false;
+ delete sn; sn = 0;
+
+ // Replace all FAMMode entries with DNotify/Stat
+ EntryMap::Iterator it;
+ it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it )
+ if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
+#ifdef HAVE_DNOTIFY
+ if (useDNotify( &(*it) )) continue;
+#endif
+ useStat( &(*it) );
+ }
+ }
+ else
+ checkFAMEvent(&fe);
+ }
+
+ QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
+}
+
+void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
+{
+ // Don't be too verbose ;-)
+ if ((fe->code == FAMExists) ||
+ (fe->code == FAMEndExist) ||
+ (fe->code == FAMAcknowledge)) return;
+
+ // $HOME/.X.err grows with debug output, so don't notify change
+ if ( *(fe->filename) == '.') {
+ if (strncmp(fe->filename, ".X.err", 6) == 0) return;
+ if (strncmp(fe->filename, ".xsession-errors", 16) == 0) 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);
+
+ kdDebug(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))
+ << ")" << endl;
+
+ if (!e) {
+ // this happens e.g. for FAMAcknowledge after deleting a dir...
+ // kdDebug(7001) << "No entry for FAM event ?!" << endl;
+ return;
+ }
+
+ if (e->m_status == NonExistent) {
+ kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
+ return;
+ }
+
+ if (e->isDir)
+ switch (fe->code)
+ {
+ case FAMDeleted:
+ // file absolute: watched dir
+ if (fe->filename[0] == '/')
+ {
+ // a watched directory was deleted
+
+ e->m_status = NonExistent;
+ FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
+ kdDebug(7001) << "Cancelled FAMReq "
+ << FAMREQUEST_GETREQNUM(&(e->fr))
+ << " for " << e->path << endl;
+ // Scan parent for a new creation
+ addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
+ }
+ emitEvent(e, Deleted, QFile::decodeName(fe->filename));
+ break;
+
+ case FAMCreated: {
+ // check for creation of a directory we have to watch
+ Entry *sub_entry = e->m_entries.first();
+ for(;sub_entry; sub_entry = e->m_entries.next())
+ if (sub_entry->path == e->path + "/" + fe->filename) break;
+ if (sub_entry && sub_entry->isDir) {
+ QString path = e->path;
+ removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
+ sub_entry->m_status = Normal;
+ if (!useFAM(sub_entry))
+ useStat(sub_entry);
+
+ emitEvent(sub_entry, Created);
+ }
+ else emitEvent(e, Created, QFile::decodeName(fe->filename));
+ break;
+ }
+
+ case FAMChanged:
+ emitEvent(e, Changed, QFile::decodeName(fe->filename));
+
+ default:
+ break;
+ }
+ else switch (fe->code)
+ {
+ case FAMCreated: emitEvent(e, Created);
+ break;
+ case FAMDeleted: emitEvent(e, Deleted);
+ break;
+ case FAMChanged: emitEvent(e, Changed);
+ break;
+ default: break;
+ }
+}
+#else
+void KDirWatchPrivate::famEventReceived() {}
+#endif
+
+
+void KDirWatchPrivate::statistics()
+{
+ EntryMap::Iterator it;
+
+ kdDebug(7001) << "Entries watched:" << endl;
+ if (m_mapEntries.count()==0) {
+ kdDebug(7001) << " None." << endl;
+ }
+ else {
+ it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it ) {
+ Entry* e = &(*it);
+ kdDebug(7001) << " " << e->path << " ("
+ << ((e->m_status==Normal)?"":"Nonexistent ")
+ << (e->isDir ? "Dir":"File") << ", using "
+ << ((e->m_mode == FAMMode) ? "FAM" :
+ (e->m_mode == DNotifyMode) ? "DNotify" :
+ (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
+ << ")" << endl;
+
+ Client* c = e->m_clients.first();
+ for(;c; c = e->m_clients.next()) {
+ QString 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;
+ }
+ kdDebug(7001) << " by " << c->instance->name()
+ << " (" << c->count << " times)"
+ << pending << endl;
+ }
+ if (e->m_entries.count()>0) {
+ kdDebug(7001) << " dependent entries:" << endl;
+ Entry* d = e->m_entries.first();
+ for(;d; d = e->m_entries.next()) {
+ kdDebug(7001) << " " << d->path << endl;
+ }
+ }
+ }
+ }
+}
+
+
+//
+// Class KDirWatch
+//
+
+static KStaticDeleter<KDirWatch> sd_dw;
+KDirWatch* KDirWatch::s_pSelf = 0L;
+
+KDirWatch* KDirWatch::self()
+{
+ if ( !s_pSelf ) {
+//US sd_dw.setObject( s_pSelf, new KDirWatch );
+ s_pSelf = sd_dw.setObject( new KDirWatch );
+ }
+
+ return s_pSelf;
+}
+
+bool KDirWatch::exists()
+{
+ return s_pSelf != 0;
+}
+
+KDirWatch::KDirWatch (QObject* parent, const char* name)
+ : QObject(parent,name)
+{
+ if (!name) {
+ static int nameCounter = 0;
+
+ nameCounter++;
+ setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
+ }
+
+ if (!dwp_self)
+ dwp_self = new KDirWatchPrivate;
+ d = dwp_self;
+ d->ref();
+
+ _isStopped = false;
+}
+
+KDirWatch::~KDirWatch()
+{
+ if (d) d->removeEntries(this);
+ if ( d->deref() )
+ {
+ // delete it if it's the last one
+ delete d;
+ dwp_self = 0L;
+ }
+}
+
+
+// TODO: add watchFiles/recursive support
+void KDirWatch::addDir( const QString& _path,
+ bool watchFiles, bool recursive)
+{
+ if (watchFiles || recursive) {
+ kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
+ << endl;
+ }
+ if (d) d->addEntry(this, _path, 0, true);
+}
+
+void KDirWatch::addFile( const QString& _path )
+{
+ if (d) d->addEntry(this, _path, 0, false);
+}
+
+QDateTime KDirWatch::ctime( const QString &_path )
+{
+ KDirWatchPrivate::Entry* e = d->entry(_path);
+
+ if (!e)
+ return QDateTime();
+
+ QDateTime result;
+ result.setTime_t(e->m_ctime);
+ return result;
+}
+
+void KDirWatch::removeDir( const QString& _path )
+{
+ if (d) d->removeEntry(this, _path, 0);
+}
+
+void KDirWatch::removeFile( const QString& _path )
+{
+ if (d) d->removeEntry(this, _path, 0);
+}
+
+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);
+ _isStopped = true;
+}
+
+void KDirWatch::startScan( bool notify, bool skippedToo )
+{
+ _isStopped = false;
+ if (d) d->startScan(this, notify, skippedToo);
+}
+
+
+bool KDirWatch::contains( const QString& _path ) const
+{
+ KDirWatchPrivate::Entry* e = d->entry(_path);
+ if (!e)
+ return false;
+
+ KDirWatchPrivate::Client* c = e->m_clients.first();
+ for(;c;c=e->m_clients.next())
+ if (c->instance == this) return true;
+
+ return false;
+}
+
+void KDirWatch::statistics()
+{
+ if (!dwp_self) {
+ kdDebug(7001) << "KDirWatch not used" << endl;
+ return;
+ }
+ dwp_self->statistics();
+}
+
+
+void KDirWatch::setCreated( const QString & _file )
+{
+ kdDebug(7001) << name() << " emitting created " << _file << endl;
+ emit created( _file );
+}
+
+void KDirWatch::setDirty( const QString & _file )
+{
+ kdDebug(7001) << name() << " emitting dirty " << _file << endl;
+ emit dirty( _file );
+}
+
+void KDirWatch::setDeleted( const QString & _file )
+{
+ kdDebug(7001) << name() << " emitting deleted " << _file << endl;
+ emit deleted( _file );
+}
+
+//US #include "kdirwatch.moc"
+//US #include "kdirwatch_p.moc"
+
+//sven
+
+// vim: sw=2 ts=8 et
diff --git a/microkde/kio/kio/kdirwatch.h b/microkde/kio/kio/kdirwatch.h
new file mode 100644
index 0000000..bee30c2
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch.h
@@ -0,0 +1,288 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
+
+ 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., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+*/
+
+/*
+Enhanced Version of the file for platform independent KDE tools.
+Copyright (c) 2004 Ulf Schenk
+
+$Id$
+*/
+
+#ifndef _KDIRWATCH_H
+#define _KDIRWATCH_H
+
+#include <qtimer.h>
+#include <qdatetime.h>
+#include <qmap.h>
+
+#define kdirwatch KDirWatch::self()
+
+class KDirWatchPrivate;
+
+ /**
+ * Watch directories and files for changes.
+ * The watched directories or files don't have to exist yet.
+ *
+ * When a watched directory is changed, i.e. when files therein are
+ * created or deleted, KDirWatch will emit the signal @ref dirty().
+ *
+ * When a watched, but previously not existing directory gets created,
+ * KDirWatch will emit the signal @ref created().
+ *
+ * When a watched directory gets deleted, KDirWatch will emit the
+ * signal @ref deleted(). The directory is still watched for new
+ * creation.
+ *
+ * When a watched file is changed, i.e. attributes changed or written
+ * to, KDirWatch will emit the signal @ref dirty().
+ *
+ * Scanning of particular directories or files can be stopped temporarily
+ * and restarted. The whole class can be stopped and restarted.
+ * Directories and files can be added/removed from the list in any state.
+ *
+ * The implementation uses the FAM service when available;
+ * if FAM is not available, the DNOTIFY functionality is used on LINUX.
+ * As a last resort, a regular polling for change of modification times
+ * is done; the polling interval is a global config option:
+ * DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted
+ * directories.
+ *
+ * @see self()
+ * @short Class for watching directory and file changes.
+ * @author Sven Radej <sven@lisa.exp.univie.ac.at>
+ */
+class KDirWatch : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor.
+ *
+ * Scanning begins immediatly when a dir/file watch
+ * is added.
+ * @param parent the parent of the QObject (or 0 for parent-less KDataTools)
+ * @param name the name of the QObject, can be 0
+ */
+ KDirWatch (QObject* parent = 0, const char* name = 0);
+
+ /**
+ * Destructor.
+ *
+ * Stops scanning and cleans up.
+ */
+ ~KDirWatch();
+
+ /**
+ * Adds a directory to be watched.
+ *
+ * The directory does not have to exist. When @p watchFiles is
+ * false (the default), the signals dirty(), created(), deleted()
+ * can be emitted, all for the watched directory.
+ * When @p watchFiles is true, all files in the watched directory
+ * are watched for changes, too. Thus, the signals dirty(),
+ * created(), deleted() can be emitted.
+ *
+ * @param path the path to watch
+ * @param watchFiles if true, the KDirWatch will also watch files
+ * @param recursive if true, all sub directories are also watched
+ */
+ void addDir(const QString& path,
+ bool watchFiles = false, bool recursive = false);
+
+ /**
+ * Adds a file to be watched.
+ * @param file the file to watch
+ */
+ void addFile(const QString& file);
+
+ /**
+ * Returns the time the directory/file was last changed.
+ * @param file the file to check
+ * @return the date of the last modification
+ */
+ QDateTime ctime(const QString& path);
+
+ /**
+ * Removes a directory from the list of scanned directories.
+ *
+ * If specified path is not in the list this does nothing.
+ * @param dir the path of the dir to be removed from the list
+ */
+ void removeDir(const QString& path);
+
+ /**
+ * Removes a file from the list of watched files.
+ *
+ * If specified path is not in the list this does nothing.
+ * @param file the file to be removed from the list
+ */
+ void removeFile(const QString& file);
+
+ /**
+ * Stops scanning the specified path.
+ *
+ * The @p path is not deleted from the interal just, it is just skipped.
+ * Call this function when you perform an huge operation
+ * on this directory (copy/move big files or many files). When finished,
+ * call @ref restartDirScan(path).
+ *
+ * @param path the path to skip
+ * @return true if the @p path is being watched, otherwise false
+ * @see restartDirScanning()
+ */
+ bool stopDirScan(const QString& path);
+
+ /**
+ * Restarts scanning for specified path.
+ *
+ * Resets ctime. It doesn't notify
+ * the change (by emitted a signal), since the ctime value is reset.
+ *
+ * Call it when you are finished with big operations on that path,
+ * @em and when @em you have refreshed that path.
+ *
+ * @param path the path to restart scanning
+ * @return true if the @p path is being watched, otherwise false
+ * @see stopDirScanning()
+ */
+ bool restartDirScan(const QString& path);
+
+ /**
+ * Starts scanning of all dirs in list.
+ *
+ * @param notify If true, all changed directories (since @ref
+ * stopScan() call) will be notified for refresh. If notify is
+ * false, all ctimes will be reset (except those who are stopped,
+ * but only if @p skippedToo is false) and changed dirs won't be
+ * notified. You can start scanning even if the list is
+ * empty. First call should be called with @p false or else all
+ * directories
+ * in list will be notified.
+ * @param skippedToo if true, the skipped directoris (scanning of which was
+ * stopped with @ref stopDirScan() ) will be reset and notified
+ * for change. Otherwise, stopped directories will continue to be
+ * unnotified.
+ */
+ void startScan( bool notify=false, bool skippedToo=false );
+
+ /**
+ * Stops scanning of all directories in internal list.
+ *
+ * The timer is stopped, but the list is not cleared.
+ */
+ void stopScan();
+
+ /**
+ * Is scanning stopped?
+ * After creation of a KDirWatch instance, this is false.
+ * @return true when scanning stopped
+ */
+ bool isStopped() { return _isStopped; }
+
+ /**
+ * Check if a directory is being watched by this KDirWatch instance
+ * @param path the directory to check
+ * @return true if the directory is being watched
+ */
+ bool contains( const QString& path ) const;
+
+ /**
+ * Dump statistic information about all KDirWatch instances.
+ * This checks for consistency, too.
+ */
+ static void statistics();
+
+ /**
+ * Emits @ref created().
+ * @param path the path of the file or directory
+ */
+ void setCreated( const QString &path );
+ /**
+ * Emits @ref dirty().
+ * @param path the path of the file or directory
+ */
+ void setDirty( const QString &path );
+ /**
+ * Emits @ref deleted().
+ * @param path the path of the file or directory
+ */
+ void setDeleted( const QString &path );
+
+ /**
+ * The KDirWatch instance usually globally used in an application.
+ * It is automatically deleted when the application exits.
+ *
+ * However, you can create an arbitrary number of KDirWatch instances
+ * aside from this one - for those you have to take care of memory management.
+ *
+ * This function returns an instance of KDirWatch. If there is none, it
+ * will be created.
+ *
+ * @return a KDirWatch instance
+ */
+ static KDirWatch* self();
+ /**
+ * Returns true if there is an instance of KDirWatch.
+ * @return true if there is an instance of KDirWatch.
+ * @see KDirWatch::self()
+ * @since 3.1
+ */
+ static bool exists();
+
+ signals:
+
+ /**
+ * Emitted when a watched object is changed.
+ * For a directory this signal is emitted when files
+ * therein are created or deleted.
+ * For a file this signal is emitted when its size or attributes change.
+ *
+ * When you watch a directory, changes in the size or attributes of
+ * contained files may or may not trigger this signal to be emitted
+ * depending on which backend is used by KDirWatch.
+ *
+ * The new ctime is set before the signal is emitted.
+ * @param path the path of the file or directory
+ */
+ void dirty (const QString &path);
+
+ /**
+ * Emitted when a file or directory is created.
+ * @param path the path of the file or directory
+ */
+ void created (const QString &path );
+
+ /**
+ * Emitted when a file or directory is deleted.
+ *
+ * The object is still watched for new creation.
+ * @param path the path of the file or directory
+ */
+ void deleted (const QString &path );
+
+ private:
+ bool _isStopped;
+
+ KDirWatchPrivate *d;
+ static KDirWatch* s_pSelf;
+};
+
+#endif
+
+// vim: sw=3 et
diff --git a/microkde/kio/kio/kdirwatch_p.h b/microkde/kio/kio/kdirwatch_p.h
new file mode 100644
index 0000000..0ab482f
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch_p.h
@@ -0,0 +1,153 @@
+/* Private Header for class of KDirWatchPrivate
+ *
+ * this separate header file is needed for MOC processing
+ * because KDirWatchPrivate has signals and slots
+ */
+
+/*
+Enhanced Version of the file for platform independent KDE tools.
+Copyright (c) 2004 Ulf Schenk
+
+$Id$
+*/
+
+#ifndef _KDIRWATCH_P_H
+#define _KDIRWATCH_P_H
+
+#ifdef HAVE_FAM
+#include <fam.h>
+#endif
+
+#include <qptrlist.h>
+
+#include <kdirwatch.h>
+
+#include <ctime>
+
+#define invalid_ctime ((time_t)-1)
+
+/* KDirWatchPrivate is a singleton and does the watching
+ * for every KDirWatch instance in the application.
+ */
+class KDirWatchPrivate : public QObject
+{
+ Q_OBJECT
+public:
+
+ enum entryStatus { Normal = 0, NonExistent };
+ enum entryMode { UnknownMode = 0, StatMode, DNotifyMode, FAMMode };
+ enum { NoChange=0, Changed=1, Created=2, Deleted=4 };
+
+ struct Client {
+ KDirWatch* instance;
+ int count;
+ // did the instance stop watching
+ bool watchingStopped;
+ // events blocked when stopped
+ int pending;
+ };
+
+ class Entry
+ {
+ public:
+ // the last observed modification time
+ time_t m_ctime;
+ // the last observed link count
+ int m_nlink;
+ entryStatus m_status;
+ entryMode m_mode;
+ bool isDir;
+ // instances interested in events
+ QPtrList<Client> m_clients;
+ // nonexistent entries of this directory
+ QPtrList<Entry> m_entries;
+ QString path;
+
+ int msecLeft, freq;
+
+ void addClient(KDirWatch*);
+ void removeClient(KDirWatch*);
+ int clients();
+ bool isValid() { return m_clients.count() || m_entries.count(); }
+
+#ifdef HAVE_FAM
+ FAMRequest fr;
+#endif
+
+#ifdef HAVE_DNOTIFY
+ int dn_fd;
+ bool dn_dirty;
+ void propagate_dirty();
+#endif
+ };
+
+ typedef QMap<QString,Entry> EntryMap;
+
+ KDirWatchPrivate();
+ ~KDirWatchPrivate();
+
+ void resetList (KDirWatch*,bool);
+ void useFreq(Entry* e, int newFreq);
+ void addEntry(KDirWatch*,const QString&, Entry*, bool);
+ void removeEntry(KDirWatch*,const QString&, Entry*);
+ bool stopEntryScan(KDirWatch*, Entry*);
+ bool restartEntryScan(KDirWatch*, Entry*, bool );
+ void stopScan(KDirWatch*);
+ void startScan(KDirWatch*, bool, bool);
+
+ void removeEntries(KDirWatch*);
+ void statistics();
+
+ Entry* entry(const QString&);
+ int scanEntry(Entry* e);
+ void emitEvent(Entry* e, int event, const QString &fileName = QString::null);
+
+ // Memory management - delete when last KDirWatch gets deleted
+ void ref() { m_ref++; }
+ bool deref() { return ( --m_ref == 0 ); }
+
+public slots:
+ void slotRescan();
+ void famEventReceived(); // for FAM
+ void slotActivated(); // for DNOTIFY
+ void slotRemoveDelayed();
+
+public:
+ QTimer *timer;
+ EntryMap m_mapEntries;
+
+private:
+ int freq;
+ int statEntries;
+ int m_nfsPollInterval, m_PollInterval;
+ int m_ref;
+ bool useStat(Entry*);
+
+ bool delayRemove;
+ QPtrList<Entry> removeList;
+
+#ifdef HAVE_FAM
+ QSocketNotifier *sn;
+ FAMConnection fc;
+ bool use_fam;
+
+ void checkFAMEvent(FAMEvent*);
+ bool useFAM(Entry*);
+#endif
+
+#ifdef HAVE_DNOTIFY
+ bool supports_dnotify;
+ bool rescan_all;
+ int mPipe[2];
+ QTimer mTimer;
+ QSocketNotifier *mSn;
+ QIntDict<Entry> fd_Entry;
+
+ static void dnotify_handler(int, siginfo_t *si, void *);
+ static void dnotify_sigio_handler(int, siginfo_t *si, void *);
+ bool useDNotify(Entry*);
+#endif
+};
+
+#endif // KDIRWATCH_P_H
+