summaryrefslogtreecommitdiffstats
path: root/tdecore
diff options
context:
space:
mode:
Diffstat (limited to 'tdecore')
-rw-r--r--tdecore/CMakeLists.txt2
-rw-r--r--tdecore/ksimpledirwatch.cpp1755
-rw-r--r--tdecore/ksimpledirwatch.h293
-rw-r--r--tdecore/ksimpledirwatch_p.h161
-rw-r--r--tdecore/tdehardwaredevices.cpp277
-rw-r--r--tdecore/tdehardwaredevices.h110
6 files changed, 2589 insertions, 9 deletions
diff --git a/tdecore/CMakeLists.txt b/tdecore/CMakeLists.txt
index 422a1f431..f96c212d6 100644
--- a/tdecore/CMakeLists.txt
+++ b/tdecore/CMakeLists.txt
@@ -123,7 +123,7 @@ set( ${target}_SRCS
ktempdir.cpp kshell.cpp kmountpoint.cpp kcalendarsystemjalali.cpp
kprotocolinfo_tdecore.cpp kprotocolinfofactory.cpp kxerrorhandler.cpp
kuser.cpp kconfigskeleton.cpp kconfigdialogmanager.cpp klockfile.cpp
- kqiodevicegzip_p.cpp ktimezones.cpp tdehardwaredevices.cpp
+ kqiodevicegzip_p.cpp ktimezones.cpp tdehardwaredevices.cpp ksimpledirwatch.cpp
)
tde_add_library( ${target} SHARED AUTOMOC
diff --git a/tdecore/ksimpledirwatch.cpp b/tdecore/ksimpledirwatch.cpp
new file mode 100644
index 000000000..cad0345ab
--- /dev/null
+++ b/tdecore/ksimpledirwatch.cpp
@@ -0,0 +1,1755 @@
+// -*- c-basic-offset: 2 -*-
+/* This file is part of the KDE libraries
+ Copyright (C) 1998 Sven Radej <[email protected]>
+
+ 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.
+*/
+
+
+// KSimpleDirWatch is a basic copy of KDirWatch
+// but with the KIO linking requirement removed
+
+#include <config.h>
+#include <errno.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 <tqdir.h>
+#include <tqfile.h>
+#include <tqintdict.h>
+#include <tqptrlist.h>
+#include <tqsocketnotifier.h>
+#include <tqstringlist.h>
+#include <tqtimer.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <kstaticdeleter.h>
+#include <kde_file.h>
+
+// debug
+#include <sys/ioctl.h>
+
+#ifdef HAVE_INOTIFY
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <linux/types.h>
+// Linux kernel headers are documented to not compile
+#define _S390_BITOPS_H
+#include <sys/inotify.h>
+
+#ifndef __NR_inotify_init
+#if defined(__i386__)
+#define __NR_inotify_init 291
+#define __NR_inotify_add_watch 292
+#define __NR_inotify_rm_watch 293
+#endif
+#if defined(__PPC__)
+#define __NR_inotify_init 275
+#define __NR_inotify_add_watch 276
+#define __NR_inotify_rm_watch 277
+#endif
+#if defined(__x86_64__)
+#define __NR_inotify_init 253
+#define __NR_inotify_add_watch 254
+#define __NR_inotify_rm_watch 255
+#endif
+#endif
+
+#ifndef IN_ONLYDIR
+#define IN_ONLYDIR 0x01000000
+#endif
+
+#ifndef IN_DONT_FOLLOW
+#define IN_DONT_FOLLOW 0x02000000
+#endif
+
+#ifndef IN_MOVE_SELF
+#define IN_MOVE_SELF 0x00000800
+#endif
+
+#endif
+
+#include <sys/utsname.h>
+
+#include "ksimpledirwatch.h"
+#include "ksimpledirwatch_p.h"
+
+#define NO_NOTIFY (time_t) 0
+
+static KSimpleDirWatchPrivate* dwp_self = 0;
+
+#ifdef HAVE_DNOTIFY
+
+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 TQSocketNotifier
+ * watching on this pipe: a timer is started and after a timeout,
+ * the rescan is done.
+ */
+void KSimpleDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
+{
+ if (!dwp_self) return;
+
+ // 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->fd_Entry.find(si->si_fd);
+
+// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
+// << TQString(e ? e->path:"unknown") << endl;
+
+ if(e && e->dn_fd == si->si_fd)
+ e->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 KSimpleDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
+{
+ if (dwp_self)
+ {
+ // write might change errno, we have to save it and restore it
+ // (Richard Stevens, Advanced programming in the Unix Environment)
+ int saved_errno = errno;
+
+ 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 KSimpleDirWatchPrivate (singleton)
+//
+
+/* All entries (files/directories) to be watched in the
+ * application (coming from multiple KSimpleDirWatch instances)
+ * are registered in a single KSimpleDirWatchPrivate 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: TQFileInfo.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.
+ * - 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.
+ */
+
+KSimpleDirWatchPrivate::KSimpleDirWatchPrivate()
+ : rescan_timer(0, "KSimpleDirWatchPrivate::rescan_timer")
+{
+ timer = new TQTimer(this, "KSimpleDirWatchPrivate::timer");
+ connect (timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotRescan()));
+ freq = 3600000; // 1 hour as upper bound
+ statEntries = 0;
+ delayRemove = false;
+ m_ref = 0;
+
+ KConfigGroup config(KGlobal::config(), TQCString("DirWatch"));
+ m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
+ m_PollInterval = config.readNumEntry("PollInterval", 500);
+
+ TQString available("Stat");
+
+ // used for FAM and DNOTIFY
+ rescan_all = false;
+ connect(&rescan_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotRescan()));
+
+#ifdef HAVE_FAM
+ // It's possible that FAM server can't be started
+ if (FAMOpen(&fc) ==0) {
+ available += ", FAM";
+ use_fam=true;
+ sn = new TQSocketNotifier( FAMCONNECTION_GETFD(&fc),
+ TQSocketNotifier::Read, this);
+ connect( sn, TQT_SIGNAL(activated(int)),
+ this, TQT_SLOT(famEventReceived()) );
+ }
+ else {
+ kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
+ use_fam=false;
+ }
+#endif
+
+#ifdef HAVE_INOTIFY
+ supports_inotify = true;
+
+ m_inotify_fd = inotify_init();
+
+ if ( m_inotify_fd <= 0 ) {
+ kdDebug(7001) << "Can't use Inotify, kernel doesn't support it" << endl;
+ supports_inotify = false;
+ }
+
+ {
+ struct utsname uts;
+ int major, minor, patch;
+ if (uname(&uts) < 0)
+ supports_inotify = false; // *shrug*
+ else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
+ supports_inotify = false; // *shrug*
+ else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
+ kdDebug(7001) << "Can't use INotify, Linux kernel too old" << endl;
+ supports_inotify = false;
+ }
+ }
+
+ if ( supports_inotify ) {
+ available += ", Inotify";
+ fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
+
+ mSn = new TQSocketNotifier( m_inotify_fd, TQSocketNotifier::Read, this );
+ connect( mSn, TQT_SIGNAL(activated( int )), this, TQT_SLOT( slotActivated() ) );
+ }
+#endif
+
+#ifdef HAVE_DNOTIFY
+
+ // if we have inotify, disable dnotify.
+#ifdef HAVE_INOTIFY
+ supports_dnotify = !supports_inotify;
+#else
+ // otherwise, not guilty until proven guilty.
+ supports_dnotify = true;
+#endif
+
+ 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);
+ fcntl(mPipe[0], F_SETFL, O_NONBLOCK | fcntl(mPipe[0], F_GETFL));
+ fcntl(mPipe[1], F_SETFL, O_NONBLOCK | fcntl(mPipe[1], F_GETFL));
+ mSn = new TQSocketNotifier( mPipe[0], TQSocketNotifier::Read, this);
+ connect(mSn, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotActivated()));
+ // Install the signal handler only once
+ if ( dnotify_signal == 0 )
+ {
+ dnotify_signal = SIGRTMIN + 8;
+
+ struct sigaction act;
+ act.sa_sigaction = KSimpleDirWatchPrivate::dnotify_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_SIGINFO;
+#ifdef SA_RESTART
+ act.sa_flags |= SA_RESTART;
+#endif
+ sigaction(dnotify_signal, &act, NULL);
+
+ act.sa_sigaction = KSimpleDirWatchPrivate::dnotify_sigio_handler;
+ sigaction(SIGIO, &act, &old_sigio_act);
+ }
+ }
+ else
+ {
+ mPipe[0] = -1;
+ mPipe[1] = -1;
+ }
+#endif
+
+ kdDebug(7001) << "Available methods: " << available << endl;
+}
+
+/* This is called on app exit (KStaticDeleter) */
+KSimpleDirWatchPrivate::~KSimpleDirWatchPrivate()
+{
+ timer->stop();
+
+ /* remove all entries being watched */
+ removeEntries(0);
+
+#ifdef HAVE_FAM
+ if (use_fam) {
+ FAMClose(&fc);
+ kdDebug(7001) << "KSimpleDirWatch deleted (FAM closed)" << endl;
+ }
+#endif
+#ifdef HAVE_INOTIFY
+ if ( supports_inotify )
+ ::close( m_inotify_fd );
+#endif
+#ifdef HAVE_DNOTIFY
+ close(mPipe[0]);
+ close(mPipe[1]);
+#endif
+}
+
+#include <stdlib.h>
+
+void KSimpleDirWatchPrivate::slotActivated()
+{
+#ifdef HAVE_DNOTIFY
+ if ( supports_dnotify )
+ {
+ char dummy_buf[4096];
+ read(mPipe[0], &dummy_buf, 4096);
+
+ if (!rescan_timer.isActive())
+ rescan_timer.start(m_PollInterval, true /* singleshot */);
+
+ return;
+ }
+#endif
+
+#ifdef HAVE_INOTIFY
+ if ( !supports_inotify )
+ return;
+
+ int pending = -1;
+ int offset = 0;
+ char buf[4096];
+ assert( m_inotify_fd > -1 );
+ ioctl( m_inotify_fd, FIONREAD, &pending );
+
+ while ( pending > 0 ) {
+
+ if ( pending > (int)sizeof( buf ) )
+ pending = sizeof( buf );
+
+ pending = read( m_inotify_fd, buf, pending);
+
+ while ( pending > 0 ) {
+ struct inotify_event *event = (struct inotify_event *) &buf[offset];
+ pending -= sizeof( struct inotify_event ) + event->len;
+ offset += sizeof( struct inotify_event ) + event->len;
+
+ TQString path;
+ if ( event->len )
+ path = TQFile::decodeName( TQCString( event->name, event->len ) );
+
+ if ( path.length() && isNoisyFile( path.latin1() ) )
+ continue;
+
+ kdDebug(7001) << "ev wd: " << event->wd << " mask " << event->mask << " path: " << path << endl;
+
+ // 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(); ++it ) {
+ Entry* e = &( *it );
+ if ( e->wd == event->wd ) {
+ e->dirty = true;
+
+ if ( 1 || e->isDir) {
+ if( event->mask & IN_DELETE_SELF) {
+ kdDebug(7001) << "-->got deleteself signal for " << e->path << endl;
+ e->m_status = NonExistent;
+ if (e->isDir)
+ addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true);
+ else
+ addEntry(0, TQFileInfo(e->path).dirPath(true), e, true);
+ }
+ if ( event->mask & IN_IGNORED ) {
+ e->wd = 0;
+ }
+ if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
+ Entry *sub_entry = e->m_entries.first();
+ for(;sub_entry; sub_entry = e->m_entries.next())
+ if (sub_entry->path == e->path + "/" + path) break;
+
+ if (sub_entry /*&& sub_entry->isDir*/) {
+ removeEntry(0,e->path, sub_entry);
+ KDE_struct_stat stat_buf;
+ TQCString tpath = TQFile::encodeName(path);
+ KDE_stat(tpath, &stat_buf);
+
+ //sub_entry->isDir = S_ISDIR(stat_buf.st_mode);
+ //sub_entry->m_ctime = stat_buf.st_ctime;
+ //sub_entry->m_status = Normal;
+ //sub_entry->m_nlink = stat_buf.st_nlink;
+
+ if(!useINotify(sub_entry))
+ useStat(sub_entry);
+ sub_entry->dirty = true;
+ }
+ }
+ }
+
+ if (!rescan_timer.isActive())
+ rescan_timer.start(m_PollInterval, true /* singleshot */);
+
+ break; // there really should be only one matching wd
+ }
+ }
+
+ }
+ }
+#endif
+}
+
+/* In DNOTIFY/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 KSimpleDirWatchPrivate::Entry::propagate_dirty()
+{
+ for (TQPtrListIterator<Entry> sub_entry (m_entries);
+ sub_entry.current(); ++sub_entry)
+ {
+ if (!sub_entry.current()->dirty)
+ {
+ sub_entry.current()->dirty = true;
+ sub_entry.current()->propagate_dirty();
+ }
+ }
+}
+
+
+/* A KSimpleDirWatch instance is interested in getting events for
+ * this file/Dir entry.
+ */
+void KSimpleDirWatchPrivate::Entry::addClient(KSimpleDirWatch* 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 KSimpleDirWatchPrivate::Entry::removeClient(KSimpleDirWatch* 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 KSimpleDirWatchPrivate::Entry::clients()
+{
+ int clients = 0;
+ Client* client = m_clients.first();
+ for(;client; client = m_clients.next())
+ clients += client->count;
+
+ return clients;
+}
+
+
+KSimpleDirWatchPrivate::Entry* KSimpleDirWatchPrivate::entry(const TQString& _path)
+{
+// we only support absolute paths
+ if (TQDir::isRelativePath(_path)) {
+ return 0;
+ }
+
+ TQString 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 KSimpleDirWatchPrivate::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;
+ }
+}
+
+
+#ifdef HAVE_FAM
+// setup FAM notification, returns false if not possible
+bool KSimpleDirWatchPrivate::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, TQDir::cleanDirPath(e->path+"/.."), e, true);
+ }
+ else {
+ int res =FAMMonitorDirectory(&fc, TQFile::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, TQFileInfo(e->path).dirPath(true), e, true);
+ }
+ else {
+ int res = FAMMonitorFile(&fc, TQFile::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 KSimpleDirWatchPrivate::useDNotify(Entry* e)
+{
+ e->dn_fd = 0;
+ e->dirty = false;
+ if (!supports_dnotify) return false;
+
+ e->m_mode = DNotifyMode;
+
+ if (e->isDir) {
+ if (e->m_status == Normal) {
+ int fd = KDE_open(TQFile::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 KSimpleDirWatch objects
+ // for a rather long time the above braindamage could get
+ // triggered.
+ //
+ // By moving the ksimpledirwatch 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, TQDir::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, TQFileInfo(e->path).dirPath(true), e, true);
+ }
+
+ return true;
+}
+#endif
+
+#ifdef HAVE_INOTIFY
+// setup INotify notification, returns false if not possible
+bool KSimpleDirWatchPrivate::useINotify( Entry* e )
+{
+ e->wd = 0;
+ e->dirty = false;
+ if (!supports_inotify) return false;
+
+ e->m_mode = INotifyMode;
+
+ 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;
+
+ // 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 |= IN_MODIFY|IN_ATTRIB; break; }
+ }
+
+ if ( ( e->wd = inotify_add_watch( m_inotify_fd,
+ TQFile::encodeName( e->path ), mask) ) > 0 )
+ return true;
+
+ if ( e->m_status == NonExistent ) {
+ if (e->isDir)
+ addEntry(0, TQDir::cleanDirPath(e->path+"/.."), e, true);
+ else
+ addEntry(0, TQFileInfo(e->path).dirPath(true), e, true);
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+bool KSimpleDirWatchPrivate::useStat(Entry* e)
+{
+ 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 KSimpleDirWatch 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 KSimpleDirWatchPrivate::addEntry(KSimpleDirWatch* instance, const TQString& _path,
+ Entry* sub_entry, bool isDir)
+{
+ TQString 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->m_mode == DNotifyMode) && (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
+
+#ifdef HAVE_INOTIFY
+ {
+ 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, TQFile::encodeName( e->path ), mask);
+ }
+ }
+#endif
+
+ }
+ else {
+ (*it).addClient(instance);
+ kdDebug(7001) << "Added already watched Entry " << path
+ << " (now " << (*it).clients() << " clients)"
+ << TQString(TQString(" [%1]").arg(instance->name())) << endl;
+ }
+ return;
+ }
+
+ // we have a new path to watch
+
+ KDE_struct_stat stat_buf;
+ TQCString tpath = TQFile::encodeName(path);
+ bool exists = (KDE_stat(tpath, &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)
+ kdWarning() << "KSimpleDirWatch: " << path << " is a directory. Use addDir!" << endl;
+ else if (!e->isDir && isDir)
+ kdWarning() << "KSimpleDirWatch: " << path << " is a file. Use addFile!" << endl;
+
+ 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 ? TQString(TQString(" for %1").arg(sub_entry->path)) : TQString(""))
+ << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
+ << endl;
+
+
+ // now setup the notification method
+ e->m_mode = UnknownMode;
+ e->msecLeft = 0;
+
+ if ( isNoisyFile( tpath ) )
+ return;
+
+#ifdef HAVE_FAM
+ if (useFAM(e)) return;
+#endif
+
+#ifdef HAVE_INOTIFY
+ if (useINotify(e)) return;
+#endif
+
+#ifdef HAVE_DNOTIFY
+ if (useDNotify(e)) return;
+#endif
+
+ useStat(e);
+}
+
+
+void KSimpleDirWatchPrivate::removeEntry( KSimpleDirWatch* instance,
+ const TQString& _path, Entry* sub_entry )
+{
+ kdDebug(7001) << "KSimpleDirWatchPrivate::removeEntry for '" << _path << "' sub_entry: " << sub_entry << endl;
+ Entry* e = entry(_path);
+ if (!e) {
+ kdDebug(7001) << "KSimpleDirWatchPrivate::removeEntry 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()) {
+ kdDebug(7001) << "removeEntry: unwatched " << e->path << " " << _path << endl;
+ 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, TQDir::cleanDirPath(e->path+"/.."), e);
+ else
+ removeEntry(0, TQFileInfo(e->path).dirPath(true), e);
+ }
+ }
+#endif
+
+#ifdef HAVE_INOTIFY
+ kdDebug(7001) << "inotify remove " << ( e->m_mode == INotifyMode ) << " " << ( e->m_status == Normal ) << endl;
+ if (e->m_mode == INotifyMode) {
+ if ( e->m_status == Normal ) {
+ (void) inotify_rm_watch( m_inotify_fd, e->wd );
+ kdDebug(7001) << "Cancelled INotify (fd " <<
+ m_inotify_fd << ", " << e->wd <<
+ ") for " << e->path << endl;
+ }
+ else {
+ if (e->isDir)
+ removeEntry(0, TQDir::cleanDirPath(e->path+"/.."), e);
+ else
+ removeEntry(0, TQFileInfo(e->path).dirPath(true), e);
+ }
+ }
+#endif
+
+#ifdef HAVE_DNOTIFY
+ if (e->m_mode == DNotifyMode) {
+ if (!e->isDir) {
+ removeEntry(0, TQFileInfo(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, TQDir::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 ? TQString(TQString(" for %1").arg(sub_entry->path)) : TQString(""))
+ << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
+ << endl;
+ m_mapEntries.remove( e->path ); // <e> not valid any more
+}
+
+
+/* Called from KSimpleDirWatch destructor:
+ * remove <instance> as client from all entries
+ */
+void KSimpleDirWatchPrivate::removeEntries( KSimpleDirWatch* instance )
+{
+ TQPtrList<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 KSimpleDirWatchPrivate::stopEntryScan( KSimpleDirWatch* 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 = NonExistent;
+ // e->m_status = Normal;
+ }
+ return true;
+}
+
+// instance ==0: start scanning for all instances
+bool KSimpleDirWatchPrivate::restartEntryScan( KSimpleDirWatch* 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 ? instance->name() : "all") << " restarted scanning " << e->path
+ << " (now " << wasWatching+newWatching << " watchers)" << endl;
+
+ // restart watching and emit pending events
+
+ int ev = NoChange;
+ if (wasWatching == 0) {
+ if (!notify) {
+ KDE_struct_stat stat_buf;
+ bool exists = (KDE_stat(TQFile::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 KSimpleDirWatchPrivate::stopScan(KSimpleDirWatch* instance)
+{
+ EntryMap::Iterator it = m_mapEntries.begin();
+ for( ; it != m_mapEntries.end(); ++it )
+ stopEntryScan(instance, &(*it));
+}
+
+
+void KSimpleDirWatchPrivate::startScan(KSimpleDirWatch* 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 KSimpleDirWatchPrivate::resetList( KSimpleDirWatch* /*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 KSimpleDirWatchPrivate::scanEntry(Entry* e)
+{
+#ifdef HAVE_FAM
+ if (e->m_mode == FAMMode) {
+ // we know nothing has changed, no need to stat
+ if(!e->dirty) return NoChange;
+ e->dirty = false;
+ }
+#endif
+
+ // Shouldn't happen: Ignore "unknown" notification method
+ if (e->m_mode == UnknownMode) return NoChange;
+
+#if defined ( HAVE_DNOTIFY ) || defined( HAVE_INOTIFY )
+ if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
+ // we know nothing has changed, no need to stat
+ if(!e->dirty) return NoChange;
+ kdDebug(7001) << "scanning " << e->path << " " << e->m_status << " " << e->m_ctime << endl;
+ e->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;
+ }
+
+ KDE_struct_stat stat_buf;
+ bool exists = (KDE_stat(TQFile::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)) ) {
+ 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 && e->m_status == NonExistent) {
+ e->m_nlink = 0;
+ e->m_status = NonExistent;
+ return NoChange;
+ }
+
+ e->m_ctime = invalid_ctime;
+ e->m_nlink = 0;
+ e->m_status = NonExistent;
+
+ return Deleted;
+}
+
+/* Notify all interested KSimpleDirWatch 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 KSimpleDirWatchPrivate::emitEvent(Entry* e, int event, const TQString &fileName)
+{
+ TQString path = e->path;
+ if (!fileName.isEmpty()) {
+ if (!TQDir::isRelativePath(fileName))
+ path = fileName;
+ else
+#ifdef Q_OS_UNIX
+ path += "/" + fileName;
+#elif defined(Q_WS_WIN)
+ //current drive is passed instead of /
+ path += TQDir::currentDirPath().left(2) + "/" + fileName;
+#endif
+ }
+
+ TQPtrListIterator<Client> cit( e->m_clients );
+ for ( ; cit.current(); ++cit )
+ {
+ Client* c = cit.current();
+
+ 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 KSimpleDirWatchPrivate::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 KSimpleDirWatchPrivate::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;
+
+#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
+ TQPtrList<Entry> dList, cList;
+#endif
+
+ 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 == DNotifyMode) && (*it).dirty )
+ (*it).propagate_dirty();
+ }
+
+ 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_INOTIFY
+ if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
+ cList.append( &(*it) );
+ if (! useINotify( &(*it) )) {
+ useStat( &(*it) );
+ }
+ }
+#endif
+
+#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);
+ }
+
+
+#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
+ // Scan parent of deleted directories for new creation
+ Entry* e;
+ for(e=dList.first();e;e=dList.next())
+ addEntry(0, TQDir::cleanDirPath( e->path+"/.."), e, true);
+
+ // Remove watch of parent of new created directories
+ for(e=cList.first();e;e=cList.next())
+ removeEntry(0, TQDir::cleanDirPath( e->path+"/.."), e);
+#endif
+
+ if ( timerRunning )
+ timer->start(freq);
+
+ TQTimer::singleShot(0, this, TQT_SLOT(slotRemoveDelayed()));
+}
+
+bool KSimpleDirWatchPrivate::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 KSimpleDirWatchPrivate::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_INOTIFY
+ if (useINotify( &(*it) )) continue;
+#endif
+#ifdef HAVE_DNOTIFY
+ if (useDNotify( &(*it) )) continue;
+#endif
+ useStat( &(*it) );
+ }
+ }
+ else
+ checkFAMEvent(&fe);
+ }
+
+ TQTimer::singleShot(0, this, TQT_SLOT(slotRemoveDelayed()));
+}
+
+void KSimpleDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
+{
+ // 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 0 // #88538
+ 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;
+#endif
+
+ 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;
+ }
+
+ // Delayed handling. This rechecks changes with own stat calls.
+ e->dirty = true;
+ if (!rescan_timer.isActive())
+ rescan_timer.start(m_PollInterval, true);
+
+ // needed FAM control actions on FAM events
+ if (e->isDir)
+ switch (fe->code)
+ {
+ case FAMDeleted:
+ // file absolute: watched dir
+ if (!TQDir::isRelativePath(fe->filename))
+ {
+ // 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, TQDir::cleanDirPath( e->path+"/.."), e, true);
+ }
+ 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) {
+ TQString path = e->path;
+ removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
+ sub_entry->m_status = Normal;
+ if (!useFAM(sub_entry))
+#ifdef HAVE_INOTIFY
+ if (!useINotify(sub_entry ))
+#endif
+ useStat(sub_entry);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+#else
+void KSimpleDirWatchPrivate::famEventReceived() {}
+#endif
+
+
+void KSimpleDirWatchPrivate::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 == INotifyMode) ? "INotify" :
+ (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()) {
+ TQString 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 << endl;
+ kdDebug(7001) << " " << d->path << " (" << d << ") " << endl;
+ }
+ }
+ }
+ }
+}
+
+
+//
+// Class KSimpleDirWatch
+//
+
+static KStaticDeleter<KSimpleDirWatch> sd_dw;
+KSimpleDirWatch* KSimpleDirWatch::s_pSelf = 0L;
+
+KSimpleDirWatch* KSimpleDirWatch::self()
+{
+ if ( !s_pSelf ) {
+ sd_dw.setObject( s_pSelf, new KSimpleDirWatch );
+ }
+
+ return s_pSelf;
+}
+
+bool KSimpleDirWatch::exists()
+{
+ return s_pSelf != 0;
+}
+
+KSimpleDirWatch::KSimpleDirWatch (TQObject* parent, const char* name)
+ : TQObject(parent,name)
+{
+ if (!name) {
+ static int nameCounter = 0;
+
+ nameCounter++;
+ setName(TQString(TQString("KSimpleDirWatch-%1").arg(nameCounter)).ascii());
+ }
+
+ if (!dwp_self)
+ dwp_self = new KSimpleDirWatchPrivate;
+ d = dwp_self;
+ d->ref();
+
+ _isStopped = false;
+}
+
+KSimpleDirWatch::~KSimpleDirWatch()
+{
+ 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 KSimpleDirWatch::addDir( const TQString& _path,
+ bool watchFiles, bool recursive)
+{
+ if (watchFiles || recursive) {
+ kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in KDE 3.x" << endl;
+ }
+ if (d) d->addEntry(this, _path, 0, true);
+}
+
+void KSimpleDirWatch::addFile( const TQString& _path )
+{
+ if (d) d->addEntry(this, _path, 0, false);
+}
+
+TQDateTime KSimpleDirWatch::ctime( const TQString &_path )
+{
+ KSimpleDirWatchPrivate::Entry* e = d->entry(_path);
+
+ if (!e)
+ return TQDateTime();
+
+ TQDateTime result;
+ result.setTime_t(e->m_ctime);
+ return result;
+}
+
+void KSimpleDirWatch::removeDir( const TQString& _path )
+{
+ if (d) d->removeEntry(this, _path, 0);
+}
+
+void KSimpleDirWatch::removeFile( const TQString& _path )
+{
+ if (d) d->removeEntry(this, _path, 0);
+}
+
+bool KSimpleDirWatch::stopDirScan( const TQString& _path )
+{
+ if (d) {
+ KSimpleDirWatchPrivate::Entry *e = d->entry(_path);
+ if (e && e->isDir) return d->stopEntryScan(this, e);
+ }
+ return false;
+}
+
+bool KSimpleDirWatch::restartDirScan( const TQString& _path )
+{
+ if (d) {
+ KSimpleDirWatchPrivate::Entry *e = d->entry(_path);
+ if (e && e->isDir)
+ // restart without notifying pending events
+ return d->restartEntryScan(this, e, false);
+ }
+ return false;
+}
+
+void KSimpleDirWatch::stopScan()
+{
+ if (d) d->stopScan(this);
+ _isStopped = true;
+}
+
+void KSimpleDirWatch::startScan( bool notify, bool skippedToo )
+{
+ _isStopped = false;
+ if (d) d->startScan(this, notify, skippedToo);
+}
+
+
+bool KSimpleDirWatch::contains( const TQString& _path ) const
+{
+ KSimpleDirWatchPrivate::Entry* e = d->entry(_path);
+ if (!e)
+ return false;
+
+ KSimpleDirWatchPrivate::Client* c = e->m_clients.first();
+ for(;c;c=e->m_clients.next())
+ if (c->instance == this) return true;
+
+ return false;
+}
+
+void KSimpleDirWatch::statistics()
+{
+ if (!dwp_self) {
+ kdDebug(7001) << "KSimpleDirWatch not used" << endl;
+ return;
+ }
+ dwp_self->statistics();
+}
+
+
+void KSimpleDirWatch::setCreated( const TQString & _file )
+{
+ kdDebug(7001) << name() << " emitting created " << _file << endl;
+ emit created( _file );
+}
+
+void KSimpleDirWatch::setDirty( const TQString & _file )
+{
+ kdDebug(7001) << name() << " emitting dirty " << _file << endl;
+ emit dirty( _file );
+}
+
+void KSimpleDirWatch::setDeleted( const TQString & _file )
+{
+ kdDebug(7001) << name() << " emitting deleted " << _file << endl;
+ emit deleted( _file );
+}
+
+KSimpleDirWatch::Method KSimpleDirWatch::internalMethod()
+{
+#ifdef HAVE_FAM
+ if (d->use_fam)
+ return KSimpleDirWatch::FAM;
+#endif
+#ifdef HAVE_INOTIFY
+ if (d->supports_inotify)
+ return KSimpleDirWatch::INotify;
+#endif
+#ifdef HAVE_DNOTIFY
+ if (d->supports_dnotify)
+ return KSimpleDirWatch::DNotify;
+#endif
+ return KSimpleDirWatch::Stat;
+}
+
+
+#include "ksimpledirwatch.moc"
+#include "ksimpledirwatch_p.moc"
+
+//sven
+
+// vim: sw=2 ts=8 et
diff --git a/tdecore/ksimpledirwatch.h b/tdecore/ksimpledirwatch.h
new file mode 100644
index 000000000..36ceafba6
--- /dev/null
+++ b/tdecore/ksimpledirwatch.h
@@ -0,0 +1,293 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 1998 Sven Radej <[email protected]>
+
+ 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.
+*/
+#ifndef _KSIMPLEDIRWATCH_H
+#define _KSIMPLEDIRWATCH_H
+
+#include <tqtimer.h>
+#include <tqdatetime.h>
+#include <tqmap.h>
+
+#include <tdelibs_export.h>
+
+#define kdirwatch KSimpleDirWatch::self()
+
+class KSimpleDirWatchPrivate;
+
+ /**
+ * KSimpleDirWatch is a basic copy of KDirWatch
+ * but with the KIO linking requirement removed
+ *
+ * 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, KSimpleDirWatch will emit the signal dirty().
+ *
+ * When a watched, but previously not existing directory gets created,
+ * KSimpleDirWatch will emit the signal created().
+ *
+ * When a watched directory gets deleted, KSimpleDirWatch will emit the
+ * signal deleted(). The directory is still watched for new
+ * creation.
+ *
+ * When a watched file is changed, i.e. attributes changed or written
+ * to, KSimpleDirWatch will emit the signal 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 <[email protected]>
+ */
+class KIO_EXPORT KSimpleDirWatch : public TQObject
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor.
+ *
+ * Scanning begins immediately when a dir/file watch
+ * is added.
+ * @param parent the parent of the TQObject (or 0 for parent-less KDataTools)
+ * @param name the name of the TQObject, can be 0
+ */
+ KSimpleDirWatch (TQObject* parent = 0, const char* name = 0);
+
+ /**
+ * Destructor.
+ *
+ * Stops scanning and cleans up.
+ */
+ ~KSimpleDirWatch();
+
+ /**
+ * 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 KSimpleDirWatch will also watch files - NOT IMPLEMENTED YET
+ * @param recursive if true, all sub directories are also watched - NOT IMPLEMENTED YET
+ */
+ void addDir(const TQString& path,
+ bool watchFiles = false, bool recursive = false);
+
+ /**
+ * Adds a file to be watched.
+ * @param file the file to watch
+ */
+ void addFile(const TQString& file);
+
+ /**
+ * Returns the time the directory/file was last changed.
+ * @param path the file to check
+ * @return the date of the last modification
+ */
+ TQDateTime ctime(const TQString& path);
+
+ /**
+ * Removes a directory from the list of scanned directories.
+ *
+ * If specified path is not in the list this does nothing.
+ * @param path the path of the dir to be removed from the list
+ */
+ void removeDir(const TQString& 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 TQString& 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 restartDirScan(path).
+ *
+ * @param path the path to skip
+ * @return true if the @p path is being watched, otherwise false
+ * @see restartDirScanning()
+ */
+ bool stopDirScan(const TQString& 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 TQString& path);
+
+ /**
+ * Starts scanning of all dirs in list.
+ *
+ * @param notify If true, all changed directories (since
+ * 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 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 KSimpleDirWatch instance, this is false.
+ * @return true when scanning stopped
+ */
+ bool isStopped() { return _isStopped; }
+
+ /**
+ * Check if a directory is being watched by this KSimpleDirWatch instance
+ * @param path the directory to check
+ * @return true if the directory is being watched
+ */
+ bool contains( const TQString& path ) const;
+
+ /**
+ * Dump statistic information about all KSimpleDirWatch instances.
+ * This checks for consistency, too.
+ */
+ static void statistics();
+
+ /**
+ * Emits created().
+ * @param path the path of the file or directory
+ */
+ void setCreated( const TQString &path );
+ /**
+ * Emits dirty().
+ * @param path the path of the file or directory
+ */
+ void setDirty( const TQString &path );
+ /**
+ * Emits deleted().
+ * @param path the path of the file or directory
+ */
+ void setDeleted( const TQString &path );
+
+ enum Method { FAM, DNotify, Stat, INotify };
+ /**
+ * Returns the preferred internal method to
+ * watch for changes.
+ * @since 3.2
+ */
+ Method internalMethod();
+
+ /**
+ * The KSimpleDirWatch instance usually globally used in an application.
+ * It is automatically deleted when the application exits.
+ *
+ * However, you can create an arbitrary number of KSimpleDirWatch instances
+ * aside from this one - for those you have to take care of memory management.
+ *
+ * This function returns an instance of KSimpleDirWatch. If there is none, it
+ * will be created.
+ *
+ * @return a KSimpleDirWatch instance
+ */
+ static KSimpleDirWatch* self();
+ /**
+ * Returns true if there is an instance of KSimpleDirWatch.
+ * @return true if there is an instance of KSimpleDirWatch.
+ * @see KSimpleDirWatch::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 KSimpleDirWatch.
+ *
+ * The new ctime is set before the signal is emitted.
+ * @param path the path of the file or directory
+ */
+ void dirty (const TQString &path);
+
+ /**
+ * Emitted when a file or directory is created.
+ * @param path the path of the file or directory
+ */
+ void created (const TQString &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 TQString &path );
+
+ private:
+ bool _isStopped;
+
+ KSimpleDirWatchPrivate *d;
+ static KSimpleDirWatch* s_pSelf;
+};
+
+#endif
+
+// vim: sw=3 et
diff --git a/tdecore/ksimpledirwatch_p.h b/tdecore/ksimpledirwatch_p.h
new file mode 100644
index 000000000..a0022f3ae
--- /dev/null
+++ b/tdecore/ksimpledirwatch_p.h
@@ -0,0 +1,161 @@
+/* Private Header for class of KSimpleDirWatchPrivate
+ *
+ * this separate header file is needed for TQMOC processing
+ * because KSimpleDirWatchPrivate has signals and slots
+ *
+ * KSimpleDirWatch is a basic copy of KDirWatch
+ * but with the KIO linking requirement removed
+ */
+
+#ifndef _KSIMPLEDIRWATCH_P_H
+#define _KSIMPLEDIRWATCH_P_H
+
+#ifdef HAVE_FAM
+#include <fam.h>
+#endif
+
+#include <ctime>
+
+#define invalid_ctime ((time_t)-1)
+
+/* KSimpleDirWatchPrivate is a singleton and does the watching
+ * for every KSimpleDirWatch instance in the application.
+ */
+class KSimpleDirWatchPrivate : public TQObject
+{
+ Q_OBJECT
+public:
+
+ enum entryStatus { Normal = 0, NonExistent };
+ enum entryMode { UnknownMode = 0, StatMode, DNotifyMode, INotifyMode, FAMMode };
+ enum { NoChange=0, Changed=1, Created=2, Deleted=4 };
+
+ struct Client {
+ KSimpleDirWatch* 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
+ TQPtrList<Client> m_clients;
+ // nonexistent entries of this directory
+ TQPtrList<Entry> m_entries;
+ TQString path;
+
+ int msecLeft, freq;
+
+ void addClient(KSimpleDirWatch*);
+ void removeClient(KSimpleDirWatch*);
+ int clients();
+ bool isValid() { return m_clients.count() || m_entries.count(); }
+
+ bool dirty;
+ void propagate_dirty();
+
+#ifdef HAVE_FAM
+ FAMRequest fr;
+#endif
+
+#ifdef HAVE_DNOTIFY
+ int dn_fd;
+#endif
+#ifdef HAVE_INOTIFY
+ int wd;
+#endif
+ };
+
+ typedef TQMap<TQString,Entry> EntryMap;
+
+ KSimpleDirWatchPrivate();
+ ~KSimpleDirWatchPrivate();
+
+ void resetList (KSimpleDirWatch*,bool);
+ void useFreq(Entry* e, int newFreq);
+ void addEntry(KSimpleDirWatch*,const TQString&, Entry*, bool);
+ void removeEntry(KSimpleDirWatch*,const TQString&, Entry*);
+ bool stopEntryScan(KSimpleDirWatch*, Entry*);
+ bool restartEntryScan(KSimpleDirWatch*, Entry*, bool );
+ void stopScan(KSimpleDirWatch*);
+ void startScan(KSimpleDirWatch*, bool, bool);
+
+ void removeEntries(KSimpleDirWatch*);
+ void statistics();
+
+ Entry* entry(const TQString&);
+ int scanEntry(Entry* e);
+ void emitEvent(Entry* e, int event, const TQString &fileName = TQString::null);
+
+ // Memory management - delete when last KSimpleDirWatch gets deleted
+ void ref() { m_ref++; }
+ bool deref() { return ( --m_ref == 0 ); }
+
+ static bool isNoisyFile( const char *filename );
+
+public slots:
+ void slotRescan();
+ void famEventReceived(); // for FAM
+ void slotActivated(); // for DNOTIFY
+ void slotRemoveDelayed();
+
+public:
+ TQTimer *timer;
+ EntryMap m_mapEntries;
+
+ int freq;
+ int statEntries;
+ int m_nfsPollInterval, m_PollInterval;
+ int m_ref;
+ bool useStat(Entry*);
+
+ bool delayRemove;
+ TQPtrList<Entry> removeList;
+
+ bool rescan_all;
+ TQTimer rescan_timer;
+
+#ifdef HAVE_FAM
+ TQSocketNotifier *sn;
+ FAMConnection fc;
+ bool use_fam;
+
+ void checkFAMEvent(FAMEvent*);
+ bool useFAM(Entry*);
+#endif
+
+#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
+ TQSocketNotifier *mSn;
+#endif
+
+#ifdef HAVE_DNOTIFY
+ bool supports_dnotify;
+ int mPipe[2];
+ TQIntDict<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
+
+#ifdef HAVE_INOTIFY
+ bool supports_inotify;
+ int m_inotify_fd;
+
+ bool useINotify(Entry*);
+#endif
+};
+
+#endif // KSIMPLEDIRWATCH_P_H
+
diff --git a/tdecore/tdehardwaredevices.cpp b/tdecore/tdehardwaredevices.cpp
index c73acc748..24afe695f 100644
--- a/tdecore/tdehardwaredevices.cpp
+++ b/tdecore/tdehardwaredevices.cpp
@@ -27,6 +27,7 @@
#include <klocale.h>
#include <kconfig.h>
#include <ktempfile.h>
+#include <ksimpledirwatch.h>
#include <kstandarddirs.h>
#include <libudev.h>
@@ -767,6 +768,76 @@ bool TDEStorageDevice::unmountDevice(TQString* errRet, int* retcode) {
return false;
}
+TDECPUDevice::TDECPUDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn) : TDEGenericDevice(dt, dn) {
+}
+
+TDECPUDevice::~TDECPUDevice() {
+}
+
+double &TDECPUDevice::frequency() {
+ return m_frequency;
+}
+
+void TDECPUDevice::setFrequency(double fr) {
+ m_frequency = fr;
+}
+
+double &TDECPUDevice::minFrequency() {
+ return m_minfrequency;
+}
+
+void TDECPUDevice::setMinFrequency(double fr) {
+ m_minfrequency = fr;
+}
+
+double &TDECPUDevice::maxFrequency() {
+ return m_maxfrequency;
+}
+
+void TDECPUDevice::setMaxFrequency(double fr) {
+ m_maxfrequency = fr;
+}
+
+double &TDECPUDevice::transitionLatency() {
+ return m_transitionlatency;
+}
+
+void TDECPUDevice::setTransitionLatency(double tl) {
+ m_transitionlatency = tl;
+}
+
+TQString &TDECPUDevice::governor() {
+ return m_governor;
+}
+
+void TDECPUDevice::setGovernor(TQString gr) {
+ m_governor = gr;
+}
+
+TQString &TDECPUDevice::scalingDriver() {
+ return m_scalingdriver;
+}
+
+void TDECPUDevice::setScalingDriver(TQString dr) {
+ m_scalingdriver = dr;
+}
+
+TQStringList &TDECPUDevice::dependentProcessors() {
+ return m_tiedprocs;
+}
+
+void TDECPUDevice::setDependentProcessors(TQStringList dp) {
+ m_tiedprocs = dp;
+}
+
+TQStringList &TDECPUDevice::availableFrequencies() {
+ return m_frequencies;
+}
+
+void TDECPUDevice::setAvailableFrequencies(TQStringList af) {
+ m_frequencies = af;
+}
+
TDEHardwareDevices::TDEHardwareDevices() {
// Initialize members
pci_id_map = 0;
@@ -808,12 +879,49 @@ TDEHardwareDevices::TDEHardwareDevices() {
m_mountScanNotifier = new TQSocketNotifier(m_procMountsFd, TQSocketNotifier::Exception, this);
connect( m_mountScanNotifier, TQT_SIGNAL(activated(int)), this, TQT_SLOT(processModifiedMounts()) );
+ // Read in the current cpu information
+ // Yes, a race condition exists between this and the cpu monitor start below, but it shouldn't be a problem 99.99% of the time
+ m_cpuInfo.clear();
+ TQFile cpufile( "/proc/cpuinfo" );
+ if ( cpufile.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &cpufile );
+ while ( !stream.atEnd() ) {
+ m_cpuInfo.append(stream.readLine());
+ }
+ cpufile.close();
+ }
+
+// [FIXME 0.01]
+// Apparently the Linux kernel just does not notify userspace applications of CPU frequency changes
+// This is STUPID, as it means I have to poll the CPU information structures with a 0.5 second or so timer just to keep the information up to date
+#if 0
+ // Monitor for changed cpu information
+ // Watched directories are set up during the initial CPU scan
+ m_cpuWatch = new KSimpleDirWatch(this);
+ connect( m_cpuWatch, TQT_SIGNAL(dirty(const TQString &)), this, TQT_SLOT(processModifiedCPUs()) );
+#else
+ m_cpuWatchTimer = new TQTimer(this);
+ connect( m_cpuWatchTimer, SIGNAL(timeout()), this, SLOT(processModifiedCPUs()) );
+ TQDir nodezerocpufreq("/sys/devices/system/cpu/cpu0/cpufreq");
+ if (nodezerocpufreq.exists()) {
+ m_cpuWatchTimer->start( 500, FALSE ); // 0.5 second repeating timer
+ }
+#endif
+
// Update internal device information
queryHardwareInformation();
}
}
TDEHardwareDevices::~TDEHardwareDevices() {
+// [FIXME 0.01]
+#if 0
+ // Stop CPU scanning
+ m_cpuWatch->stopScan();
+#else
+ m_cpuWatchTimer->stop();
+#endif
+
// Stop mount scanning
close(m_procMountsFd);
@@ -946,6 +1054,158 @@ void TDEHardwareDevices::processHotPluggedHardware() {
}
}
+void TDEHardwareDevices::processModifiedCPUs() {
+ // Detect what changed between the old cpu information and the new information,
+ // and emit appropriate events
+
+ // Read new CPU information table
+ m_cpuInfo.clear();
+ TQFile cpufile( "/proc/cpuinfo" );
+ if ( cpufile.open( IO_ReadOnly ) ) {
+ TQTextStream stream( &cpufile );
+ while ( !stream.atEnd() ) {
+ m_cpuInfo.append(stream.readLine());
+ }
+ cpufile.close();
+ }
+
+ // Parse CPU information table
+ TDECPUDevice *cdevice;
+ cdevice = 0;
+ bool modified = false;
+
+ TQString curline;
+ int processorNumber = 0;
+ int processorCount = 0;
+ for (TQStringList::Iterator cpuit = m_cpuInfo.begin(); cpuit != m_cpuInfo.end(); ++cpuit) {
+ // WARNING This routine assumes that "processor" is always the first entry in /proc/cpuinfo!
+ curline = *cpuit;
+ if (curline.startsWith("processor")) {
+ curline.remove(0, curline.find(":")+1);
+ curline = curline.stripWhiteSpace();
+ processorNumber = curline.toInt();
+ cdevice = dynamic_cast<TDECPUDevice*>(findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber)));
+ }
+ if (curline.startsWith("model name")) {
+ curline.remove(0, curline.find(":")+1);
+ curline = curline.stripWhiteSpace();
+ if (cdevice->name() != curline) modified = true;
+ cdevice->setName(curline);
+ }
+ if (curline.startsWith("cpu MHz")) {
+ curline.remove(0, curline.find(":")+1);
+ curline = curline.stripWhiteSpace();
+ if (cdevice->frequency() != curline.toDouble()) modified = true;
+ cdevice->setFrequency(curline.toDouble());
+ }
+ if (curline.startsWith("vendor_id")) {
+ curline.remove(0, curline.find(":")+1);
+ curline = curline.stripWhiteSpace();
+ if (cdevice->vendorName() != curline) modified = true;
+ cdevice->setVendorName(curline);
+ if (cdevice->vendorEncoded() != curline) modified = true;
+ cdevice->setVendorEncoded(curline);
+ }
+ }
+
+ processorCount = processorNumber+1;
+
+ // Read in other information from cpufreq, if available
+ for (processorNumber=0; processorNumber<processorCount; processorNumber++) {
+ cdevice = dynamic_cast<TDECPUDevice*>(findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber)));
+ TQDir cpufreq_dir(TQString("/sys/devices/system/cpu/cpu%1/cpufreq").arg(processorNumber));
+ TQString scalinggovernor;
+ TQString scalingdriver;
+ double minfrequency = -1;
+ double maxfrequency = -1;
+ double trlatency = -1;
+ TQStringList affectedcpulist;
+ TQStringList frequencylist;
+ if (cpufreq_dir.exists()) {
+ TQString nodename = cpufreq_dir.path();
+ nodename.append("/scaling_governor");
+ TQFile scalinggovernorfile(nodename);
+ if (scalinggovernorfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &scalinggovernorfile );
+ scalinggovernor = stream.readLine();
+ scalinggovernorfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/scaling_driver");
+ TQFile scalingdriverfile(nodename);
+ if (scalingdriverfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &scalingdriverfile );
+ scalingdriver = stream.readLine();
+ scalingdriverfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/scaling_min_freq");
+ TQFile minfrequencyfile(nodename);
+ if (minfrequencyfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &minfrequencyfile );
+ minfrequency = stream.readLine().toDouble()/1000.0;
+ minfrequencyfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/scaling_max_freq");
+ TQFile maxfrequencyfile(nodename);
+ if (maxfrequencyfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &maxfrequencyfile );
+ maxfrequency = stream.readLine().toDouble()/1000.0;
+ maxfrequencyfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/cpuinfo_transition_latency");
+ TQFile trlatencyfile(nodename);
+ if (trlatencyfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &trlatencyfile );
+ trlatency = stream.readLine().toDouble()/1000.0;
+ trlatencyfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/affected_cpus");
+ TQFile tiedcpusfile(nodename);
+ if (tiedcpusfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &tiedcpusfile );
+ affectedcpulist = TQStringList::split(" ", stream.readLine());
+ tiedcpusfile.close();
+ }
+ nodename = cpufreq_dir.path();
+ nodename.append("/scaling_available_frequencies");
+ TQFile availfreqsfile(nodename);
+ if (availfreqsfile.open(IO_ReadOnly)) {
+ TQTextStream stream( &availfreqsfile );
+ frequencylist = TQStringList::split(" ", stream.readLine());
+ availfreqsfile.close();
+ }
+ }
+
+ // Update CPU information structure
+ if (cdevice->governor() != scalinggovernor) modified = true;
+ cdevice->setGovernor(scalinggovernor);
+ if (cdevice->scalingDriver() != scalingdriver) modified = true;
+ cdevice->setScalingDriver(scalingdriver);
+ if (cdevice->minFrequency() != minfrequency) modified = true;
+ cdevice->setMinFrequency(minfrequency);
+ if (cdevice->maxFrequency() != maxfrequency) modified = true;
+ cdevice->setMaxFrequency(maxfrequency);
+ if (cdevice->transitionLatency() != trlatency) modified = true;
+ cdevice->setTransitionLatency(trlatency);
+ if (cdevice->dependentProcessors().join(" ") != affectedcpulist.join(" ")) modified = true;
+ cdevice->setDependentProcessors(affectedcpulist);
+ if (cdevice->availableFrequencies().join(" ") != frequencylist.join(" ")) modified = true;
+ cdevice->setAvailableFrequencies(frequencylist);
+ }
+
+ if (modified) {
+ for (processorNumber=0; processorNumber<processorCount; processorNumber++) {
+ TDEGenericDevice* hwdevice = findBySystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber));
+ // Signal new information available
+ emit hardwareUpdated(hwdevice);
+ }
+ }
+}
+
void TDEHardwareDevices::processModifiedMounts() {
// Detect what changed between the old mount table and the new one,
// and emit appropriate events
@@ -2325,22 +2585,23 @@ void TDEHardwareDevices::addCoreSystemDevices() {
line.remove(0, line.find(":")+1);
line = line.stripWhiteSpace();
processorNumber = line.toInt();
- hwdevice = new TDEGenericDevice(TDEGenericDeviceType::CPU);
+ hwdevice = new TDECPUDevice(TDEGenericDeviceType::CPU);
hwdevice->setSystemPath(TQString("/sys/devices/system/cpu/cpu%1").arg(processorNumber));
m_deviceList.append(hwdevice);
- }
- if (line.startsWith("model name")) {
- line.remove(0, line.find(":")+1);
- line = line.stripWhiteSpace();
- hwdevice->setName(line);
+#if 0
+ // Set up CPU information monitor
+ // The only way CPU information can be changed is if something changes in the cpufreq node
+ // This may change in the future, but for now it is a fairly good assumption
+ m_cpuWatch->addDir(TQString("/sys/devices/system/cpu/cpu%1/cpufreq").arg(processorNumber));
+#endif
}
lines += line;
}
file.close();
}
- // FIXME
- // For each CPU, look for cpufreq and parse as much information as possible
+ // Populate CPU information
+ processModifiedCPUs();
}
TQString TDEHardwareDevices::findPCIDeviceName(TQString vendorid, TQString modelid, TQString subvendorid, TQString submodelid) {
diff --git a/tdecore/tdehardwaredevices.h b/tdecore/tdehardwaredevices.h
index d30c125c2..495a6a761 100644
--- a/tdecore/tdehardwaredevices.h
+++ b/tdecore/tdehardwaredevices.h
@@ -636,10 +636,116 @@ class TDECORE_EXPORT TDEStorageDevice : public TDEGenericDevice
TQStringList m_slaveDevices;
};
+class TDECORE_EXPORT TDECPUDevice : public TDEGenericDevice
+{
+ public:
+ /**
+ * Constructor.
+ * @param Device type
+ */
+ TDECPUDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn=TQString::null);
+
+ /**
+ * Destructor.
+ */
+ ~TDECPUDevice();
+
+ /**
+ * @return a double with the current CPU frequency in MHz, if available
+ */
+ double &frequency();
+
+ /**
+ * @param a double with the current CPU frequency in MHz, if available
+ */
+ void setFrequency(double fr);
+
+ /**
+ * @return a double with the minimum CPU frequency in MHz, if available
+ */
+ double &minFrequency();
+
+ /**
+ * @param a double with the minimum CPU frequency in MHz, if available
+ */
+ void setMinFrequency(double fr);
+
+ /**
+ * @return a double with the maximum CPU frequency in MHz, if available
+ */
+ double &maxFrequency();
+
+ /**
+ * @param a double with the maximum CPU frequency in MHz, if available
+ */
+ void setMaxFrequency(double fr);
+
+ /**
+ * @return a double with the transition latency in ns, if available
+ */
+ double &transitionLatency();
+
+ /**
+ * @param a double with the transition latency in ns, if available
+ */
+ void setTransitionLatency(double tl);
+
+ /**
+ * @return a TQString with the current CPU governor policy, if available
+ */
+ TQString &governor();
+
+ /**
+ * @param a TQString with the current CPU governor policy, if available
+ */
+ void setGovernor(TQString gr);
+
+ /**
+ * @return a TQString with the current CPU scaling driver, if available
+ */
+ TQString &scalingDriver();
+
+ /**
+ * @param a TQString with the current CPU scaling driver, if available
+ */
+ void setScalingDriver(TQString dr);
+
+ /**
+ * @return a TQStringList with the IDs of all processors that are dependent on the frequency/power settings of this one, if available
+ */
+ TQStringList &dependentProcessors();
+
+ /**
+ * @param a TQStringList with the IDs of all processors that are dependent on the frequency/power settings of this one, if available
+ */
+ void setDependentProcessors(TQStringList dp);
+
+ /**
+ * @return a TQStringList with all valid scaling frequencies in Hz, if available
+ */
+ TQStringList &availableFrequencies();
+
+ /**
+ * @param a TQStringList with all valid scaling frequencies in Hz, if available
+ */
+ void setAvailableFrequencies(TQStringList af);
+
+ private:
+ double m_frequency;
+ double m_minfrequency;
+ double m_maxfrequency;
+ double m_transitionlatency;
+ TQString m_governor;
+ TQString m_scalingdriver;
+ TQStringList m_tiedprocs;
+ TQStringList m_frequencies;
+};
+
typedef TQPtrList<TDEGenericDevice> TDEGenericHardwareList;
typedef TQMap<TQString, TQString> TDEDeviceIDMap;
class TQSocketNotifier;
+class KSimpleDirWatch;
class TDECORE_EXPORT TDEHardwareDevices : public TQObject
{
@@ -750,6 +856,7 @@ class TDECORE_EXPORT TDEHardwareDevices : public TQObject
private slots:
void processHotPluggedHardware();
void processModifiedMounts();
+ void processModifiedCPUs();
private:
void rescanDeviceInformation(TDEGenericDevice* hwdevice);
@@ -768,11 +875,14 @@ class TDECORE_EXPORT TDEHardwareDevices : public TQObject
struct udev_monitor *m_udevMonitorStruct;
TDEGenericHardwareList m_deviceList;
int m_procMountsFd;
+ KSimpleDirWatch* m_cpuWatch;
+ TQTimer* m_cpuWatchTimer;
TQSocketNotifier* m_devScanNotifier;
TQSocketNotifier* m_mountScanNotifier;
TQStringList m_mountTable;
+ TQStringList m_cpuInfo;
TDEDeviceIDMap* pci_id_map;
TDEDeviceIDMap* usb_id_map;