• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KIO

kdirwatch.cpp

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

KIO

Skip menu "KIO"
  • Main Page
  • 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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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