Diffstat (limited to 'microkde/kio/kio/kdirwatch.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r-- | microkde/kio/kio/kdirwatch.cpp | 12 |
1 files changed, 7 insertions, 5 deletions
diff --git a/microkde/kio/kio/kdirwatch.cpp b/microkde/kio/kio/kdirwatch.cpp index 1596d1f..5f07c54 100644 --- a/microkde/kio/kio/kdirwatch.cpp +++ b/microkde/kio/kio/kdirwatch.cpp @@ -1,283 +1,285 @@ // -*- 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 <q3intdict.h> +#include <q3ptrlist.h> #include <qsocketnotifier.h> #include <qstringlist.h> #include <qtimer.h> +//Added by qt3to4: +#include <Q3CString> #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") ); + KConfigGroupSaver saver( config, Q3CString("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... */ @@ -638,193 +640,193 @@ void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path, #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; + Q3PtrList<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; } @@ -914,193 +916,193 @@ int KDirWatchPrivate::scanEntry(Entry* e) 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; + Q3PtrList<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 |