• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KDECore

kdirwatch.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
00003    Copyright (C) 2006 Dirk Mueller <mueller@kde.org>
00004    Copyright (C) 2007 Flavio Castelli <flavio.castelli@gmail.com>
00005    Copyright (C) 2008 Rafal Rzepecki <divided.mind@gmail.com>
00006    Copyright (C) 2010 David Faure <faure@kde.org>
00007 
00008    This library is free software; you can redistribute it and/or
00009    modify it under the terms of the GNU Library General Public
00010    License version 2 as published by the Free Software Foundation.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020    Boston, MA 02110-1301, USA.
00021 */
00022 
00023 
00024 // CHANGES:
00025 // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal)
00026 // Aug 6,  2007 - KDirWatch::WatchModes support complete, flags work fine also
00027 // when using FAMD (Flavio Castelli)
00028 // Aug 3,  2007 - Handled KDirWatch::WatchModes flags when using inotify, now
00029 // recursive and file monitoring modes are implemented (Flavio Castelli)
00030 // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes
00031 // flag (Flavio Castelli)
00032 // Oct 4,  2005 - Inotify support (Dirk Mueller)
00033 // Februar 2002 - Add file watching and remote mount check for STAT
00034 // Mar 30, 2001 - Native support for Linux dir change notification.
00035 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
00036 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
00037 // May 23. 1998 - Removed static pointer - you can have more instances.
00038 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
00039 // call (or need) KFM. No more URL's - just plain paths. (sven)
00040 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
00041 // deep copies for list of dirs. (sven)
00042 // Mar 28. 1998 - Created.  (sven)
00043 
00044 #include "kdirwatch.h"
00045 #include "kdirwatch_p.h"
00046 
00047 #include "io/config-kdirwatch.h"
00048 #include <config.h>
00049 
00050 #include <sys/stat.h>
00051 #include <assert.h>
00052 #include <errno.h>
00053 #include <QtCore/QDir>
00054 #include <QtCore/QFile>
00055 #include <QtCore/QSocketNotifier>
00056 #include <QtCore/QTimer>
00057 #include <QtCore/QCoreApplication>
00058 
00059 #include <ksharedconfig.h>
00060 #include <kdebug.h>
00061 #include <kconfig.h>
00062 #include <kglobal.h>
00063 #include <kde_file.h>
00064 #include <kconfiggroup.h>
00065 #include "kmountpoint.h"
00066 
00067 #include <stdlib.h>
00068 #include <string.h>
00069 
00070 // debug
00071 #include <sys/ioctl.h>
00072 
00073 
00074 #include <sys/utsname.h>
00075 
00076 // set this to true for much more verbose debug output
00077 static const bool s_verboseDebug = false;
00078 
00079 // The KDirWatchPrivate instance is refcounted, and deleted by the last KDirWatch instance
00080 static KDirWatchPrivate* dwp_self = 0;
00081 static KDirWatchPrivate* createPrivate() {
00082   if (!dwp_self)
00083     dwp_self = new KDirWatchPrivate;
00084   return dwp_self;
00085 }
00086 
00087 // Convert a string into a watch Method
00088 static KDirWatch::Method methodFromString(const QString& method) {
00089   if (method == QLatin1String("Fam")) {
00090     return KDirWatch::FAM;
00091   } else if (method == QLatin1String("Stat")) {
00092     return KDirWatch::Stat;
00093   } else if (method == QLatin1String("QFSWatch")) {
00094     return KDirWatch::QFSWatch;
00095   } else {
00096 #ifdef Q_OS_LINUX
00097     // inotify supports delete+recreate+modify, which QFSWatch doesn't support
00098     return KDirWatch::INotify;
00099 #else
00100     return KDirWatch::QFSWatch;
00101 #endif
00102   }
00103 }
00104 
00105 #ifndef NDEBUG
00106 static const char* methodToString(KDirWatch::Method method)
00107 {
00108     switch (method) {
00109     case KDirWatch::FAM:
00110         return "Fam";
00111     case KDirWatch::INotify:
00112         return "INotify";
00113     case KDirWatch::DNotify:
00114         return "DNotify";
00115     case KDirWatch::Stat:
00116         return "Stat";
00117     case KDirWatch::QFSWatch:
00118         return "QFSWatch";
00119     default:
00120         return "ERROR!";
00121     }
00122 }
00123 #endif
00124 
00125 //
00126 // Class KDirWatchPrivate (singleton)
00127 //
00128 
00129 /* All entries (files/directories) to be watched in the
00130  * application (coming from multiple KDirWatch instances)
00131  * are registered in a single KDirWatchPrivate instance.
00132  *
00133  * At the moment, the following methods for file watching
00134  * are supported:
00135  * - Polling: All files to be watched are polled regularly
00136  *   using stat (more precise: QFileInfo.lastModified()).
00137  *   The polling frequency is determined from global kconfig
00138  *   settings, defaulting to 500 ms for local directories
00139  *   and 5000 ms for remote mounts
00140  * - FAM (File Alternation Monitor): first used on IRIX, SGI
00141  *   has ported this method to LINUX. It uses a kernel part
00142  *   (IMON, sending change events to /dev/imon) and a user
00143  *   level damon (fam), to which applications connect for
00144  *   notification of file changes. For NFS, the fam damon
00145  *   on the NFS server machine is used; if IMON is not built
00146  *   into the kernel, fam uses polling for local files.
00147  * - INOTIFY: In LINUX 2.6.13, inode change notification was
00148  *   introduced. You're now able to watch arbitrary inode's
00149  *   for changes, and even get notification when they're
00150  *   unmounted.
00151  */
00152 
00153 KDirWatchPrivate::KDirWatchPrivate()
00154   : timer(),
00155     freq( 3600000 ), // 1 hour as upper bound
00156     statEntries( 0 ),
00157     m_ref( 0 ),
00158     delayRemove( false ),
00159     rescan_all( false ),
00160     rescan_timer()
00161 {
00162   timer.setObjectName(QLatin1String("KDirWatchPrivate::timer"));
00163   connect (&timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00164 
00165   KConfigGroup config(KGlobal::config(), "DirWatch");
00166   m_nfsPollInterval = config.readEntry("NFSPollInterval", 5000);
00167   m_PollInterval = config.readEntry("PollInterval", 500);
00168 
00169   QString method = config.readEntry("PreferredMethod", "inotify");
00170   m_preferredMethod = methodFromString(method);
00171 
00172   // The nfs method defaults to the normal (local) method
00173   m_nfsPreferredMethod = methodFromString(config.readEntry("nfsPreferredMethod", "Fam"));
00174 
00175   QList<QByteArray> availableMethods;
00176 
00177   availableMethods << "Stat";
00178 
00179   // used for FAM and inotify
00180   rescan_timer.setObjectName(QString::fromLatin1("KDirWatchPrivate::rescan_timer"));
00181   rescan_timer.setSingleShot( true );
00182   connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00183 
00184 #ifdef HAVE_FAM
00185   // It's possible that FAM server can't be started
00186   if (FAMOpen(&fc) ==0) {
00187     availableMethods << "FAM";
00188     use_fam=true;
00189     sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
00190                   QSocketNotifier::Read, this);
00191     connect( sn, SIGNAL(activated(int)),
00192          this, SLOT(famEventReceived()) );
00193   }
00194   else {
00195     kDebug(7001) << "Can't use FAM (fam daemon not running?)";
00196     use_fam=false;
00197   }
00198 #endif
00199 
00200 #ifdef HAVE_SYS_INOTIFY_H
00201   supports_inotify = true;
00202 
00203   m_inotify_fd = inotify_init();
00204 
00205   if ( m_inotify_fd <= 0 ) {
00206     kDebug(7001) << "Can't use Inotify, kernel doesn't support it";
00207     supports_inotify = false;
00208   }
00209 
00210   {
00211     struct utsname uts;
00212     int major, minor, patch;
00213     if (uname(&uts) < 0)
00214       supports_inotify = false; // *shrug*
00215     else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00216       supports_inotify = false; // *shrug*
00217     else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
00218       kDebug(7001) << "Can't use INotify, Linux kernel too old";
00219       supports_inotify = false;
00220     }
00221   }
00222 
00223   if ( supports_inotify ) {
00224     availableMethods << "INotify";
00225     fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
00226 
00227     mSn = new QSocketNotifier( m_inotify_fd, QSocketNotifier::Read, this );
00228     connect( mSn, SIGNAL(activated( int )),
00229              this, SLOT( inotifyEventReceived() ) );
00230   }
00231 #endif
00232 #ifdef HAVE_QFILESYSTEMWATCHER
00233   availableMethods << "QFileSystemWatcher";
00234   fsWatcher = 0;
00235 #endif
00236 #ifndef NDEBUG
00237   kDebug(7001) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod);
00238 #endif
00239 }
00240 
00241 // This is called on app exit (when K_GLOBAL_STATIC deletes KDirWatch::self)
00242 KDirWatchPrivate::~KDirWatchPrivate()
00243 {
00244   timer.stop();
00245 
00246   /* remove all entries being watched */
00247   removeEntries(0);
00248 
00249 #ifdef HAVE_FAM
00250   if (use_fam) {
00251     FAMClose(&fc);
00252   }
00253 #endif
00254 #ifdef HAVE_SYS_INOTIFY_H
00255   if ( supports_inotify )
00256     ::close( m_inotify_fd );
00257 #endif
00258 #ifdef HAVE_QFILESYSTEMWATCHER
00259   if(QCoreApplication::instance() != 0)
00260     delete fsWatcher;
00261 #endif
00262 }
00263 
00264 void KDirWatchPrivate::inotifyEventReceived()
00265 {
00266   //kDebug(7001);
00267 #ifdef HAVE_SYS_INOTIFY_H
00268   if ( !supports_inotify )
00269     return;
00270 
00271   int pending = -1;
00272   int offsetStartRead = 0; // where we read into buffer
00273   char buf[8192];
00274   assert( m_inotify_fd > -1 );
00275   ioctl( m_inotify_fd, FIONREAD, &pending );
00276 
00277   while ( pending > 0 ) {
00278 
00279     const int bytesToRead = qMin( pending, (int)sizeof( buf ) - offsetStartRead );
00280 
00281     int bytesAvailable = read( m_inotify_fd, &buf[offsetStartRead], bytesToRead );
00282     pending -= bytesAvailable;
00283     bytesAvailable += offsetStartRead;
00284     offsetStartRead = 0;
00285 
00286     int offsetCurrent = 0;
00287     while ( bytesAvailable >= (int)sizeof( struct inotify_event ) ) {
00288       const struct inotify_event * const event = (struct inotify_event *) &buf[offsetCurrent];
00289       const int eventSize = sizeof( struct inotify_event ) + event->len;
00290       if ( bytesAvailable < eventSize ) {
00291           break;
00292       }
00293 
00294       bytesAvailable -= eventSize;
00295       offsetCurrent += eventSize;
00296 
00297       QString path;
00298       QByteArray cpath(event->name, event->len);
00299       if(event->len)
00300         path = QFile::decodeName ( cpath );
00301 
00302       if ( path.length() && isNoisyFile( cpath ) )
00303         continue;
00304 
00305       // now we're in deep trouble of finding the
00306       // associated entries
00307       // for now, we suck and iterate
00308       for ( EntryMap::Iterator it = m_mapEntries.begin();
00309             it != m_mapEntries.end();  ) {
00310         Entry* e = &( *it );
00311         ++it;
00312         if ( e->wd == event->wd ) {
00313           e->dirty = true;
00314 
00315           //if (s_verboseDebug) {
00316           //  kDebug(7001) << "got event" << "0x"+QString::number(event->mask, 16) << "for" << e->path;
00317           //}
00318 
00319           if( event->mask & IN_DELETE_SELF) {
00320             if (s_verboseDebug) {
00321               kDebug(7001) << "-->got deleteself signal for" << e->path;
00322             }
00323             e->m_status = NonExistent;
00324             e->wd = -1;
00325             e->m_ctime = invalid_ctime;
00326             emitEvent(e, Deleted, e->path);
00327             // Add entry to parent dir to notice if the entry gets recreated
00328             addEntry(0, e->parentDirectory(), e, true /*isDir*/);
00329           }
00330           if ( event->mask & IN_IGNORED ) {
00331             // Causes bug #207361 with kernels 2.6.31 and 2.6.32!
00332             //e->wd = -1;
00333           }
00334           if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
00335             const QString tpath = e->path + QLatin1Char('/') + path;
00336             Entry* sub_entry = e->findSubEntry(tpath);
00337 
00338             if (s_verboseDebug) {
00339               kDebug(7001) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry;
00340               kDebug(7001) << *e;
00341             }
00342 
00343             // The code below is very similar to the one in checkFAMEvent...
00344             if (sub_entry) {
00345               // We were waiting for this new file/dir to be created
00346               sub_entry->dirty = true;
00347               rescan_timer.start(0); // process this asap, to start watching that dir
00348             } else if (e->isDir && !e->m_clients.empty()) {
00349               bool isDir = false;
00350               const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
00351               Q_FOREACH(Client *client, clients) {
00352                 // See discussion in addEntry for why we don't addEntry for individual
00353                 // files in WatchFiles mode with inotify.
00354                 if (isDir) {
00355                   addEntry(client->instance, tpath, 0, isDir,
00356                            isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
00357                 }
00358               }
00359               if (!clients.isEmpty()) {
00360                 emitEvent(e, Created, tpath);
00361                 kDebug(7001).nospace() << clients.count() << " instance(s) monitoring the new "
00362                                        << (isDir ? "dir " : "file ") << tpath;
00363               }
00364               e->m_pendingFileChanges.append(e->path);
00365               if (!rescan_timer.isActive())
00366                   rescan_timer.start(m_PollInterval); // singleshot
00367             }
00368           }
00369           if (event->mask & (IN_DELETE|IN_MOVED_FROM)) {
00370             const QString tpath = e->path + QLatin1Char('/') + path;
00371             if (s_verboseDebug) {
00372               kDebug(7001) << "-->got DELETE signal for" << tpath;
00373             }
00374             if ((e->isDir) && (!e->m_clients.empty())) {
00375               Client* client = 0;
00376               // A file in this directory has been removed.  It wasn't an explicitly
00377               // watched file as it would have its own watch descriptor, so
00378               // no addEntry/ removeEntry bookkeeping should be required.  Emit
00379               // the event immediately if any clients are interested.
00380               KDE_struct_stat stat_buf;
00381               // Unlike clientsForFileOrDir, the stat can fail here (item deleted),
00382               // so in that case we'll just take both kinds of clients and emit Deleted.
00383               KDirWatch::WatchModes flag = KDirWatch::WatchSubDirs | KDirWatch::WatchFiles;
00384               if (KDE::stat(tpath, &stat_buf) == 0) {
00385                 bool isDir = S_ISDIR(stat_buf.st_mode);
00386                 flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
00387               }
00388               int counter = 0;
00389               Q_FOREACH(client, e->m_clients) {
00390                   if (client->m_watchModes & flag) {
00391                         counter++;
00392                   }
00393               }
00394               if (counter != 0) {
00395                   emitEvent(e, Deleted, tpath);
00396               }
00397             }
00398           }
00399           if (event->mask & (IN_MODIFY|IN_ATTRIB)) {
00400             if ((e->isDir) && (!e->m_clients.empty())) {
00401               const QString tpath = e->path + QLatin1Char('/') + path;
00402               if (s_verboseDebug) {
00403                 kDebug(7001) << "-->got MODIFY signal for" << (tpath);
00404               }
00405               // A file in this directory has been changed.  No
00406               // addEntry/ removeEntry bookkeeping should be required.
00407               // Add the path to the list of pending file changes if
00408               // there are any interested clients.
00409               //KDE_struct_stat stat_buf;
00410               //QByteArray tpath = QFile::encodeName(e->path+'/'+path);
00411               //KDE_stat(tpath, &stat_buf);
00412               //bool isDir = S_ISDIR(stat_buf.st_mode);
00413 
00414               // The API doc is somewhat vague as to whether we should emit
00415               // dirty() for implicitly watched files when WatchFiles has
00416               // not been specified - we'll assume they are always interested,
00417               // regardless.
00418               // Don't worry about duplicates for the time
00419               // being; this is handled in slotRescan.
00420               e->m_pendingFileChanges.append(tpath);
00421             }
00422           }
00423 
00424           if (!rescan_timer.isActive())
00425             rescan_timer.start(m_PollInterval); // singleshot
00426 
00427           break;
00428         }
00429       }
00430     }
00431     if (bytesAvailable > 0) {
00432         // copy partial event to beginning of buffer
00433         memmove(buf, &buf[offsetCurrent], bytesAvailable);
00434         offsetStartRead = bytesAvailable;
00435     }
00436   }
00437 #endif
00438 }
00439 
00440 /* In FAM mode, only entries which are marked dirty are scanned.
00441  * We first need to mark all yet nonexistent, but possible created
00442  * entries as dirty...
00443  */
00444 void KDirWatchPrivate::Entry::propagate_dirty()
00445 {
00446   foreach(Entry *sub_entry, m_entries)
00447   {
00448      if (!sub_entry->dirty)
00449      {
00450         sub_entry->dirty = true;
00451         sub_entry->propagate_dirty();
00452      }
00453   }
00454 }
00455 
00456 
00457 /* A KDirWatch instance is interested in getting events for
00458  * this file/Dir entry.
00459  */
00460 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance,
00461                                         KDirWatch::WatchModes watchModes)
00462 {
00463   if (instance == 0)
00464     return;
00465 
00466   foreach(Client* client, m_clients) {
00467     if (client->instance == instance) {
00468       client->count++;
00469       client->m_watchModes = watchModes;
00470       return;
00471     }
00472   }
00473 
00474   Client* client = new Client;
00475   client->instance = instance;
00476   client->count = 1;
00477   client->watchingStopped = instance->isStopped();
00478   client->pending = NoChange;
00479   client->m_watchModes = watchModes;
00480 
00481   m_clients.append(client);
00482 }
00483 
00484 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
00485 {
00486   QList<Client *>::iterator it = m_clients.begin();
00487   const QList<Client *>::iterator end = m_clients.end();
00488   for ( ; it != end ; ++it ) {
00489     Client* client = *it;
00490     if (client->instance == instance) {
00491       client->count--;
00492       if (client->count == 0) {
00493         m_clients.erase(it);
00494         delete client;
00495       }
00496       return;
00497     }
00498   }
00499 }
00500 
00501 /* get number of clients */
00502 int KDirWatchPrivate::Entry::clientCount() const
00503 {
00504   int clients = 0;
00505   foreach(Client* client, m_clients)
00506     clients += client->count;
00507 
00508   return clients;
00509 }
00510 
00511 QString KDirWatchPrivate::Entry::parentDirectory() const
00512 {
00513   return QDir::cleanPath(path + QLatin1String("/.."));
00514 }
00515 
00516 QList<KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString& tpath, bool* isDir) const
00517 {
00518   QList<Client *> ret;
00519   KDE_struct_stat stat_buf;
00520   if (KDE::stat(tpath, &stat_buf) == 0) {
00521     *isDir = S_ISDIR(stat_buf.st_mode);
00522     const KDirWatch::WatchModes flag =
00523       *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles;
00524     Q_FOREACH(Client *client, this->m_clients) {
00525       if (client->m_watchModes & flag) {
00526         ret.append(client);
00527       }
00528     }
00529   } else {
00530     // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp"
00531     //kDebug(7001) << "ERROR: couldn't stat" << tpath;
00532   }
00533   // If KDE_stat fails then isDir is not set, but ret is empty anyway
00534   // so isDir won't be used.
00535   return ret;
00536 }
00537 
00538 QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry)
00539 {
00540   debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file");
00541   if (entry.m_status == KDirWatchPrivate::NonExistent)
00542     debug << ", non-existent";
00543   debug << ", using " << ((entry.m_mode == KDirWatchPrivate::FAMMode) ? "FAM" :
00544                        (entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" :
00545                        (entry.m_mode == KDirWatchPrivate::DNotifyMode) ? "DNotify" :
00546                        (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" :
00547                        (entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" : "Unknown Method");
00548 #ifdef HAVE_SYS_INOTIFY_H
00549   if (entry.m_mode == KDirWatchPrivate::INotifyMode)
00550     debug << " inotify_wd=" << entry.wd;
00551 #endif
00552   debug << ", has " << entry.m_clients.count() << " clients";
00553   debug.space();
00554   if (!entry.m_entries.isEmpty()) {
00555     debug << ", nonexistent subentries:";
00556     Q_FOREACH(KDirWatchPrivate::Entry* subEntry, entry.m_entries)
00557       debug << subEntry << subEntry->path;
00558   }
00559   debug << ']';
00560   return debug;
00561 }
00562 
00563 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00564 {
00565 // we only support absolute paths
00566   if (_path.isEmpty() || QDir::isRelativePath(_path)) {
00567     return 0;
00568   }
00569 
00570   QString path (_path);
00571 
00572   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00573     path.truncate( path.length() - 1 );
00574 
00575   EntryMap::Iterator it = m_mapEntries.find( path );
00576   if ( it == m_mapEntries.end() )
00577     return 0;
00578   else
00579     return &(*it);
00580 }
00581 
00582 // set polling frequency for a entry and adjust global freq if needed
00583 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00584 {
00585   e->freq = newFreq;
00586 
00587   // a reasonable frequency for the global polling timer
00588   if (e->freq < freq) {
00589     freq = e->freq;
00590     if (timer.isActive()) timer.start(freq);
00591     kDebug(7001) << "Global Poll Freq is now" << freq << "msec";
00592   }
00593 }
00594 
00595 
00596 #if defined(HAVE_FAM)
00597 // setup FAM notification, returns false if not possible
00598 bool KDirWatchPrivate::useFAM(Entry* e)
00599 {
00600   if (!use_fam) return false;
00601 
00602   // handle FAM events to avoid deadlock
00603   // (FAM sends back all files in a directory when monitoring)
00604   famEventReceived();
00605 
00606   e->m_mode = FAMMode;
00607   e->dirty = false;
00608 
00609   if (e->isDir) {
00610     if (e->m_status == NonExistent) {
00611       // If the directory does not exist we watch the parent directory
00612       addEntry(0, e->parentDirectory(), e, true);
00613     }
00614     else {
00615       int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
00616                    &(e->fr), e);
00617       if (res<0) {
00618     e->m_mode = UnknownMode;
00619     use_fam=false;
00620         delete sn; sn = 0;
00621     return false;
00622       }
00623       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00624                    << ") for " << e->path;
00625     }
00626   }
00627   else {
00628     if (e->m_status == NonExistent) {
00629       // If the file does not exist we watch the directory
00630       addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
00631     }
00632     else {
00633       int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
00634                    &(e->fr), e);
00635       if (res<0) {
00636     e->m_mode = UnknownMode;
00637     use_fam=false;
00638         delete sn; sn = 0;
00639     return false;
00640       }
00641 
00642       kDebug(7001).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00643                    << ") for " << e->path;
00644     }
00645   }
00646 
00647   // handle FAM events to avoid deadlock
00648   // (FAM sends back all files in a directory when monitoring)
00649   famEventReceived();
00650 
00651   return true;
00652 }
00653 #endif
00654 
00655 #ifdef HAVE_SYS_INOTIFY_H
00656 // setup INotify notification, returns false if not possible
00657 bool KDirWatchPrivate::useINotify( Entry* e )
00658 {
00659   //kDebug (7001) << "trying to use inotify for monitoring";
00660 
00661   e->wd = -1;
00662   e->dirty = false;
00663 
00664   if (!supports_inotify) return false;
00665 
00666   e->m_mode = INotifyMode;
00667 
00668   if ( e->m_status == NonExistent ) {
00669     addEntry(0, e->parentDirectory(), e, true);
00670     return true;
00671   }
00672 
00673   // May as well register for almost everything - it's free!
00674   int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW|IN_MOVED_FROM|IN_MODIFY|IN_ATTRIB;
00675 
00676   if ( ( e->wd = inotify_add_watch( m_inotify_fd,
00677                                     QFile::encodeName( e->path ), mask) ) >= 0)
00678   {
00679     if (s_verboseDebug) {
00680       kDebug(7001) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd;
00681     }
00682     return true;
00683   }
00684 
00685   return false;
00686 }
00687 #endif
00688 #ifdef HAVE_QFILESYSTEMWATCHER
00689 bool KDirWatchPrivate::useQFSWatch(Entry* e)
00690 {
00691   e->m_mode = QFSWatchMode;
00692   e->dirty = false;
00693 
00694   if ( e->m_status == NonExistent ) {
00695     addEntry(0, e->parentDirectory(), e, true /*isDir*/);
00696     return true;
00697   }
00698 
00699   kDebug(7001) << "fsWatcher->addPath" << e->path;
00700   if (!fsWatcher) {
00701       fsWatcher = new KFileSystemWatcher();
00702       connect(fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(fswEventReceived(QString)));
00703       connect(fsWatcher, SIGNAL(fileChanged(QString)),      this, SLOT(fswEventReceived(QString)));
00704   }
00705   fsWatcher->addPath( e->path );
00706   return true;
00707 }
00708 #endif
00709 
00710 bool KDirWatchPrivate::useStat(Entry* e)
00711 {
00712   KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath(e->path);
00713   const bool slow = mp ? mp->probablySlow() : false;
00714   if (slow)
00715     useFreq(e, m_nfsPollInterval);
00716   else
00717     useFreq(e, m_PollInterval);
00718 
00719   if (e->m_mode != StatMode) {
00720     e->m_mode = StatMode;
00721     statEntries++;
00722 
00723     if ( statEntries == 1 ) {
00724       // if this was first STAT entry (=timer was stopped)
00725       timer.start(freq);      // then start the timer
00726       kDebug(7001) << " Started Polling Timer, freq " << freq;
00727     }
00728   }
00729 
00730   kDebug(7001) << " Setup Stat (freq " << e->freq << ") for " << e->path;
00731 
00732   return true;
00733 }
00734 
00735 
00736 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
00737  * providing in <isDir> the type of the entry to be watched.
00738  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
00739  * this entry needs another entry to watch himself (when notExistent).
00740  */
00741 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00742                 Entry* sub_entry, bool isDir, KDirWatch::WatchModes watchModes)
00743 {
00744   QString path (_path);
00745   if (path.isEmpty()
00746 #ifndef Q_WS_WIN
00747      || ((path.startsWith(QLatin1String("/dev/")) || (path == QLatin1String("/dev")) && !path.startsWith(QLatin1String("/dev/."))))
00748 #endif
00749   )
00750     return; // Don't even go there.
00751 
00752   if ( path.length() > 1 && path.endsWith( QLatin1Char( '/' ) ) )
00753     path.truncate( path.length() - 1 );
00754 
00755   EntryMap::Iterator it = m_mapEntries.find( path );
00756   if ( it != m_mapEntries.end() )
00757   {
00758     if (sub_entry) {
00759        (*it).m_entries.append(sub_entry);
00760        if (s_verboseDebug) {
00761          kDebug(7001) << "Added already watched Entry" << path
00762                       << "(for" << sub_entry->path << ")";
00763        }
00764 #ifdef HAVE_SYS_INOTIFY_H
00765        Entry* e = &(*it);
00766        if( (e->m_mode == INotifyMode) && (e->wd >= 0) ) {
00767          int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
00768          if(!e->isDir)
00769            mask |= IN_MODIFY|IN_ATTRIB;
00770          else
00771            mask |= IN_ONLYDIR;
00772 
00773          inotify_rm_watch (m_inotify_fd, e->wd);
00774          e->wd = inotify_add_watch( m_inotify_fd, QFile::encodeName( e->path ),
00775                                     mask);
00776          //Q_ASSERT(e->wd >= 0); // fails in KDirListerTest::testDeleteCurrentDir
00777        }
00778 #endif
00779     }
00780     else {
00781        (*it).addClient(instance, watchModes);
00782        if (s_verboseDebug) {
00783          kDebug(7001) << "Added already watched Entry" << path
00784                       << "(now" <<  (*it).clientCount() << "clients)"
00785                       << QString::fromLatin1("[%1]").arg(instance->objectName());
00786        }
00787     }
00788     return;
00789   }
00790 
00791   // we have a new path to watch
00792 
00793   KDE_struct_stat stat_buf;
00794   bool exists = (KDE::stat(path, &stat_buf) == 0);
00795 
00796   EntryMap::iterator newIt = m_mapEntries.insert( path, Entry() );
00797   // the insert does a copy, so we have to use <e> now
00798   Entry* e = &(*newIt);
00799 
00800   if (exists) {
00801     e->isDir = S_ISDIR(stat_buf.st_mode);
00802 
00803     if (e->isDir && !isDir) {
00804       KDE::lstat(path, &stat_buf);
00805       if (S_ISLNK(stat_buf.st_mode))
00806         // if it's a symlink, don't follow it
00807         e->isDir = false;
00808       else
00809         qWarning() << "KDirWatch:" << path << "is a directory. Use addDir!";
00810     } else if (!e->isDir && isDir)
00811       qWarning("KDirWatch: %s is a file. Use addFile!", qPrintable(path));
00812 
00813     if (!e->isDir && ( watchModes != KDirWatch::WatchDirOnly)) {
00814       qWarning() << "KDirWatch:" << path << "is a file. You can't use recursive or "
00815                     "watchFiles options";
00816       watchModes = KDirWatch::WatchDirOnly;
00817     }
00818 
00819 #ifdef Q_OS_WIN
00820     // ctime is the 'creation time' on windows - use mtime instead
00821     e->m_ctime = stat_buf.st_mtime;
00822 #else
00823     e->m_ctime = stat_buf.st_ctime;
00824 #endif
00825     e->m_status = Normal;
00826     e->m_nlink = stat_buf.st_nlink;
00827     e->m_ino = stat_buf.st_ino;
00828   }
00829   else {
00830     e->isDir = isDir;
00831     e->m_ctime = invalid_ctime;
00832     e->m_status = NonExistent;
00833     e->m_nlink = 0;
00834     e->m_ino = 0;
00835   }
00836 
00837   e->path = path;
00838   if (sub_entry)
00839     e->m_entries.append(sub_entry);
00840   else
00841     e->addClient(instance, watchModes);
00842 
00843   kDebug(7001).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path
00844     << (e->m_status == NonExistent ? " NotExisting" : "")
00845     << " for " << (sub_entry ? sub_entry->path : QString())
00846     << " [" << (instance ? instance->objectName() : QString()) << "]";
00847 
00848   // now setup the notification method
00849   e->m_mode = UnknownMode;
00850   e->msecLeft = 0;
00851 
00852   if ( isNoisyFile( QFile::encodeName( path ) ) )
00853     return;
00854 
00855   if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) {
00856     QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot;
00857 
00858     if ((watchModes & KDirWatch::WatchSubDirs) &&
00859         (watchModes & KDirWatch::WatchFiles)) {
00860       filters |= (QDir::Dirs|QDir::Files);
00861     } else if (watchModes & KDirWatch::WatchSubDirs) {
00862       filters |= QDir::Dirs;
00863     } else if (watchModes & KDirWatch::WatchFiles) {
00864       filters |= QDir::Files;
00865     }
00866 
00867 #if defined(HAVE_SYS_INOTIFY_H)
00868     if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == KDirWatch::INotify)  )
00869     {
00870         //kDebug(7001) << "Ignoring WatchFiles directive - this is implicit with inotify";
00871         // Placing a watch on individual files is redundant with inotify
00872         // (inotify gives us WatchFiles functionality "for free") and indeed
00873         // actively harmful, so prevent it.  WatchSubDirs is necessary, though.
00874         filters &= ~QDir::Files;
00875     }
00876 #endif
00877 
00878     QDir basedir (e->path);
00879     const QFileInfoList contents = basedir.entryInfoList(filters);
00880     for (QFileInfoList::const_iterator iter = contents.constBegin();
00881          iter != contents.constEnd(); ++iter)
00882     {
00883       const QFileInfo &fileInfo = *iter;
00884       // treat symlinks as files--don't follow them.
00885       bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
00886 
00887       addEntry (instance, fileInfo.absoluteFilePath(), 0, isDir,
00888                 isDir ? watchModes : KDirWatch::WatchDirOnly);
00889     }
00890   }
00891 
00892   addWatch(e);
00893 }
00894 
00895 void KDirWatchPrivate::addWatch(Entry* e)
00896 {
00897   // If the watch is on a network filesystem use the nfsPreferredMethod as the
00898   // default, otherwise use preferredMethod as the default, if the methods are
00899   // the same we can skip the mountpoint check
00900 
00901   // This allows to configure a different method for NFS mounts, since inotify
00902   // cannot detect changes made by other machines. However as a default inotify
00903   // is fine, since the most common case is a NFS-mounted home, where all changes
00904   // are made locally. #177892.
00905   KDirWatch::Method preferredMethod = m_preferredMethod;
00906   if (m_nfsPreferredMethod != m_preferredMethod) {
00907     KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(e->path);
00908     if (mountPoint && mountPoint->probablySlow()) {
00909       preferredMethod = m_nfsPreferredMethod;
00910     }
00911   }
00912 
00913   // Try the appropriate preferred method from the config first
00914   bool entryAdded = false;
00915   switch (preferredMethod) {
00916 #if defined(HAVE_FAM)
00917     case KDirWatch::FAM: entryAdded = useFAM(e); break;
00918 #endif
00919 #if defined(HAVE_SYS_INOTIFY_H)
00920     case KDirWatch::INotify: entryAdded = useINotify(e); break;
00921 #endif
00922 #if defined(HAVE_QFILESYSTEMWATCHER)
00923     case KDirWatch::QFSWatch: entryAdded = useQFSWatch(e); break;
00924 #endif
00925     case KDirWatch::Stat: entryAdded = useStat(e); break;
00926     default: break;
00927   }
00928 
00929   // Failing that try in order INotify, FAM, QFSWatch, Stat
00930   if (!entryAdded) {
00931 #if defined(HAVE_SYS_INOTIFY_H)
00932     if (useINotify(e)) return;
00933 #endif
00934 #if defined(HAVE_FAM)
00935     if (useFAM(e)) return;
00936 #endif
00937 #if defined(HAVE_QFILESYSTEMWATCHER)
00938     if (useQFSWatch(e)) return;
00939 #endif
00940     useStat(e);
00941   }
00942 }
00943 
00944 void KDirWatchPrivate::removeWatch(Entry* e)
00945 {
00946 #ifdef HAVE_FAM
00947     if (e->m_mode == FAMMode) {
00948         FAMCancelMonitor(&fc, &(e->fr) );
00949         kDebug(7001).nospace()  << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr))
00950                                 << ") for " << e->path;
00951     }
00952 #endif
00953 #ifdef HAVE_SYS_INOTIFY_H
00954     if (e->m_mode == INotifyMode) {
00955         (void) inotify_rm_watch( m_inotify_fd, e->wd );
00956         if (s_verboseDebug) {
00957             kDebug(7001).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", "
00958                                    << e->wd << ") for " << e->path;
00959         }
00960     }
00961 #endif
00962 #ifdef HAVE_QFILESYSTEMWATCHER
00963     if (e->m_mode == QFSWatchMode && fsWatcher) {
00964         if (s_verboseDebug)
00965             kDebug(7001) << "fsWatcher->removePath" << e->path;
00966         fsWatcher->removePath(e->path);
00967     }
00968 #endif
00969 }
00970 
00971 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00972                                    const QString& _path,
00973                                    Entry* sub_entry)
00974 {
00975   if (s_verboseDebug) {
00976     kDebug(7001) << "path=" << _path << "sub_entry:" << sub_entry;
00977   }
00978   Entry* e = entry(_path);
00979   if (!e) {
00980     kWarning(7001) << "doesn't know" << _path;
00981     return;
00982   }
00983 
00984   removeEntry(instance, e, sub_entry);
00985 }
00986 
00987 void KDirWatchPrivate::removeEntry(KDirWatch* instance,
00988                                    Entry* e,
00989                                    Entry* sub_entry)
00990 {
00991   removeList.remove(e);
00992 
00993   if (sub_entry)
00994     e->m_entries.removeAll(sub_entry);
00995   else
00996     e->removeClient(instance);
00997 
00998   if (e->m_clients.count() || e->m_entries.count())
00999     return;
01000 
01001   if (delayRemove) {
01002     removeList.insert(e);
01003     // now e->isValid() is false
01004     return;
01005   }
01006 
01007     if ( e->m_status == Normal) {
01008         removeWatch(e);
01009     } else {
01010         // Removed a NonExistent entry - we just remove it from the parent
01011         if (e->isDir)
01012             removeEntry(0, e->parentDirectory(), e);
01013         else
01014             removeEntry(0, QFileInfo(e->path).absolutePath(), e);
01015     }
01016 
01017   if (e->m_mode == StatMode) {
01018     statEntries--;
01019     if ( statEntries == 0 ) {
01020       timer.stop(); // stop timer if lists are empty
01021       kDebug(7001) << " Stopped Polling Timer";
01022     }
01023   }
01024 
01025   if (s_verboseDebug) {
01026     kDebug(7001).nospace() << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
01027                            << " for " << (sub_entry ? sub_entry->path : QString())
01028                            << " [" << (instance ? instance->objectName() : QString()) << "]";
01029   }
01030   m_mapEntries.remove( e->path ); // <e> not valid any more
01031 }
01032 
01033 
01034 /* Called from KDirWatch destructor:
01035  * remove <instance> as client from all entries
01036  */
01037 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
01038 {
01039   int minfreq = 3600000;
01040 
01041   QStringList pathList;
01042   // put all entries where instance is a client in list
01043   EntryMap::Iterator it = m_mapEntries.begin();
01044   for( ; it != m_mapEntries.end(); ++it ) {
01045     Client* c = 0;
01046     foreach(Client* client, (*it).m_clients) {
01047       if (client->instance == instance) {
01048         c = client;
01049         break;
01050       }
01051     }
01052     if (c) {
01053       c->count = 1; // forces deletion of instance as client
01054       pathList.append((*it).path);
01055     }
01056     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
01057       minfreq = (*it).freq;
01058   }
01059 
01060   foreach(const QString &path, pathList)
01061     removeEntry(instance, path, 0);
01062 
01063   if (minfreq > freq) {
01064     // we can decrease the global polling frequency
01065     freq = minfreq;
01066     if (timer.isActive()) timer.start(freq);
01067     kDebug(7001) << "Poll Freq now" << freq << "msec";
01068   }
01069 }
01070 
01071 // instance ==0: stop scanning for all instances
01072 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
01073 {
01074   int stillWatching = 0;
01075   foreach(Client* client, e->m_clients) {
01076     if (!instance || instance == client->instance)
01077       client->watchingStopped = true;
01078     else if (!client->watchingStopped)
01079       stillWatching += client->count;
01080   }
01081 
01082   kDebug(7001)  << (instance ? instance->objectName() : QString::fromLatin1("all"))
01083                 << "stopped scanning" << e->path << "(now"
01084                 << stillWatching << "watchers)";
01085 
01086   if (stillWatching == 0) {
01087     // if nobody is interested, we don't watch
01088     if ( e->m_mode != INotifyMode ) {
01089       e->m_ctime = invalid_ctime; // invalid
01090       e->m_status = NonExistent;
01091     }
01092     //    e->m_status = Normal;
01093   }
01094   return true;
01095 }
01096 
01097 // instance ==0: start scanning for all instances
01098 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
01099                      bool notify)
01100 {
01101   int wasWatching = 0, newWatching = 0;
01102   foreach(Client* client, e->m_clients) {
01103     if (!client->watchingStopped)
01104       wasWatching += client->count;
01105     else if (!instance || instance == client->instance) {
01106       client->watchingStopped = false;
01107       newWatching += client->count;
01108     }
01109   }
01110   if (newWatching == 0)
01111     return false;
01112 
01113   kDebug(7001)  << (instance ? instance->objectName() : QString::fromLatin1("all"))
01114                 << "restarted scanning" << e->path
01115                 << "(now" << wasWatching+newWatching << "watchers)";
01116 
01117   // restart watching and emit pending events
01118 
01119   int ev = NoChange;
01120   if (wasWatching == 0) {
01121     if (!notify) {
01122       KDE_struct_stat stat_buf;
01123       bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01124       if (exists) {
01125 #ifdef Q_OS_WIN
01126         // ctime is the 'creation time' on windows - use mtime instead
01127         e->m_ctime = stat_buf.st_mtime;
01128 #else
01129         e->m_ctime = stat_buf.st_ctime;
01130 #endif
01131         e->m_status = Normal;
01132         if (s_verboseDebug) {
01133           kDebug(7001) << "Setting status to Normal for" << e << e->path;
01134         }
01135         e->m_nlink = stat_buf.st_nlink;
01136         e->m_ino = stat_buf.st_ino;
01137 
01138         // Same as in scanEntry: ensure no subentry in parent dir
01139         removeEntry(0, e->parentDirectory(), e);
01140       }
01141       else {
01142         e->m_ctime = invalid_ctime;
01143         e->m_status = NonExistent;
01144         e->m_nlink = 0;
01145         if (s_verboseDebug) {
01146           kDebug(7001) << "Setting status to NonExistent for" << e << e->path;
01147         }
01148       }
01149     }
01150     e->msecLeft = 0;
01151     ev = scanEntry(e);
01152   }
01153   emitEvent(e,ev);
01154 
01155   return true;
01156 }
01157 
01158 // instance ==0: stop scanning for all instances
01159 void KDirWatchPrivate::stopScan(KDirWatch* instance)
01160 {
01161   EntryMap::Iterator it = m_mapEntries.begin();
01162   for( ; it != m_mapEntries.end(); ++it )
01163     stopEntryScan(instance, &(*it));
01164 }
01165 
01166 
01167 void KDirWatchPrivate::startScan(KDirWatch* instance,
01168                                  bool notify, bool skippedToo )
01169 {
01170   if (!notify)
01171     resetList(instance,skippedToo);
01172 
01173   EntryMap::Iterator it = m_mapEntries.begin();
01174   for( ; it != m_mapEntries.end(); ++it )
01175     restartEntryScan(instance, &(*it), notify);
01176 
01177   // timer should still be running when in polling mode
01178 }
01179 
01180 
01181 // clear all pending events, also from stopped
01182 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/, bool skippedToo )
01183 {
01184   EntryMap::Iterator it = m_mapEntries.begin();
01185   for( ; it != m_mapEntries.end(); ++it ) {
01186 
01187     foreach(Client* client, (*it).m_clients) {
01188       if (!client->watchingStopped || skippedToo)
01189         client->pending = NoChange;
01190     }
01191   }
01192 }
01193 
01194 // Return event happened on <e>
01195 //
01196 int KDirWatchPrivate::scanEntry(Entry* e)
01197 {
01198   // Shouldn't happen: Ignore "unknown" notification method
01199   if (e->m_mode == UnknownMode) return NoChange;
01200 
01201   if (e->m_mode == FAMMode || e->m_mode == INotifyMode) {
01202     // we know nothing has changed, no need to stat
01203     if(!e->dirty) return NoChange;
01204     e->dirty = false;
01205   }
01206 
01207   if (e->m_mode == StatMode) {
01208     // only scan if timeout on entry timer happens;
01209     // e.g. when using 500msec global timer, a entry
01210     // with freq=5000 is only watched every 10th time
01211 
01212     e->msecLeft -= freq;
01213     if (e->msecLeft>0) return NoChange;
01214     e->msecLeft += e->freq;
01215   }
01216 
01217   KDE_struct_stat stat_buf;
01218   const bool exists = (KDE::stat(e->path, &stat_buf) == 0);
01219   if (exists) {
01220 
01221     if (e->m_status == NonExistent) {
01222       // ctime is the 'creation time' on windows, but with qMax
01223       // we get the latest change of any kind, on any platform.
01224       e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
01225       e->m_status = Normal;
01226       e->m_ino = stat_buf.st_ino;
01227       if (s_verboseDebug) {
01228         kDebug(7001) << "Setting status to Normal for just created" << e << e->path;
01229       }
01230       // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo)
01231       removeEntry(0, e->parentDirectory(), e);
01232 
01233       return Created;
01234     }
01235 
01236 #if 1 // for debugging the if() below
01237     if (s_verboseDebug) {
01238       struct tm* tmp = localtime(&e->m_ctime);
01239       char outstr[200];
01240       strftime(outstr, sizeof(outstr), "%T", tmp);
01241       kDebug(7001) << "e->m_ctime=" << e->m_ctime << outstr
01242                    << "stat_buf.st_ctime=" << stat_buf.st_ctime
01243                    << "e->m_nlink=" << e->m_nlink
01244                    << "stat_buf.st_nlink=" << stat_buf.st_nlink
01245                    << "e->m_ino=" << e->m_ino
01246                    << "stat_buf.st_ino=" << stat_buf.st_ino;
01247     }
01248 #endif
01249 
01250     if ( ((e->m_ctime != invalid_ctime) &&
01251           (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime ||
01252            stat_buf.st_ino != e->m_ino ||
01253            stat_buf.st_nlink != nlink_t(e->m_nlink)))
01254 #ifdef Q_OS_WIN
01255           // we trust QFSW to get it right, the ctime comparisons above
01256           // fail for example when adding files to directories on Windows
01257           // which doesn't change the mtime of the directory
01258         || e->m_mode == QFSWatchMode
01259 #endif
01260     ) {
01261       e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
01262       e->m_nlink = stat_buf.st_nlink;
01263       if (e->m_ino != stat_buf.st_ino) {
01264           // The file got deleted and recreated. We need to watch it again.
01265           removeWatch(e);
01266           addWatch(e);
01267       }
01268       e->m_ino = stat_buf.st_ino;
01269       return Changed;
01270     }
01271 
01272     return NoChange;
01273   }
01274 
01275   // dir/file doesn't exist
01276 
01277   e->m_nlink = 0;
01278   e->m_ino = 0;
01279   e->m_status = NonExistent;
01280 
01281   if (e->m_ctime == invalid_ctime) {
01282     return NoChange;
01283   }
01284 
01285   e->m_ctime = invalid_ctime;
01286   return Deleted;
01287 }
01288 
01289 /* Notify all interested KDirWatch instances about a given event on an entry
01290  * and stored pending events. When watching is stopped, the event is
01291  * added to the pending events.
01292  */
01293 void KDirWatchPrivate::emitEvent(const Entry* e, int event, const QString &fileName)
01294 {
01295   QString path (e->path);
01296   if (!fileName.isEmpty()) {
01297     if (!QDir::isRelativePath(fileName))
01298       path = fileName;
01299     else {
01300 #ifdef Q_OS_UNIX
01301       path += QLatin1Char('/') + fileName;
01302 #elif defined(Q_WS_WIN)
01303       //current drive is passed instead of /
01304       path += QDir::currentPath().left(2) + QLatin1Char('/') + fileName;
01305 #endif
01306     }
01307   }
01308 
01309   if (s_verboseDebug) {
01310     kDebug(7001) << event << path << e->m_clients.count() << "clients";
01311   }
01312 
01313   foreach(Client* c, e->m_clients)
01314   {
01315     if (c->instance==0 || c->count==0) continue;
01316 
01317     if (c->watchingStopped) {
01318       // add event to pending...
01319       if (event == Changed)
01320         c->pending |= event;
01321       else if (event == Created || event == Deleted)
01322         c->pending = event;
01323       continue;
01324     }
01325     // not stopped
01326     if (event == NoChange || event == Changed)
01327       event |= c->pending;
01328     c->pending = NoChange;
01329     if (event == NoChange) continue;
01330 
01331     // Emit the signals delayed, to avoid unexpected re-entrancy from the slots (#220153)
01332 
01333     if (event & Deleted) {
01334       QMetaObject::invokeMethod(c->instance, "setDeleted", Qt::QueuedConnection, Q_ARG(QString, path));
01335       // emit only Deleted event...
01336       continue;
01337     }
01338 
01339     if (event & Created) {
01340       QMetaObject::invokeMethod(c->instance, "setCreated", Qt::QueuedConnection, Q_ARG(QString, path));
01341       // possible emit Change event after creation
01342     }
01343 
01344     if (event & Changed) {
01345       QMetaObject::invokeMethod(c->instance, "setDirty", Qt::QueuedConnection, Q_ARG(QString, path));
01346     }
01347   }
01348 }
01349 
01350 // Remove entries which were marked to be removed
01351 void KDirWatchPrivate::slotRemoveDelayed()
01352 {
01353   delayRemove = false;
01354   // Removing an entry could also take care of removing its parent
01355   // (e.g. in FAM or inotify mode), which would remove other entries in removeList,
01356   // so don't use foreach or iterators here...
01357   while (!removeList.isEmpty()) {
01358     Entry* entry = *removeList.begin();
01359     removeEntry(0, entry, 0); // this will remove entry from removeList
01360   }
01361 }
01362 
01363 /* Scan all entries to be watched for changes. This is done regularly
01364  * when polling. FAM and inotify use a single-shot timer to call this slot delayed.
01365  */
01366 void KDirWatchPrivate::slotRescan()
01367 {
01368   if (s_verboseDebug)
01369     kDebug(7001);
01370 
01371   EntryMap::Iterator it;
01372 
01373   // People can do very long things in the slot connected to dirty(),
01374   // like showing a message box. We don't want to keep polling during
01375   // that time, otherwise the value of 'delayRemove' will be reset.
01376   // ### TODO: now the emitEvent delays emission, this can be cleaned up
01377   bool timerRunning = timer.isActive();
01378   if ( timerRunning )
01379     timer.stop();
01380 
01381   // We delay deletions of entries this way.
01382   // removeDir(), when called in slotDirty(), can cause a crash otherwise
01383   // ### TODO: now the emitEvent delays emission, this can be cleaned up
01384   delayRemove = true;
01385 
01386   if (rescan_all)
01387   {
01388     // mark all as dirty
01389     it = m_mapEntries.begin();
01390     for( ; it != m_mapEntries.end(); ++it )
01391       (*it).dirty = true;
01392     rescan_all = false;
01393   }
01394   else
01395   {
01396     // progate dirty flag to dependant entries (e.g. file watches)
01397     it = m_mapEntries.begin();
01398     for( ; it != m_mapEntries.end(); ++it )
01399       if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty )
01400         (*it).propagate_dirty();
01401   }
01402 
01403 #ifdef HAVE_SYS_INOTIFY_H
01404   QList<Entry*> cList;
01405 #endif
01406 
01407   it = m_mapEntries.begin();
01408   for( ; it != m_mapEntries.end(); ++it ) {
01409     // we don't check invalid entries (i.e. remove delayed)
01410     Entry* entry = &(*it);
01411     if (!entry->isValid()) continue;
01412 
01413     const int ev = scanEntry(entry);
01414     if (s_verboseDebug)
01415       kDebug(7001) << "scanEntry for" << entry->path << "says" << ev;
01416 
01417     switch(entry->m_mode) {
01418 #ifdef HAVE_SYS_INOTIFY_H
01419     case INotifyMode:
01420       if ( ev == Deleted ) {
01421         if (s_verboseDebug)
01422           kDebug(7001) << "scanEntry says" << entry->path << "was deleted";
01423         addEntry(0, entry->parentDirectory(), entry, true);
01424       } else if (ev == Created) {
01425         if (s_verboseDebug)
01426           kDebug(7001) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd;
01427         if (entry->wd < 0) {
01428           cList.append(entry);
01429           addWatch(entry);
01430         }
01431       }
01432       break;
01433 #endif
01434     case FAMMode:
01435     case QFSWatchMode:
01436       if (ev == Created) {
01437         addWatch(entry);
01438       }
01439       break;
01440     default:
01441       // dunno about StatMode...
01442       break;
01443     }
01444 
01445 #ifdef HAVE_SYS_INOTIFY_H
01446     if (entry->isDir) {
01447       // Report and clear the the list of files that have changed in this directory.
01448       // Remove duplicates by changing to set and back again:
01449       // we don't really care about preserving the order of the
01450       // original changes.
01451       QStringList pendingFileChanges = entry->m_pendingFileChanges;
01452       pendingFileChanges.removeDuplicates();
01453       Q_FOREACH(const QString &changedFilename, pendingFileChanges) {
01454         if (s_verboseDebug) {
01455           kDebug(7001) << "processing pending file change for" << changedFilename;
01456         }
01457         emitEvent(entry, Changed, changedFilename);
01458       }
01459       entry->m_pendingFileChanges.clear();
01460     }
01461 #endif
01462 
01463     if ( ev != NoChange ) {
01464       emitEvent(entry, ev);
01465     }
01466   }
01467 
01468   if ( timerRunning )
01469     timer.start(freq);
01470 
01471 #ifdef HAVE_SYS_INOTIFY_H
01472   // Remove watch of parent of new created directories
01473   Q_FOREACH(Entry* e, cList)
01474     removeEntry(0, e->parentDirectory(), e);
01475 #endif
01476 
01477   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01478 }
01479 
01480 bool KDirWatchPrivate::isNoisyFile( const char * filename )
01481 {
01482   // $HOME/.X.err grows with debug output, so don't notify change
01483   if ( *filename == '.') {
01484     if (strncmp(filename, ".X.err", 6) == 0) return true;
01485     if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
01486     // fontconfig updates the cache on every KDE app start
01487     // (inclusive kio_thumbnail slaves)
01488     if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
01489   }
01490 
01491   return false;
01492 }
01493 
01494 #ifdef HAVE_FAM
01495 void KDirWatchPrivate::famEventReceived()
01496 {
01497   static FAMEvent fe;
01498 
01499   delayRemove = true;
01500 
01501   //kDebug(7001) << "Fam event received";
01502 
01503   while(use_fam && FAMPending(&fc)) {
01504     if (FAMNextEvent(&fc, &fe) == -1) {
01505       kWarning(7001) << "FAM connection problem, switching to polling.";
01506       use_fam = false;
01507       delete sn; sn = 0;
01508 
01509       // Replace all FAMMode entries with INotify/Stat
01510       EntryMap::Iterator it = m_mapEntries.begin();
01511       for( ; it != m_mapEntries.end(); ++it )
01512         if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01513             Entry* e = &(*it);
01514             addWatch(e);
01515         }
01516     }
01517     else
01518       checkFAMEvent(&fe);
01519   }
01520 
01521   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01522 }
01523 
01524 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01525 {
01526   //kDebug(7001);
01527 
01528   // Don't be too verbose ;-)
01529   if ((fe->code == FAMExists) ||
01530       (fe->code == FAMEndExist) ||
01531       (fe->code == FAMAcknowledge)) return;
01532 
01533   if ( isNoisyFile( fe->filename ) )
01534     return;
01535 
01536   Entry* e = 0;
01537   EntryMap::Iterator it = m_mapEntries.begin();
01538   for( ; it != m_mapEntries.end(); ++it )
01539     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01540        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01541       e = &(*it);
01542       break;
01543     }
01544 
01545   // Entry* e = static_cast<Entry*>(fe->userdata);
01546 
01547   if (s_verboseDebug) { // don't enable this except when debugging, see #88538
01548     kDebug(7001)  << "Processing FAM event ("
01549                 << ((fe->code == FAMChanged) ? "FAMChanged" :
01550                     (fe->code == FAMDeleted) ? "FAMDeleted" :
01551                     (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01552                     (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01553                     (fe->code == FAMCreated) ? "FAMCreated" :
01554                     (fe->code == FAMMoved) ? "FAMMoved" :
01555                     (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01556                     (fe->code == FAMExists) ? "FAMExists" :
01557                     (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01558                   << ", " << fe->filename
01559                   << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ") e=" << e;
01560   }
01561 
01562   if (!e) {
01563     // this happens e.g. for FAMAcknowledge after deleting a dir...
01564     //    kDebug(7001) << "No entry for FAM event ?!";
01565     return;
01566   }
01567 
01568   if (e->m_status == NonExistent) {
01569     kDebug(7001) << "FAM event for nonExistent entry " << e->path;
01570     return;
01571   }
01572 
01573   // Delayed handling. This rechecks changes with own stat calls.
01574   e->dirty = true;
01575   if (!rescan_timer.isActive())
01576     rescan_timer.start(m_PollInterval); // singleshot
01577 
01578     // needed FAM control actions on FAM events
01579     switch (fe->code) {
01580     case FAMDeleted:
01581         // fe->filename is an absolute path when a watched file-or-dir is deleted
01582         if (!QDir::isRelativePath(QFile::decodeName(fe->filename))) {
01583           FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
01584           kDebug(7001)  << "Cancelled FAMReq"
01585                         << FAMREQUEST_GETREQNUM(&(e->fr))
01586                         << "for" << e->path;
01587           e->m_status = NonExistent;
01588           e->m_ctime = invalid_ctime;
01589           emitEvent(e, Deleted, e->path);
01590           // Add entry to parent dir to notice if the entry gets recreated
01591           addEntry(0, e->parentDirectory(), e, true /*isDir*/);
01592         } else {
01593             // A file in this directory has been removed, and wasn't explicitely watched.
01594             // We could still inform clients, like inotify does? But stat can't.
01595             // For now we just marked e dirty and slotRescan will emit the dir as dirty.
01596             //kDebug(7001) << "Got FAMDeleted for" << QFile::decodeName(fe->filename) << "in" << e->path << ". Absolute path -> NOOP!";
01597         }
01598         break;
01599 
01600       case FAMCreated: {
01601           // check for creation of a directory we have to watch
01602         QString tpath(e->path + QLatin1Char('/') + QFile::decodeName(fe->filename));
01603 
01604         // This code is very similar to the one in inotifyEventReceived...
01605         Entry* sub_entry = e->findSubEntry(tpath);
01606         if (sub_entry /*&& sub_entry->isDir*/) {
01607           // We were waiting for this new file/dir to be created
01608           emitEvent(sub_entry, Created);
01609           sub_entry->dirty = true;
01610           rescan_timer.start(0); // process this asap, to start watching that dir
01611         } else if (e->isDir && !e->m_clients.empty()) {
01612           bool isDir = false;
01613           const QList<Client *> clients = e->clientsForFileOrDir(tpath, &isDir);
01614           Q_FOREACH(Client *client, clients) {
01615             addEntry (client->instance, tpath, 0, isDir,
01616                       isDir ? client->m_watchModes : KDirWatch::WatchDirOnly);
01617           }
01618 
01619           if (!clients.isEmpty()) {
01620             emitEvent(e, Created, tpath);
01621 
01622             kDebug(7001).nospace() << clients.count() << " instance(s) monitoring the new "
01623                                    << (isDir ? "dir " : "file ") << tpath;
01624           }
01625         }
01626       }
01627         break;
01628       default:
01629         break;
01630     }
01631 }
01632 #else
01633 void KDirWatchPrivate::famEventReceived()
01634 {
01635     kWarning (7001) << "Fam event received but FAM is not supported";
01636 }
01637 #endif
01638 
01639 
01640 void KDirWatchPrivate::statistics()
01641 {
01642   EntryMap::Iterator it;
01643 
01644   kDebug(7001) << "Entries watched:";
01645   if (m_mapEntries.count()==0) {
01646     kDebug(7001) << "  None.";
01647   }
01648   else {
01649     it = m_mapEntries.begin();
01650     for( ; it != m_mapEntries.end(); ++it ) {
01651       Entry* e = &(*it);
01652       kDebug(7001) << "  " << *e;
01653 
01654       foreach(Client* c, e->m_clients) {
01655         QByteArray pending;
01656         if (c->watchingStopped) {
01657           if (c->pending & Deleted) pending += "deleted ";
01658           if (c->pending & Created) pending += "created ";
01659           if (c->pending & Changed) pending += "changed ";
01660           if (!pending.isEmpty()) pending = " (pending: " + pending + ')';
01661           pending = ", stopped" + pending;
01662         }
01663         kDebug(7001)  << "    by " << c->instance->objectName()
01664                       << " (" << c->count << " times)" << pending;
01665       }
01666       if (e->m_entries.count()>0) {
01667         kDebug(7001) << "    dependent entries:";
01668         foreach(Entry *d, e->m_entries) {
01669           kDebug(7001) << "      " << d << d->path << (d->m_status == NonExistent ? "NonExistent" : "EXISTS!!! ERROR!");
01670           if (s_verboseDebug) {
01671             Q_ASSERT(d->m_status == NonExistent); // it doesn't belong here otherwise
01672           }
01673         }
01674       }
01675     }
01676   }
01677 }
01678 
01679 #ifdef HAVE_QFILESYSTEMWATCHER
01680 // Slot for QFileSystemWatcher
01681 void KDirWatchPrivate::fswEventReceived(const QString &path)
01682 {
01683   if (s_verboseDebug)
01684     kDebug(7001) << path;
01685   EntryMap::Iterator it = m_mapEntries.find(path);
01686   if(it != m_mapEntries.end()) {
01687     Entry* e = &(*it);
01688     e->dirty = true;
01689     const int ev = scanEntry(e);
01690     if (s_verboseDebug)
01691       kDebug(7001) << "scanEntry for" << e->path << "says" << ev;
01692     if (ev != NoChange)
01693       emitEvent(e, ev);
01694     if(ev == Deleted) {
01695       if (e->isDir)
01696         addEntry(0, e->parentDirectory(), e, true);
01697       else
01698         addEntry(0, QFileInfo(e->path).absolutePath(), e, true);
01699     } else if (ev == Created) {
01700       // We were waiting for it to appear; now watch it
01701       addWatch(e);
01702     } else if (e->isDir) {
01703       // Check if any file or dir was created under this directory, that we were waiting for
01704       Q_FOREACH(Entry* sub_entry, e->m_entries) {
01705           fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed
01706       }
01707     }
01708   }
01709 }
01710 #else
01711 void KDirWatchPrivate::fswEventReceived(const QString &path)
01712 {
01713     Q_UNUSED(path);
01714     kWarning (7001) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported";
01715 }
01716 #endif    // HAVE_QFILESYSTEMWATCHER
01717 
01718 //
01719 // Class KDirWatch
01720 //
01721 
01722 K_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf)
01723 KDirWatch* KDirWatch::self()
01724 {
01725   return s_pKDirWatchSelf;
01726 }
01727 
01728 bool KDirWatch::exists()
01729 {
01730   return s_pKDirWatchSelf != 0;
01731 }
01732 
01733 KDirWatch::KDirWatch (QObject* parent)
01734   : QObject(parent), d(createPrivate())
01735 {
01736   static int nameCounter = 0;
01737 
01738   nameCounter++;
01739   setObjectName(QString::fromLatin1("KDirWatch-%1").arg(nameCounter) );
01740 
01741   d->ref();
01742 
01743   d->_isStopped = false;
01744 }
01745 
01746 KDirWatch::~KDirWatch()
01747 {
01748   d->removeEntries(this);
01749   if ( d->deref() )
01750   {
01751     // delete it if it's the last one
01752     delete d;
01753     dwp_self = 0;
01754   }
01755 }
01756 
01757 void KDirWatch::addDir( const QString& _path, WatchModes watchModes)
01758 {
01759   if (d) d->addEntry(this, _path, 0, true, watchModes);
01760 }
01761 
01762 void KDirWatch::addFile( const QString& _path )
01763 {
01764   if ( !d )
01765     return;
01766 
01767   d->addEntry(this, _path, 0, false);
01768 }
01769 
01770 QDateTime KDirWatch::ctime( const QString &_path ) const
01771 {
01772   KDirWatchPrivate::Entry* e = d->entry(_path);
01773 
01774   if (!e)
01775     return QDateTime();
01776 
01777   return QDateTime::fromTime_t(e->m_ctime);
01778 }
01779 
01780 void KDirWatch::removeDir( const QString& _path )
01781 {
01782   if (d) d->removeEntry(this, _path, 0);
01783 }
01784 
01785 void KDirWatch::removeFile( const QString& _path )
01786 {
01787   if (d) d->removeEntry(this, _path, 0);
01788 }
01789 
01790 bool KDirWatch::stopDirScan( const QString& _path )
01791 {
01792   if (d) {
01793     KDirWatchPrivate::Entry *e = d->entry(_path);
01794     if (e && e->isDir) return d->stopEntryScan(this, e);
01795   }
01796   return false;
01797 }
01798 
01799 bool KDirWatch::restartDirScan( const QString& _path )
01800 {
01801   if (d) {
01802     KDirWatchPrivate::Entry *e = d->entry(_path);
01803     if (e && e->isDir)
01804       // restart without notifying pending events
01805       return d->restartEntryScan(this, e, false);
01806   }
01807   return false;
01808 }
01809 
01810 void KDirWatch::stopScan()
01811 {
01812   if (d) {
01813     d->stopScan(this);
01814     d->_isStopped = true;
01815   }
01816 }
01817 
01818 bool KDirWatch::isStopped()
01819 {
01820   return d->_isStopped;
01821 }
01822 
01823 void KDirWatch::startScan( bool notify, bool skippedToo )
01824 {
01825   if (d) {
01826     d->_isStopped = false;
01827     d->startScan(this, notify, skippedToo);
01828   }
01829 }
01830 
01831 
01832 bool KDirWatch::contains( const QString& _path ) const
01833 {
01834   KDirWatchPrivate::Entry* e = d->entry(_path);
01835   if (!e)
01836      return false;
01837 
01838   foreach(KDirWatchPrivate::Client* client, e->m_clients) {
01839     if (client->instance == this)
01840       return true;
01841   }
01842 
01843   return false;
01844 }
01845 
01846 void KDirWatch::statistics()
01847 {
01848   if (!dwp_self) {
01849     kDebug(7001) << "KDirWatch not used";
01850     return;
01851   }
01852   dwp_self->statistics();
01853 }
01854 
01855 
01856 void KDirWatch::setCreated( const QString & _file )
01857 {
01858   kDebug(7001) << objectName() << "emitting created" << _file;
01859   emit created( _file );
01860 }
01861 
01862 void KDirWatch::setDirty( const QString & _file )
01863 {
01864   //kDebug(7001) << objectName() << "emitting dirty" << _file;
01865   emit dirty( _file );
01866 }
01867 
01868 void KDirWatch::setDeleted( const QString & _file )
01869 {
01870   kDebug(7001) << objectName() << "emitting deleted" << _file;
01871   emit deleted( _file );
01872 }
01873 
01874 KDirWatch::Method KDirWatch::internalMethod()
01875 {
01876   return d->m_preferredMethod;
01877 }
01878 
01879 
01880 #include "kdirwatch.moc"
01881 #include "kdirwatch_p.moc"
01882 
01883 //sven

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal