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

KDECore

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • 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.5
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