kio Library API Documentation

kdirwatch.cpp

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 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License version 2 as published by the Free Software Foundation.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00017    Boston, MA 02111-1307, USA.
00018 */
00019 
00020 
00021 // CHANGES:
00022 // Februar 2002 - Add file watching and remote mount check for STAT
00023 // Mar 30, 2001 - Native support for Linux dir change notification.
00024 // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
00025 // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
00026 // May 23. 1998 - Removed static pointer - you can have more instances.
00027 // It was Needed for KRegistry. KDirWatch now emits signals and doesn't
00028 // call (or need) KFM. No more URL's - just plain paths. (sven)
00029 // Mar 29. 1998 - added docs, stop/restart for particular Dirs and
00030 // deep copies for list of dirs. (sven)
00031 // Mar 28. 1998 - Created.  (sven)
00032 
00033 
00034 #include <config.h>
00035 
00036 #ifdef HAVE_DNOTIFY
00037 //#include <sys/stat.h>
00038 #include <unistd.h>
00039 //#include <time.h>
00040 #include <fcntl.h>
00041 #include <signal.h>
00042 #include <errno.h>
00043 #endif
00044 
00045 #include <assert.h>
00046 #include <qdir.h>
00047 #include <qfile.h>
00048 #include <qintdict.h>
00049 #include <qptrlist.h>
00050 #include <qsocketnotifier.h>
00051 #include <qstringlist.h>
00052 #include <qtimer.h>
00053 
00054 #include <kapplication.h>
00055 #include <kdebug.h>
00056 #include <kconfig.h>
00057 #include <kglobal.h>
00058 #include <kstaticdeleter.h>
00059 
00060 #include "kdirwatch.h"
00061 #include "kdirwatch_p.h"
00062 #include "global.h" //  KIO::probably_slow_mounted
00063 
00064 #define NO_NOTIFY (time_t) 0
00065 
00066 static KDirWatchPrivate* dwp_self = 0;
00067 
00068 #ifdef HAVE_DNOTIFY
00069 
00070 #include <sys/utsname.h>
00071 
00072 static int dnotify_signal = 0;
00073 
00074 /* DNOTIFY signal handler
00075  *
00076  * As this is called asynchronously, only a flag is set and
00077  * a rescan is requested.
00078  * This is done by writing into a pipe to trigger a QSocketNotifier
00079  * watching on this pipe: a timer is started and after a timeout,
00080  * the rescan is done.
00081  */
00082 void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
00083 {
00084   if (!dwp_self) return;
00085 
00086   // write might change errno, we have to save it and restore it
00087   // (Richard Stevens, Advanced programming in the Unix Environment)
00088   int saved_errno = errno;
00089 
00090   Entry* e = dwp_self->fd_Entry.find(si->si_fd);
00091 
00092 //  kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
00093 //      << QString(e ? e->path:"unknown") << endl;
00094 
00095   if(!e || e->dn_fd != si->si_fd) {
00096     qDebug("fatal error in KDirWatch");
00097   } else
00098     e->dn_dirty = true;
00099 
00100   char c = 0;
00101   write(dwp_self->mPipe[1], &c, 1);
00102   errno = saved_errno;
00103 }
00104 
00105 static struct sigaction old_sigio_act;
00106 /* DNOTIFY SIGIO signal handler
00107  *
00108  * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
00109  */
00110 void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
00111 {
00112   if (dwp_self)
00113   {
00114     // write might change errno, we have to save it and restore it
00115     // (Richard Stevens, Advanced programming in the Unix Environment)
00116     int saved_errno = errno;
00117 
00118     dwp_self->rescan_all = true;
00119     char c = 0;
00120     write(dwp_self->mPipe[1], &c, 1);
00121 
00122     errno = saved_errno;
00123   }
00124   
00125   // Call previous signal handler
00126   if (old_sigio_act.sa_flags & SA_SIGINFO)
00127   {
00128     if (old_sigio_act.sa_sigaction)
00129       (*old_sigio_act.sa_sigaction)(sig, si, p);
00130   }
00131   else
00132   {
00133     if ((old_sigio_act.sa_handler != SIG_DFL) &&
00134         (old_sigio_act.sa_handler != SIG_IGN))
00135       (*old_sigio_act.sa_handler)(sig);
00136   }
00137 }
00138 #endif
00139 
00140 
00141 //
00142 // Class KDirWatchPrivate (singleton)
00143 //
00144 
00145 /* All entries (files/directories) to be watched in the
00146  * application (coming from multiple KDirWatch instances)
00147  * are registered in a single KDirWatchPrivate instance.
00148  *
00149  * At the moment, the following methods for file watching
00150  * are supported:
00151  * - Polling: All files to be watched are polled regularly
00152  *   using stat (more precise: QFileInfo.lastModified()).
00153  *   The polling frequency is determined from global kconfig
00154  *   settings, defaulting to 500 ms for local directories
00155  *   and 5000 ms for remote mounts
00156  * - FAM (File Alternation Monitor): first used on IRIX, SGI
00157  *   has ported this method to LINUX. It uses a kernel part
00158  *   (IMON, sending change events to /dev/imon) and a user
00159  *   level damon (fam), to which applications connect for
00160  *   notification of file changes. For NFS, the fam damon
00161  *   on the NFS server machine is used; if IMON is not built
00162  *   into the kernel, fam uses polling for local files.
00163  * - DNOTIFY: In late LINUX 2.3.x, directory notification was
00164  *   introduced. By opening a directory, you can request for
00165  *   UNIX signals to be sent to the process when a directory
00166  *   is changed.
00167  */
00168 
00169 KDirWatchPrivate::KDirWatchPrivate()
00170 {
00171   timer = new QTimer(this);
00172   connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00173   freq = 3600000; // 1 hour as upper bound
00174   statEntries = 0;
00175   delayRemove = false;
00176   m_ref = 0;
00177 
00178   KConfigGroup config(KGlobal::config(), QCString("DirWatch"));
00179   m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
00180   m_PollInterval = config.readNumEntry("PollInterval", 500);
00181 
00182   QString available("Stat");
00183 
00184 #ifdef HAVE_FAM
00185   // It's possible that FAM server can't be started
00186   if (FAMOpen(&fc) ==0) {
00187     available += ", 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     kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
00196     use_fam=false;
00197   }
00198 #endif
00199 
00200 #ifdef HAVE_DNOTIFY
00201   supports_dnotify = true; // not guilty until proven guilty
00202   rescan_all = false;
00203   struct utsname uts;
00204   int major, minor, patch;
00205   if (uname(&uts) < 0)
00206     supports_dnotify = false; // *shrug*
00207   else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00208     supports_dnotify = false; // *shrug*
00209   else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
00210     kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
00211     supports_dnotify = false; 
00212   }
00213 
00214   if( supports_dnotify ) {
00215     available += ", DNotify";
00216 
00217     pipe(mPipe);
00218     fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
00219     fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
00220     mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
00221     connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
00222     connect(&mTimer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00223     // Install the signal handler only once
00224     if ( dnotify_signal == 0 )
00225     {
00226        dnotify_signal = SIGRTMIN + 8;
00227 
00228        struct sigaction act;
00229        act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
00230        sigemptyset(&act.sa_mask);
00231        act.sa_flags = SA_SIGINFO;
00232 #ifdef SA_RESTART
00233        act.sa_flags |= SA_RESTART;
00234 #endif
00235        sigaction(dnotify_signal, &act, NULL);
00236     
00237        act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
00238        sigaction(SIGIO, &act, &old_sigio_act);
00239     }
00240   }
00241   else
00242   {
00243     mPipe[0] = -1;
00244     mPipe[1] = -1;
00245   }
00246 #endif
00247 
00248   kdDebug(7001) << "Available methods: " << available << endl;
00249 }
00250 
00251 /* This should never be called, but doesn't harm */
00252 KDirWatchPrivate::~KDirWatchPrivate()
00253 {
00254   timer->stop();
00255 
00256   /* remove all entries being watched */
00257   removeEntries(0);
00258 
00259 #ifdef HAVE_FAM
00260   if (use_fam) {
00261     FAMClose(&fc);
00262     kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
00263   }
00264 #endif
00265 #ifdef HAVE_DNOTIFY
00266   close(mPipe[0]);
00267   close(mPipe[1]);
00268 #endif
00269 }
00270 
00271 #ifdef HAVE_DNOTIFY
00272 void KDirWatchPrivate::slotActivated()
00273 {
00274    char dummy_buf[100];
00275    read(mPipe[0], &dummy_buf, 100);
00276 
00277    if (!mTimer.isActive())
00278       mTimer.start(200, true);
00279 }
00280 
00281 /* In DNOTIFY mode, only entries which are marked dirty are scanned.
00282  * We first need to mark all yet nonexistant, but possible created
00283  * entries as dirty...
00284  */
00285 void KDirWatchPrivate::Entry::propagate_dirty()
00286 {
00287   Entry* sub_entry;
00288   for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next())
00289   {
00290      if (!sub_entry->dn_dirty)
00291      {
00292         sub_entry->dn_dirty = true;
00293         sub_entry->propagate_dirty();
00294      }
00295   }
00296 }
00297 
00298 #else // !HAVE_DNOTIFY
00299 // slots always have to be defined...
00300 void KDirWatchPrivate::slotActivated() {}
00301 #endif
00302 
00303 /* A KDirWatch instance is interested in getting events for
00304  * this file/Dir entry.
00305  */
00306 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
00307 {
00308   Client* client = m_clients.first();
00309   for(;client; client = m_clients.next())
00310     if (client->instance == instance) break;
00311 
00312   if (client) {
00313     client->count++;
00314     return;
00315   }
00316 
00317   client = new Client;
00318   client->instance = instance;
00319   client->count = 1;
00320   client->watchingStopped = instance->isStopped();
00321   client->pending = NoChange;
00322 
00323   m_clients.append(client);
00324 }
00325 
00326 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
00327 {
00328   Client* client = m_clients.first();
00329   for(;client; client = m_clients.next())
00330     if (client->instance == instance) break;
00331 
00332   if (client) {
00333     client->count--;
00334     if (client->count == 0) {
00335       m_clients.removeRef(client);
00336       delete client;
00337     }
00338   }
00339 }
00340 
00341 /* get number of clients */
00342 int KDirWatchPrivate::Entry::clients()
00343 {
00344   int clients = 0;
00345   Client* client = m_clients.first();
00346   for(;client; client = m_clients.next())
00347     clients += client->count;
00348 
00349   return clients;
00350 }
00351 
00352 
00353 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00354 {
00355 // we only support absolute paths
00356   if (_path.left(1) != "/") {
00357     return 0;
00358   }
00359 
00360   QString path = _path;
00361 
00362   if ( path.length() > 1 && path.right(1) == "/" )
00363     path.truncate( path.length() - 1 );
00364 
00365   EntryMap::Iterator it = m_mapEntries.find( path );
00366   if ( it == m_mapEntries.end() )
00367     return 0;
00368   else
00369     return &(*it);
00370 }
00371 
00372 // set polling frequency for a entry and adjust global freq if needed
00373 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00374 {
00375   e->freq = newFreq;
00376 
00377   // a reasonable frequency for the global polling timer
00378   if (e->freq < freq) {
00379     freq = e->freq;
00380     if (timer->isActive()) timer->changeInterval(freq);
00381     kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
00382   }
00383 }
00384 
00385 
00386 #if defined(HAVE_FAM)
00387 // setup FAM notification, returns false if not possible
00388 bool KDirWatchPrivate::useFAM(Entry* e)
00389 {
00390   if (!use_fam) return false;
00391 
00392   e->m_mode = FAMMode;
00393 
00394   if (e->isDir) {
00395     if (e->m_status == NonExistent) {
00396       // If the directory does not exist we watch the parent directory
00397       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00398     }
00399     else {
00400       int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
00401                    &(e->fr), e);
00402       if (res<0) {
00403     e->m_mode = UnknownMode;
00404     use_fam=false;
00405     return false;
00406       }
00407       kdDebug(7001) << " Setup FAM (Req "
00408             << FAMREQUEST_GETREQNUM(&(e->fr))
00409             << ") for " << e->path << endl;
00410     }
00411   }
00412   else {
00413     if (e->m_status == NonExistent) {
00414       // If the file does not exist we watch the directory
00415       addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00416     }
00417     else {
00418       int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
00419                    &(e->fr), e);
00420       if (res<0) {
00421     e->m_mode = UnknownMode;
00422     use_fam=false;
00423     return false;
00424       }
00425 
00426       kdDebug(7001) << " Setup FAM (Req "
00427             << FAMREQUEST_GETREQNUM(&(e->fr))
00428             << ") for " << e->path << endl;
00429     }
00430   }
00431 
00432   // handle FAM events to avoid deadlock
00433   // (FAM sends back all files in a directory when monitoring)
00434   famEventReceived();
00435 
00436   return true;
00437 }
00438 #endif
00439 
00440 
00441 #ifdef HAVE_DNOTIFY
00442 // setup DNotify notification, returns false if not possible
00443 bool KDirWatchPrivate::useDNotify(Entry* e)
00444 {
00445   e->dn_fd = 0;
00446   if (!supports_dnotify) return false;
00447 
00448   e->m_mode = DNotifyMode;
00449 
00450   if (e->isDir) {
00451     e->dn_dirty = false;
00452     if (e->m_status == Normal) {
00453       int fd = open(QFile::encodeName(e->path).data(), O_RDONLY);
00454       if (fd<0) {
00455     e->m_mode = UnknownMode;
00456     return false;
00457       }
00458 
00459       int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00460       // if dependant is a file watch, we check for MODIFY & ATTRIB too
00461       for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00462     if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00463 
00464       if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
00465      fcntl(fd, F_NOTIFY, mask) < 0) {
00466 
00467     kdDebug(7001) << "Not using Linux Directory Notifications."
00468               << endl;
00469     supports_dnotify = false;
00470     ::close(fd);
00471     e->m_mode = UnknownMode;
00472     return false;
00473       }
00474 
00475       fd_Entry.replace(fd, e);
00476       e->dn_fd = fd;
00477 
00478       kdDebug(7001) << " Setup DNotify (fd " << fd
00479             << ") for " << e->path << endl;
00480     }
00481     else { // NotExisting
00482       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00483     }
00484   }
00485   else { // File
00486     // we always watch the directory (DNOTIFY can't watch files alone)
00487     // this notifies us about changes of files therein
00488     addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00489   }
00490 
00491   return true;
00492 }
00493 #endif
00494 
00495 
00496 bool KDirWatchPrivate::useStat(Entry* e)
00497 {
00498   if (KIO::probably_slow_mounted(e->path))
00499     useFreq(e, m_nfsPollInterval);
00500   else
00501     useFreq(e, m_PollInterval);
00502 
00503   if (e->m_mode != StatMode) {
00504     e->m_mode = StatMode;
00505     statEntries++;
00506 
00507     if ( statEntries == 1 ) {
00508       // if this was first STAT entry (=timer was stopped)
00509       timer->start(freq);      // then start the timer
00510       kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
00511     }
00512   }
00513 
00514   kdDebug(7001) << " Setup Stat (freq " << e->freq
00515         << ") for " << e->path << endl;
00516 
00517   return true;
00518 }
00519 
00520 
00521 /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
00522  * providing in <isDir> the type of the entry to be watched.
00523  * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
00524  * this entry needs another entry to watch himself (when notExistent).
00525  */
00526 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00527                 Entry* sub_entry, bool isDir)
00528 {
00529   QString path = _path;
00530   if (path.startsWith("/dev/") || (path == "/dev"))
00531     return; // Don't even go there.
00532 
00533   if ( path.length() > 1 && path.right(1) == "/" )
00534     path.truncate( path.length() - 1 );
00535 
00536   EntryMap::Iterator it = m_mapEntries.find( path );
00537   if ( it != m_mapEntries.end() )
00538   {
00539     if (sub_entry) {
00540        (*it).m_entries.append(sub_entry);
00541        kdDebug(7001) << "Added already watched Entry " << path
00542              << " (for " << sub_entry->path << ")" << endl;
00543 #ifdef HAVE_DNOTIFY
00544        Entry* e = &(*it);
00545        if( e->dn_fd > 0 ) {
00546          int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00547          // if dependant is a file watch, we check for MODIFY & ATTRIB too
00548          for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00549            if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00550      if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
00551        ::close(e->dn_fd);
00552        e->m_mode = UnknownMode;
00553        fd_Entry.remove(e->dn_fd);
00554            e->dn_fd = 0;
00555            useStat( e );
00556          }
00557        }
00558 #endif
00559     }
00560     else {
00561        (*it).addClient(instance);
00562        kdDebug(7001) << "Added already watched Entry " << path
00563              << " (now " <<  (*it).clients() << " clients)"
00564              << QString(" [%1]").arg(instance->name()) << endl;
00565     }
00566     return;
00567   }
00568 
00569   // we have a new path to watch
00570 
00571   QFileInfo info(path);
00572 
00573   Entry newEntry;
00574   m_mapEntries.insert( path, newEntry );
00575   // the insert does a copy, so we have to use <e> now
00576   Entry* e = &(m_mapEntries[path]);
00577 
00578   if (info.exists()) {
00579     e->isDir = info.isDir();
00580 
00581     if (e->isDir && !isDir)
00582       qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
00583     else if (!e->isDir && isDir)
00584       qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
00585 
00586     e->m_ctime = info.lastModified();
00587     e->m_status = Normal;
00588   }
00589   else {
00590     e->isDir = isDir;
00591     e->m_ctime = QDateTime(); // invalid
00592     e->m_status = NonExistent;
00593   }
00594 
00595   e->path = path;
00596   if (sub_entry)
00597      e->m_entries.append(sub_entry);
00598   else
00599     e->addClient(instance);
00600 
00601   kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
00602         << (e->m_status == NonExistent ? " NotExisting" : "")
00603         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00604         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00605         << endl;
00606 
00607 
00608   // now setup the notification method
00609   e->m_mode = UnknownMode;
00610   e->msecLeft = 0;
00611 
00612 #if defined(HAVE_FAM)
00613   if (useFAM(e)) return;
00614 #endif
00615 
00616 #ifdef HAVE_DNOTIFY
00617   if (useDNotify(e)) return;
00618 #endif
00619 
00620   useStat(e);
00621 }
00622 
00623 
00624 void KDirWatchPrivate::removeEntry( KDirWatch* instance,
00625                     const QString& _path, Entry* sub_entry )
00626 {
00627   Entry* e = entry(_path);
00628   if (!e) {
00629     kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
00630     return;
00631   }
00632 
00633   if (sub_entry)
00634     e->m_entries.removeRef(sub_entry);
00635   else
00636     e->removeClient(instance);
00637 
00638   if (e->m_clients.count() || e->m_entries.count())
00639     return;
00640 
00641   if (delayRemove) {
00642     // removeList is allowed to contain any entry at most once
00643     if (removeList.findRef(e)==-1)
00644     removeList.append(e);
00645     // now e->isValid() is false
00646     return;
00647   }
00648 
00649 #ifdef HAVE_FAM
00650   if (e->m_mode == FAMMode) {
00651     if ( e->m_status == Normal) {
00652       FAMCancelMonitor(&fc, &(e->fr) );
00653       kdDebug(7001) << "Cancelled FAM (Req "
00654             << FAMREQUEST_GETREQNUM(&(e->fr))
00655             << ") for " << e->path << endl;
00656     }
00657     else {
00658       if (e->isDir)
00659     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00660       else
00661     removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00662     }
00663   }
00664 #endif
00665 
00666 #ifdef HAVE_DNOTIFY
00667   if (e->m_mode == DNotifyMode) {
00668     if (!e->isDir) {
00669       removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00670     }
00671     else { // isDir
00672       // must close the FD.
00673       if ( e->m_status == Normal) {
00674     if (e->dn_fd) {
00675       ::close(e->dn_fd);
00676       fd_Entry.remove(e->dn_fd);
00677 
00678       kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
00679             << ") for " << e->path << endl;
00680       e->dn_fd = 0;
00681 
00682     }
00683       }
00684       else {
00685     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00686       }
00687     }
00688   }
00689 #endif
00690 
00691   if (e->m_mode == StatMode) {
00692     statEntries--;
00693     if ( statEntries == 0 ) {
00694       timer->stop(); // stop timer if lists are empty
00695       kdDebug(7001) << " Stopped Polling Timer" << endl;
00696     }
00697   }
00698 
00699   kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00700         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00701         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00702         << endl;
00703   m_mapEntries.remove( e->path ); // <e> not valid any more
00704 }
00705 
00706 
00707 /* Called from KDirWatch destructor:
00708  * remove <instance> as client from all entries
00709  */
00710 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
00711 {
00712   QPtrList<Entry> list;
00713   int minfreq = 3600000;
00714 
00715   // put all entries where instance is a client in list
00716   EntryMap::Iterator it = m_mapEntries.begin();
00717   for( ; it != m_mapEntries.end(); ++it ) {
00718     Client* c = (*it).m_clients.first();
00719     for(;c;c=(*it).m_clients.next())
00720       if (c->instance == instance) break;
00721     if (c) {
00722       c->count = 1; // forces deletion of instance as client
00723       list.append(&(*it));
00724     }
00725     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
00726       minfreq = (*it).freq;
00727   }
00728 
00729   for(Entry* e=list.first();e;e=list.next())
00730     removeEntry(instance, e->path, 0);
00731 
00732   if (minfreq > freq) {
00733     // we can decrease the global polling frequency
00734     freq = minfreq;
00735     if (timer->isActive()) timer->changeInterval(freq);
00736     kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
00737   }
00738 }
00739 
00740 // instance ==0: stop scanning for all instances
00741 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
00742 {
00743   int stillWatching = 0;
00744   Client* c = e->m_clients.first();
00745   for(;c;c=e->m_clients.next()) {
00746     if (!instance || instance == c->instance)
00747       c->watchingStopped = true;
00748     else if (!c->watchingStopped)
00749       stillWatching += c->count;
00750   }
00751 
00752   kdDebug(7001) << instance->name() << " stopped scanning " << e->path
00753         << " (now " << stillWatching << " watchers)" << endl;
00754 
00755   if (stillWatching == 0) {
00756     // if nobody is interested, we don't watch
00757     e->m_ctime = QDateTime(); // invalid
00758     //    e->m_status = Normal;
00759   }
00760   return true;
00761 }
00762 
00763 // instance ==0: start scanning for all instances
00764 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
00765                      bool notify)
00766 {
00767   int wasWatching = 0, newWatching = 0;
00768   Client* c = e->m_clients.first();
00769   for(;c;c=e->m_clients.next()) {
00770     if (!c->watchingStopped)
00771       wasWatching += c->count;
00772     else if (!instance || instance == c->instance) {
00773       c->watchingStopped = false;
00774       newWatching += c->count;
00775     }
00776   }
00777   if (newWatching == 0)
00778     return false;
00779 
00780   kdDebug(7001) << instance->name() << " restarted scanning " << e->path
00781         << " (now " << wasWatching+newWatching << " watchers)" << endl;
00782 
00783   // restart watching and emit pending events
00784 
00785   int ev = NoChange;
00786   if (wasWatching == 0) {
00787     if (!notify) {
00788       QFileInfo info(e->path);
00789       if (info.exists()) {
00790     e->m_ctime = info.lastModified();
00791     e->m_status = Normal;
00792       }
00793       else {
00794     e->m_ctime = QDateTime(); // invalid
00795     e->m_status = NonExistent;
00796       }
00797     }
00798     e->msecLeft = 0;
00799     ev = scanEntry(e);
00800   }
00801   emitEvent(e,ev);
00802 
00803   return true;
00804 }
00805 
00806 // instance ==0: stop scanning for all instances
00807 void KDirWatchPrivate::stopScan(KDirWatch* instance)
00808 {
00809   EntryMap::Iterator it = m_mapEntries.begin();
00810   for( ; it != m_mapEntries.end(); ++it )
00811     stopEntryScan(instance, &(*it));
00812 }
00813 
00814 
00815 void KDirWatchPrivate::startScan(KDirWatch* instance,
00816                  bool notify, bool skippedToo )
00817 {
00818   if (!notify)
00819     resetList(instance,skippedToo);
00820 
00821   EntryMap::Iterator it = m_mapEntries.begin();
00822   for( ; it != m_mapEntries.end(); ++it )
00823     restartEntryScan(instance, &(*it), notify);
00824 
00825   // timer should still be running when in polling mode
00826 }
00827 
00828 
00829 // clear all pending events, also from stopped
00830 void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
00831                   bool skippedToo )
00832 {
00833   EntryMap::Iterator it = m_mapEntries.begin();
00834   for( ; it != m_mapEntries.end(); ++it ) {
00835 
00836     Client* c = (*it).m_clients.first();
00837     for(;c;c=(*it).m_clients.next())
00838       if (!c->watchingStopped || skippedToo)
00839     c->pending = NoChange;
00840   }
00841 }
00842 
00843 // Return event happened on <e>
00844 //
00845 int KDirWatchPrivate::scanEntry(Entry* e)
00846 {
00847 #ifdef HAVE_FAM
00848   // we do not stat entries using FAM
00849   if (e->m_mode == FAMMode) return NoChange;
00850 #endif
00851 
00852   // Shouldn't happen: Ignore "unknown" notification method
00853   if (e->m_mode == UnknownMode) return NoChange;
00854 
00855 #ifdef HAVE_DNOTIFY
00856   if (e->m_mode == DNotifyMode) {
00857     // we know nothing has changed, no need to stat
00858     if(!e->dn_dirty) return NoChange;
00859     e->dn_dirty = false;
00860   }
00861 #endif
00862 
00863   if (e->m_mode == StatMode) {
00864     // only scan if timeout on entry timer happens;
00865     // e.g. when using 500msec global timer, a entry
00866     // with freq=5000 is only watched every 10th time
00867 
00868     e->msecLeft -= freq;
00869     if (e->msecLeft>0) return NoChange;
00870     e->msecLeft += e->freq;
00871   }
00872 
00873   QFileInfo info(e->path);
00874   if (info.exists()) {
00875 
00876     if (e->m_status == NonExistent) {
00877       e->m_ctime = info.lastModified();
00878       e->m_status = Normal;
00879       return Created;
00880     }
00881 
00882     if ( e->m_ctime.isValid() &&
00883      (info.lastModified() != e->m_ctime) ) {
00884       e->m_ctime = info.lastModified();
00885       return Changed;
00886     }
00887 
00888     return NoChange;
00889   }
00890 
00891   // dir/file doesn't exist
00892 
00893   if (!e->m_ctime.isValid())
00894     return NoChange;
00895 
00896   e->m_ctime = QDateTime(); // invalid
00897   e->m_status = NonExistent;
00898 
00899   return Deleted;
00900 }
00901 
00902 /* Notify all interested KDirWatch instances about a given event on an entry
00903  * and stored pending events. When watching is stopped, the event is
00904  * added to the pending events.
00905  */
00906 void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
00907 {
00908   QString path = e->path;
00909   if (!fileName.isEmpty()) {
00910     if (fileName[0] == '/')
00911       path = fileName;
00912     else
00913       path += "/" + fileName;
00914   }
00915 
00916   Client* c = e->m_clients.first();
00917   for(;c;c=e->m_clients.next()) {
00918     if (c->instance==0 || c->count==0) continue;
00919 
00920     if (c->watchingStopped) {
00921       // add event to pending...
00922       if (event == Changed)
00923     c->pending |= event;
00924       else if (event == Created || event == Deleted)
00925     c->pending = event;
00926       continue;
00927     }
00928     // not stopped
00929     if (event == NoChange || event == Changed)
00930       event |= c->pending;
00931     c->pending = NoChange;
00932     if (event == NoChange) continue;
00933 
00934     if (event & Deleted) {
00935       c->instance->setDeleted(path);
00936       // emit only Deleted event...
00937       continue;
00938     }
00939 
00940     if (event & Created) {
00941       c->instance->setCreated(path);
00942       // possible emit Change event after creation
00943     }
00944 
00945     if (event & Changed)
00946       c->instance->setDirty(path);
00947   }
00948 }
00949 
00950 // Remove entries which were marked to be removed
00951 void KDirWatchPrivate::slotRemoveDelayed()
00952 {
00953   Entry* e;
00954   delayRemove = false;
00955   for(e=removeList.first();e;e=removeList.next())
00956     removeEntry(0, e->path, 0);
00957   removeList.clear();
00958 }
00959 
00960 /* Scan all entries to be watched for changes. This is done regularly
00961  * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
00962  */
00963 void KDirWatchPrivate::slotRescan()
00964 {
00965   EntryMap::Iterator it;
00966 
00967   // People can do very long things in the slot connected to dirty(),
00968   // like showing a message box. We don't want to keep polling during
00969   // that time, otherwise the value of 'delayRemove' will be reset.
00970   bool timerRunning = timer->isActive();
00971   if ( timerRunning )
00972     timer->stop();
00973 
00974   // We delay deletions of entries this way.
00975   // removeDir(), when called in slotDirty(), can cause a crash otherwise
00976   delayRemove = true;
00977 
00978 #ifdef HAVE_DNOTIFY
00979   QPtrList<Entry> dList, cList;
00980 
00981   // for DNotify method,
00982   if (rescan_all)
00983   {
00984     // mark all as dirty
00985     it = m_mapEntries.begin();
00986     for( ; it != m_mapEntries.end(); ++it )
00987       (*it).dn_dirty = true;
00988     rescan_all = false;
00989   }
00990   else
00991   {
00992     // progate dirty flag to dependant entries (e.g. file watches)
00993     it = m_mapEntries.begin();
00994     for( ; it != m_mapEntries.end(); ++it )
00995       if ( ((*it).m_mode == DNotifyMode) && (*it).dn_dirty )
00996         (*it).propagate_dirty();
00997   }
00998 
00999 #endif
01000 
01001   it = m_mapEntries.begin();
01002   for( ; it != m_mapEntries.end(); ++it ) {
01003     // we don't check invalid entries (i.e. remove delayed)
01004     if (!(*it).isValid()) continue;
01005 
01006     int ev = scanEntry( &(*it) );
01007 
01008 #ifdef HAVE_DNOTIFY
01009     if ((*it).m_mode == DNotifyMode) {
01010       if ((*it).isDir && (ev == Deleted)) {
01011     dList.append( &(*it) );
01012 
01013     // must close the FD.
01014     if ((*it).dn_fd) {
01015       ::close((*it).dn_fd);
01016       fd_Entry.remove((*it).dn_fd);
01017       (*it).dn_fd = 0;
01018     }
01019       }
01020 
01021       else if ((*it).isDir && (ev == Created)) {
01022     // For created, but yet without DNOTIFYing ...
01023     if ( (*it).dn_fd == 0) {
01024       cList.append( &(*it) );
01025       if (! useDNotify( &(*it) )) {
01026         // if DNotify setup fails...
01027         useStat( &(*it) );
01028       }
01029     }
01030       }
01031     }
01032 #endif
01033 
01034     if ( ev != NoChange )
01035       emitEvent( &(*it), ev);
01036   }
01037 
01038 
01039 #ifdef HAVE_DNOTIFY
01040   // Scan parent of deleted directories for new creation
01041   Entry* e;
01042   for(e=dList.first();e;e=dList.next())
01043     addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01044 
01045   // Remove watch of parent of new created directories
01046   for(e=cList.first();e;e=cList.next())
01047     removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
01048 #endif
01049 
01050   if ( timerRunning )
01051     timer->start(freq);
01052 
01053   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01054 }
01055 
01056 #ifdef HAVE_FAM
01057 void KDirWatchPrivate::famEventReceived()
01058 {
01059   static FAMEvent fe;
01060 
01061   delayRemove = true;
01062 
01063   while(use_fam && FAMPending(&fc)) {
01064     if (FAMNextEvent(&fc, &fe) == -1) {
01065       kdWarning(7001) << "FAM connection problem, switching to polling."
01066               << endl;
01067       use_fam = false;
01068       delete sn; sn = 0;
01069 
01070       // Replace all FAMMode entries with DNotify/Stat
01071       EntryMap::Iterator it;
01072       it = m_mapEntries.begin();
01073       for( ; it != m_mapEntries.end(); ++it )
01074     if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01075 #ifdef HAVE_DNOTIFY
01076       if (useDNotify( &(*it) )) continue;
01077 #endif
01078       useStat( &(*it) );
01079     }
01080     }
01081     else
01082       checkFAMEvent(&fe);
01083   }
01084 
01085   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01086 }
01087 
01088 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01089 {
01090   // Don't be too verbose ;-)
01091   if ((fe->code == FAMExists) ||
01092       (fe->code == FAMEndExist) ||
01093       (fe->code == FAMAcknowledge)) return;
01094 
01095   // $HOME/.X.err grows with debug output, so don't notify change
01096   if ( *(fe->filename) == '.') {
01097     if (strncmp(fe->filename, ".X.err", 6) == 0) return;
01098     if (strncmp(fe->filename, ".xsession-errors", 16) == 0) return;
01099   }
01100 
01101   Entry* e = 0;
01102   EntryMap::Iterator it = m_mapEntries.begin();
01103   for( ; it != m_mapEntries.end(); ++it )
01104     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01105        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01106       e = &(*it);
01107       break;
01108     }
01109 
01110   // Entry* e = static_cast<Entry*>(fe->userdata);
01111 
01112   kdDebug(7001) << "Processing FAM event ("
01113         << ((fe->code == FAMChanged) ? "FAMChanged" :
01114             (fe->code == FAMDeleted) ? "FAMDeleted" :
01115             (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01116             (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01117             (fe->code == FAMCreated) ? "FAMCreated" :
01118             (fe->code == FAMMoved) ? "FAMMoved" :
01119             (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01120             (fe->code == FAMExists) ? "FAMExists" :
01121             (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01122         << ", " << fe->filename
01123         << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
01124         << ")" << endl;
01125 
01126   if (!e) {
01127     // this happens e.g. for FAMAcknowledge after deleting a dir...
01128     //    kdDebug(7001) << "No entry for FAM event ?!" << endl;
01129     return;
01130   }
01131 
01132   if (e->m_status == NonExistent) {
01133     kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
01134     return;
01135   }
01136 
01137   if (e->isDir)
01138     switch (fe->code)
01139     {
01140       case FAMDeleted:
01141        // file absolute: watched dir
01142         if (fe->filename[0] == '/')
01143         {
01144           // a watched directory was deleted
01145 
01146           e->m_status = NonExistent;
01147           FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
01148           kdDebug(7001) << "Cancelled FAMReq "
01149                         << FAMREQUEST_GETREQNUM(&(e->fr))
01150                         << " for " << e->path << endl;
01151           // Scan parent for a new creation
01152           addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01153         }
01154         emitEvent(e, Deleted, QFile::decodeName(fe->filename));
01155         break;
01156 
01157       case FAMCreated: {
01158           // check for creation of a directory we have to watch
01159           Entry *sub_entry = e->m_entries.first();
01160           for(;sub_entry; sub_entry = e->m_entries.next())
01161             if (sub_entry->path == e->path + "/" + fe->filename) break;
01162           if (sub_entry && sub_entry->isDir) {
01163             QString path = e->path;
01164             removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
01165             sub_entry->m_status = Normal;
01166             if (!useFAM(sub_entry))
01167               useStat(sub_entry);
01168 
01169             emitEvent(sub_entry, Created);
01170           }
01171           else emitEvent(e, Created, QFile::decodeName(fe->filename));
01172           break;
01173         }
01174 
01175       case FAMChanged:
01176         emitEvent(e, Changed, QFile::decodeName(fe->filename));
01177 
01178       default:
01179         break;
01180     }
01181   else switch (fe->code)
01182     {
01183       case FAMCreated: emitEvent(e, Created);
01184                        break;
01185       case FAMDeleted: emitEvent(e, Deleted);
01186                        break;
01187       case FAMChanged: emitEvent(e, Changed);
01188                        break;
01189       default: break;
01190     }
01191 }
01192 #else
01193 void KDirWatchPrivate::famEventReceived() {}
01194 #endif
01195 
01196 
01197 void KDirWatchPrivate::statistics()
01198 {
01199   EntryMap::Iterator it;
01200 
01201   kdDebug(7001) << "Entries watched:" << endl;
01202   if (m_mapEntries.count()==0) {
01203     kdDebug(7001) << "  None." << endl;
01204   }
01205   else {
01206     it = m_mapEntries.begin();
01207     for( ; it != m_mapEntries.end(); ++it ) {
01208       Entry* e = &(*it);
01209       kdDebug(7001) << "  " << e->path << " ("
01210             << ((e->m_status==Normal)?"":"Nonexistent ")
01211             << (e->isDir ? "Dir":"File") << ", using "
01212             << ((e->m_mode == FAMMode) ? "FAM" :
01213             (e->m_mode == DNotifyMode) ? "DNotify" :
01214             (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01215             << ")" << endl;
01216 
01217       Client* c = e->m_clients.first();
01218       for(;c; c = e->m_clients.next()) {
01219     QString pending;
01220     if (c->watchingStopped) {
01221       if (c->pending & Deleted) pending += "deleted ";
01222       if (c->pending & Created) pending += "created ";
01223       if (c->pending & Changed) pending += "changed ";
01224       if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
01225       pending = ", stopped" + pending;
01226     }
01227     kdDebug(7001) << "    by " << c->instance->name()
01228               << " (" << c->count << " times)"
01229               << pending << endl;
01230       }
01231       if (e->m_entries.count()>0) {
01232     kdDebug(7001) << "    dependent entries:" << endl;
01233     Entry* d = e->m_entries.first();
01234     for(;d; d = e->m_entries.next()) {
01235       kdDebug(7001) << "      " << d->path << endl;
01236     }
01237       }
01238     }
01239   }
01240 }
01241 
01242 
01243 //
01244 // Class KDirWatch
01245 //
01246 
01247 static KStaticDeleter<KDirWatch> sd_dw;
01248 KDirWatch* KDirWatch::s_pSelf = 0L;
01249 
01250 KDirWatch* KDirWatch::self()
01251 {
01252   if ( !s_pSelf ) {
01253     sd_dw.setObject( s_pSelf, new KDirWatch );
01254   }
01255 
01256   return s_pSelf;
01257 }
01258 
01259 bool KDirWatch::exists()
01260 {
01261   return s_pSelf != 0;
01262 }
01263 
01264 KDirWatch::KDirWatch (QObject* parent, const char* name)
01265   : QObject(parent,name)
01266 {
01267   if (!name) {
01268     static int nameCounter = 0;
01269 
01270     nameCounter++;
01271     setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
01272   }
01273 
01274   if (!dwp_self)
01275     dwp_self = new KDirWatchPrivate;
01276   d = dwp_self;
01277   d->ref();
01278 
01279   _isStopped = false;
01280 }
01281 
01282 KDirWatch::~KDirWatch()
01283 {
01284   if (d) d->removeEntries(this);
01285   if ( d->deref() )
01286   {
01287     // delete it if it's the last one
01288     delete d;
01289     dwp_self = 0L;
01290   }
01291 }
01292 
01293 
01294 // TODO: add watchFiles/recursive support
01295 void KDirWatch::addDir( const QString& _path,
01296             bool watchFiles, bool recursive)
01297 {
01298   if (watchFiles || recursive) {
01299     kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
01300           << endl;
01301   }
01302   if (d) d->addEntry(this, _path, 0, true);
01303 }
01304 
01305 void KDirWatch::addFile( const QString& _path )
01306 {
01307   if (d) d->addEntry(this, _path, 0, false);
01308 }
01309 
01310 QDateTime KDirWatch::ctime( const QString &_path )
01311 {
01312   KDirWatchPrivate::Entry* e = d->entry(_path);
01313 
01314   if (!e)
01315     return QDateTime();
01316   else
01317     return e->m_ctime;
01318 }
01319 
01320 void KDirWatch::removeDir( const QString& _path )
01321 {
01322   if (d) d->removeEntry(this, _path, 0);
01323 }
01324 
01325 void KDirWatch::removeFile( const QString& _path )
01326 {
01327   if (d) d->removeEntry(this, _path, 0);
01328 }
01329 
01330 bool KDirWatch::stopDirScan( const QString& _path )
01331 {
01332   if (d) {
01333     KDirWatchPrivate::Entry *e = d->entry(_path);
01334     if (e && e->isDir) return d->stopEntryScan(this, e);
01335   }
01336   return false;
01337 }
01338 
01339 bool KDirWatch::restartDirScan( const QString& _path )
01340 {
01341   if (d) {
01342     KDirWatchPrivate::Entry *e = d->entry(_path);
01343     if (e && e->isDir)
01344       // restart without notifying pending events
01345       return d->restartEntryScan(this, e, false);
01346   }
01347   return false;
01348 }
01349 
01350 void KDirWatch::stopScan()
01351 {
01352   if (d) d->stopScan(this);
01353   _isStopped = true;
01354 }
01355 
01356 void KDirWatch::startScan( bool notify, bool skippedToo )
01357 {
01358   _isStopped = false;
01359   if (d) d->startScan(this, notify, skippedToo);
01360 }
01361 
01362 
01363 bool KDirWatch::contains( const QString& _path ) const
01364 {
01365   KDirWatchPrivate::Entry* e = d->entry(_path);
01366   if (!e)
01367      return false;
01368 
01369   KDirWatchPrivate::Client* c = e->m_clients.first();
01370   for(;c;c=e->m_clients.next())
01371     if (c->instance == this) return true;
01372 
01373   return false;
01374 }
01375 
01376 void KDirWatch::statistics()
01377 {
01378   if (!dwp_self) {
01379     kdDebug(7001) << "KDirWatch not used" << endl;
01380     return;
01381   }
01382   dwp_self->statistics();
01383 }
01384 
01385 
01386 void KDirWatch::setCreated( const QString & _file )
01387 {
01388   kdDebug(7001) << name() << " emitting created " << _file << endl;
01389   emit created( _file );
01390 }
01391 
01392 void KDirWatch::setDirty( const QString & _file )
01393 {
01394   kdDebug(7001) << name() << " emitting dirty " << _file << endl;
01395   emit dirty( _file );
01396 }
01397 
01398 void KDirWatch::setDeleted( const QString & _file )
01399 {
01400   kdDebug(7001) << name() << " emitting deleted " << _file << endl;
01401   emit deleted( _file );
01402 }
01403 
01404 #include "kdirwatch.moc"
01405 #include "kdirwatch_p.moc"
01406 
01407 //sven
01408 
01409 // vim: sw=2 ts=8 et
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:15:29 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001