00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
00032 
00033 
00034 #include <config.h>
00035 
00036 #ifdef HAVE_DNOTIFY
00037 #include <unistd.h>
00038 #include <time.h>
00039 #include <fcntl.h>
00040 #include <signal.h>
00041 #include <errno.h>
00042 #endif
00043 
00044 #include <sys/stat.h>
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" 
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 
00075 
00076 
00077 
00078 
00079 
00080 
00081 
00082 void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
00083 {
00084   if (!dwp_self) return;
00085 
00086   
00087   
00088   int saved_errno = errno;
00089 
00090   Entry* e = dwp_self->fd_Entry.find(si->si_fd);
00091 
00092 
00093 
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 
00107 
00108 
00109 
00110 void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
00111 {
00112   if (dwp_self)
00113   {
00114     
00115     
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   
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 
00143 
00144 
00145 
00146 
00147 
00148 
00149 
00150 
00151 
00152 
00153 
00154 
00155 
00156 
00157 
00158 
00159 
00160 
00161 
00162 
00163 
00164 
00165 
00166 
00167 
00168 
00169 KDirWatchPrivate::KDirWatchPrivate()
00170 {
00171   timer = new QTimer(this);
00172   connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00173   freq = 3600000; 
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   
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; 
00202   rescan_all = false;
00203   struct utsname uts;
00204   int major, minor, patch;
00205   if (uname(&uts) < 0)
00206     supports_dnotify = false; 
00207   else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00208     supports_dnotify = false; 
00209   else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { 
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     
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 
00252 KDirWatchPrivate::~KDirWatchPrivate()
00253 {
00254   timer->stop();
00255 
00256   
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 
00282 
00283 
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 
00300 void KDirWatchPrivate::slotActivated() {}
00301 #endif
00302 
00303 
00304 
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 
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 
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 
00373 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00374 {
00375   e->freq = newFreq;
00376 
00377   
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 
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       
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       
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   
00433   
00434   famEventReceived();
00435 
00436   return true;
00437 }
00438 #endif
00439 
00440 
00441 #ifdef HAVE_DNOTIFY
00442 
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       
00455       
00456       
00457       
00458       
00459       
00460       
00461       
00462       
00463       
00464       
00465       
00466       int fd2 = fcntl(fd, F_DUPFD, 128);
00467       if (fd2 >= 0)
00468       {
00469         close(fd);
00470         fd = fd2;
00471       }
00472       if (fd<0) {
00473     e->m_mode = UnknownMode;
00474     return false;
00475       }
00476 
00477       int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00478       
00479       for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00480     if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00481 
00482       if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
00483      fcntl(fd, F_NOTIFY, mask) < 0) {
00484 
00485     kdDebug(7001) << "Not using Linux Directory Notifications."
00486               << endl;
00487     supports_dnotify = false;
00488     ::close(fd);
00489     e->m_mode = UnknownMode;
00490     return false;
00491       }
00492 
00493       fd_Entry.replace(fd, e);
00494       e->dn_fd = fd;
00495 
00496       kdDebug(7001) << " Setup DNotify (fd " << fd
00497             << ") for " << e->path << endl;
00498     }
00499     else { 
00500       addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
00501     }
00502   }
00503   else { 
00504     
00505     
00506     addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
00507   }
00508 
00509   return true;
00510 }
00511 #endif
00512 
00513 
00514 bool KDirWatchPrivate::useStat(Entry* e)
00515 {
00516   if (KIO::probably_slow_mounted(e->path))
00517     useFreq(e, m_nfsPollInterval);
00518   else
00519     useFreq(e, m_PollInterval);
00520 
00521   if (e->m_mode != StatMode) {
00522     e->m_mode = StatMode;
00523     statEntries++;
00524 
00525     if ( statEntries == 1 ) {
00526       
00527       timer->start(freq);      
00528       kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
00529     }
00530   }
00531 
00532   kdDebug(7001) << " Setup Stat (freq " << e->freq
00533         << ") for " << e->path << endl;
00534 
00535   return true;
00536 }
00537 
00538 
00539 
00540 
00541 
00542 
00543 
00544 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00545                 Entry* sub_entry, bool isDir)
00546 {
00547   QString path = _path;
00548   if (path.startsWith("/dev/") || (path == "/dev"))
00549     return; 
00550 
00551   if ( path.length() > 1 && path.right(1) == "/" )
00552     path.truncate( path.length() - 1 );
00553 
00554   EntryMap::Iterator it = m_mapEntries.find( path );
00555   if ( it != m_mapEntries.end() )
00556   {
00557     if (sub_entry) {
00558        (*it).m_entries.append(sub_entry);
00559        kdDebug(7001) << "Added already watched Entry " << path
00560              << " (for " << sub_entry->path << ")" << endl;
00561 #ifdef HAVE_DNOTIFY
00562        Entry* e = &(*it);
00563        if( e->dn_fd > 0 ) {
00564          int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00565          
00566          for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00567            if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00568      if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { 
00569        ::close(e->dn_fd);
00570        e->m_mode = UnknownMode;
00571        fd_Entry.remove(e->dn_fd);
00572            e->dn_fd = 0;
00573            useStat( e );
00574          }
00575        }
00576 #endif
00577     }
00578     else {
00579        (*it).addClient(instance);
00580        kdDebug(7001) << "Added already watched Entry " << path
00581              << " (now " <<  (*it).clients() << " clients)"
00582              << QString(" [%1]").arg(instance->name()) << endl;
00583     }
00584     return;
00585   }
00586 
00587   
00588 
00589   struct stat stat_buf;
00590   bool exists = (stat(QFile::encodeName(path), &stat_buf) == 0);
00591 
00592   Entry newEntry;
00593   m_mapEntries.insert( path, newEntry );
00594   
00595   Entry* e = &(m_mapEntries[path]);
00596 
00597   if (exists) {
00598     e->isDir = S_ISDIR(stat_buf.st_mode);
00599 
00600     if (e->isDir && !isDir)
00601       qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
00602     else if (!e->isDir && isDir)
00603       qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
00604 
00605     e->m_ctime = stat_buf.st_ctime;
00606     e->m_status = Normal;
00607     e->m_nlink = stat_buf.st_nlink;
00608   }
00609   else {
00610     e->isDir = isDir;
00611     e->m_ctime = invalid_ctime;
00612     e->m_status = NonExistent;
00613     e->m_nlink = 0;
00614   }
00615 
00616   e->path = path;
00617   if (sub_entry)
00618      e->m_entries.append(sub_entry);
00619   else
00620     e->addClient(instance);
00621 
00622   kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
00623         << (e->m_status == NonExistent ? " NotExisting" : "")
00624         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00625         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00626         << endl;
00627 
00628 
00629   
00630   e->m_mode = UnknownMode;
00631   e->msecLeft = 0;
00632 
00633 #if defined(HAVE_FAM)
00634   if (useFAM(e)) return;
00635 #endif
00636 
00637 #ifdef HAVE_DNOTIFY
00638   if (useDNotify(e)) return;
00639 #endif
00640 
00641   useStat(e);
00642 }
00643 
00644 
00645 void KDirWatchPrivate::removeEntry( KDirWatch* instance,
00646                     const QString& _path, Entry* sub_entry )
00647 {
00648   Entry* e = entry(_path);
00649   if (!e) {
00650     kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
00651     return;
00652   }
00653 
00654   if (sub_entry)
00655     e->m_entries.removeRef(sub_entry);
00656   else
00657     e->removeClient(instance);
00658 
00659   if (e->m_clients.count() || e->m_entries.count())
00660     return;
00661 
00662   if (delayRemove) {
00663     
00664     if (removeList.findRef(e)==-1)
00665     removeList.append(e);
00666     
00667     return;
00668   }
00669 
00670 #ifdef HAVE_FAM
00671   if (e->m_mode == FAMMode) {
00672     if ( e->m_status == Normal) {
00673       FAMCancelMonitor(&fc, &(e->fr) );
00674       kdDebug(7001) << "Cancelled FAM (Req "
00675             << FAMREQUEST_GETREQNUM(&(e->fr))
00676             << ") for " << e->path << endl;
00677     }
00678     else {
00679       if (e->isDir)
00680     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00681       else
00682     removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00683     }
00684   }
00685 #endif
00686 
00687 #ifdef HAVE_DNOTIFY
00688   if (e->m_mode == DNotifyMode) {
00689     if (!e->isDir) {
00690       removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00691     }
00692     else { 
00693       
00694       if ( e->m_status == Normal) {
00695     if (e->dn_fd) {
00696       ::close(e->dn_fd);
00697       fd_Entry.remove(e->dn_fd);
00698 
00699       kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
00700             << ") for " << e->path << endl;
00701       e->dn_fd = 0;
00702 
00703     }
00704       }
00705       else {
00706     removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00707       }
00708     }
00709   }
00710 #endif
00711 
00712   if (e->m_mode == StatMode) {
00713     statEntries--;
00714     if ( statEntries == 0 ) {
00715       timer->stop(); 
00716       kdDebug(7001) << " Stopped Polling Timer" << endl;
00717     }
00718   }
00719 
00720   kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00721         << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00722         << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00723         << endl;
00724   m_mapEntries.remove( e->path ); 
00725 }
00726 
00727 
00728 
00729 
00730 
00731 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
00732 {
00733   QPtrList<Entry> list;
00734   int minfreq = 3600000;
00735 
00736   
00737   EntryMap::Iterator it = m_mapEntries.begin();
00738   for( ; it != m_mapEntries.end(); ++it ) {
00739     Client* c = (*it).m_clients.first();
00740     for(;c;c=(*it).m_clients.next())
00741       if (c->instance == instance) break;
00742     if (c) {
00743       c->count = 1; 
00744       list.append(&(*it));
00745     }
00746     else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
00747       minfreq = (*it).freq;
00748   }
00749 
00750   for(Entry* e=list.first();e;e=list.next())
00751     removeEntry(instance, e->path, 0);
00752 
00753   if (minfreq > freq) {
00754     
00755     freq = minfreq;
00756     if (timer->isActive()) timer->changeInterval(freq);
00757     kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
00758   }
00759 }
00760 
00761 
00762 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
00763 {
00764   int stillWatching = 0;
00765   Client* c = e->m_clients.first();
00766   for(;c;c=e->m_clients.next()) {
00767     if (!instance || instance == c->instance)
00768       c->watchingStopped = true;
00769     else if (!c->watchingStopped)
00770       stillWatching += c->count;
00771   }
00772 
00773   kdDebug(7001) << instance->name() << " stopped scanning " << e->path
00774         << " (now " << stillWatching << " watchers)" << endl;
00775 
00776   if (stillWatching == 0) {
00777     
00778     e->m_ctime = invalid_ctime; 
00779     
00780   }
00781   return true;
00782 }
00783 
00784 
00785 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
00786                      bool notify)
00787 {
00788   int wasWatching = 0, newWatching = 0;
00789   Client* c = e->m_clients.first();
00790   for(;c;c=e->m_clients.next()) {
00791     if (!c->watchingStopped)
00792       wasWatching += c->count;
00793     else if (!instance || instance == c->instance) {
00794       c->watchingStopped = false;
00795       newWatching += c->count;
00796     }
00797   }
00798   if (newWatching == 0)
00799     return false;
00800 
00801   kdDebug(7001) << instance->name() << " restarted scanning " << e->path
00802         << " (now " << wasWatching+newWatching << " watchers)" << endl;
00803 
00804   
00805 
00806   int ev = NoChange;
00807   if (wasWatching == 0) {
00808     if (!notify) {
00809       struct stat stat_buf;
00810       bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
00811       if (exists) {
00812     e->m_ctime = stat_buf.st_ctime;
00813     e->m_status = Normal;
00814         e->m_nlink = stat_buf.st_nlink;
00815       }
00816       else {
00817     e->m_ctime = invalid_ctime;
00818     e->m_status = NonExistent;
00819         e->m_nlink = 0;
00820       }
00821     }
00822     e->msecLeft = 0;
00823     ev = scanEntry(e);
00824   }
00825   emitEvent(e,ev);
00826 
00827   return true;
00828 }
00829 
00830 
00831 void KDirWatchPrivate::stopScan(KDirWatch* instance)
00832 {
00833   EntryMap::Iterator it = m_mapEntries.begin();
00834   for( ; it != m_mapEntries.end(); ++it )
00835     stopEntryScan(instance, &(*it));
00836 }
00837 
00838 
00839 void KDirWatchPrivate::startScan(KDirWatch* instance,
00840                  bool notify, bool skippedToo )
00841 {
00842   if (!notify)
00843     resetList(instance,skippedToo);
00844 
00845   EntryMap::Iterator it = m_mapEntries.begin();
00846   for( ; it != m_mapEntries.end(); ++it )
00847     restartEntryScan(instance, &(*it), notify);
00848 
00849   
00850 }
00851 
00852 
00853 
00854 void KDirWatchPrivate::resetList( KDirWatch* ,
00855                   bool skippedToo )
00856 {
00857   EntryMap::Iterator it = m_mapEntries.begin();
00858   for( ; it != m_mapEntries.end(); ++it ) {
00859 
00860     Client* c = (*it).m_clients.first();
00861     for(;c;c=(*it).m_clients.next())
00862       if (!c->watchingStopped || skippedToo)
00863     c->pending = NoChange;
00864   }
00865 }
00866 
00867 
00868 
00869 int KDirWatchPrivate::scanEntry(Entry* e)
00870 {
00871 #ifdef HAVE_FAM
00872   
00873   if (e->m_mode == FAMMode) return NoChange;
00874 #endif
00875 
00876   
00877   if (e->m_mode == UnknownMode) return NoChange;
00878 
00879 #ifdef HAVE_DNOTIFY
00880   if (e->m_mode == DNotifyMode) {
00881     
00882     if(!e->dn_dirty) return NoChange;
00883     e->dn_dirty = false;
00884   }
00885 #endif
00886 
00887   if (e->m_mode == StatMode) {
00888     
00889     
00890     
00891 
00892     e->msecLeft -= freq;
00893     if (e->msecLeft>0) return NoChange;
00894     e->msecLeft += e->freq;
00895   }
00896 
00897   struct stat stat_buf;
00898   bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
00899   if (exists) {
00900 
00901     if (e->m_status == NonExistent) {
00902       e->m_ctime = stat_buf.st_ctime;
00903       e->m_status = Normal;
00904       e->m_nlink = stat_buf.st_nlink;
00905       return Created;
00906     }
00907 
00908     if ( (e->m_ctime != invalid_ctime) && 
00909      ((stat_buf.st_ctime != e->m_ctime) || 
00910       (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
00911       e->m_ctime = stat_buf.st_ctime;
00912       e->m_nlink = stat_buf.st_nlink;
00913       return Changed;
00914     }
00915 
00916     return NoChange;
00917   }
00918 
00919   
00920 
00921   if (e->m_ctime == invalid_ctime)
00922     return NoChange;
00923 
00924   e->m_ctime = invalid_ctime;
00925   e->m_nlink = 0;
00926   e->m_status = NonExistent;
00927 
00928   return Deleted;
00929 }
00930 
00931 
00932 
00933 
00934 
00935 void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
00936 {
00937   QString path = e->path;
00938   if (!fileName.isEmpty()) {
00939     if (fileName[0] == '/')
00940       path = fileName;
00941     else
00942       path += "/" + fileName;
00943   }
00944 
00945   Client* c = e->m_clients.first();
00946   for(;c;c=e->m_clients.next()) {
00947     if (c->instance==0 || c->count==0) continue;
00948 
00949     if (c->watchingStopped) {
00950       
00951       if (event == Changed)
00952     c->pending |= event;
00953       else if (event == Created || event == Deleted)
00954     c->pending = event;
00955       continue;
00956     }
00957     
00958     if (event == NoChange || event == Changed)
00959       event |= c->pending;
00960     c->pending = NoChange;
00961     if (event == NoChange) continue;
00962 
00963     if (event & Deleted) {
00964       c->instance->setDeleted(path);
00965       
00966       continue;
00967     }
00968 
00969     if (event & Created) {
00970       c->instance->setCreated(path);
00971       
00972     }
00973 
00974     if (event & Changed)
00975       c->instance->setDirty(path);
00976   }
00977 }
00978 
00979 
00980 void KDirWatchPrivate::slotRemoveDelayed()
00981 {
00982   Entry* e;
00983   delayRemove = false;
00984   for(e=removeList.first();e;e=removeList.next())
00985     removeEntry(0, e->path, 0);
00986   removeList.clear();
00987 }
00988 
00989 
00990 
00991 
00992 void KDirWatchPrivate::slotRescan()
00993 {
00994   EntryMap::Iterator it;
00995 
00996   
00997   
00998   
00999   bool timerRunning = timer->isActive();
01000   if ( timerRunning )
01001     timer->stop();
01002 
01003   
01004   
01005   delayRemove = true;
01006 
01007 #ifdef HAVE_DNOTIFY
01008   QPtrList<Entry> dList, cList;
01009 
01010   
01011   if (rescan_all)
01012   {
01013     
01014     it = m_mapEntries.begin();
01015     for( ; it != m_mapEntries.end(); ++it )
01016       (*it).dn_dirty = true;
01017     rescan_all = false;
01018   }
01019   else
01020   {
01021     
01022     it = m_mapEntries.begin();
01023     for( ; it != m_mapEntries.end(); ++it )
01024       if ( ((*it).m_mode == DNotifyMode) && (*it).dn_dirty )
01025         (*it).propagate_dirty();
01026   }
01027 
01028 #endif
01029 
01030   it = m_mapEntries.begin();
01031   for( ; it != m_mapEntries.end(); ++it ) {
01032     
01033     if (!(*it).isValid()) continue;
01034 
01035     int ev = scanEntry( &(*it) );
01036 
01037 #ifdef HAVE_DNOTIFY
01038     if ((*it).m_mode == DNotifyMode) {
01039       if ((*it).isDir && (ev == Deleted)) {
01040     dList.append( &(*it) );
01041 
01042     
01043     if ((*it).dn_fd) {
01044       ::close((*it).dn_fd);
01045       fd_Entry.remove((*it).dn_fd);
01046       (*it).dn_fd = 0;
01047     }
01048       }
01049 
01050       else if ((*it).isDir && (ev == Created)) {
01051     
01052     if ( (*it).dn_fd == 0) {
01053       cList.append( &(*it) );
01054       if (! useDNotify( &(*it) )) {
01055         
01056         useStat( &(*it) );
01057       }
01058     }
01059       }
01060     }
01061 #endif
01062 
01063     if ( ev != NoChange )
01064       emitEvent( &(*it), ev);
01065   }
01066 
01067 
01068 #ifdef HAVE_DNOTIFY
01069   
01070   Entry* e;
01071   for(e=dList.first();e;e=dList.next())
01072     addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01073 
01074   
01075   for(e=cList.first();e;e=cList.next())
01076     removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
01077 #endif
01078 
01079   if ( timerRunning )
01080     timer->start(freq);
01081 
01082   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01083 }
01084 
01085 #ifdef HAVE_FAM
01086 void KDirWatchPrivate::famEventReceived()
01087 {
01088   static FAMEvent fe;
01089 
01090   delayRemove = true;
01091 
01092   while(use_fam && FAMPending(&fc)) {
01093     if (FAMNextEvent(&fc, &fe) == -1) {
01094       kdWarning(7001) << "FAM connection problem, switching to polling."
01095               << endl;
01096       use_fam = false;
01097       delete sn; sn = 0;
01098 
01099       
01100       EntryMap::Iterator it;
01101       it = m_mapEntries.begin();
01102       for( ; it != m_mapEntries.end(); ++it )
01103     if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01104 #ifdef HAVE_DNOTIFY
01105       if (useDNotify( &(*it) )) continue;
01106 #endif
01107       useStat( &(*it) );
01108     }
01109     }
01110     else
01111       checkFAMEvent(&fe);
01112   }
01113 
01114   QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01115 }
01116 
01117 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01118 {
01119   
01120   if ((fe->code == FAMExists) ||
01121       (fe->code == FAMEndExist) ||
01122       (fe->code == FAMAcknowledge)) return;
01123 
01124   
01125   if ( *(fe->filename) == '.') {
01126     if (strncmp(fe->filename, ".X.err", 6) == 0) return;
01127     if (strncmp(fe->filename, ".xsession-errors", 16) == 0) return;
01128     
01129     
01130     if (strncmp(fe->filename, ".fonts.cache", 12) == 0) return;
01131   }
01132 
01133   Entry* e = 0;
01134   EntryMap::Iterator it = m_mapEntries.begin();
01135   for( ; it != m_mapEntries.end(); ++it )
01136     if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01137        FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01138       e = &(*it);
01139       break;
01140     }
01141 
01142   
01143 
01144   kdDebug(7001) << "Processing FAM event ("
01145         << ((fe->code == FAMChanged) ? "FAMChanged" :
01146             (fe->code == FAMDeleted) ? "FAMDeleted" :
01147             (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01148             (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01149             (fe->code == FAMCreated) ? "FAMCreated" :
01150             (fe->code == FAMMoved) ? "FAMMoved" :
01151             (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01152             (fe->code == FAMExists) ? "FAMExists" :
01153             (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01154         << ", " << fe->filename
01155         << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
01156         << ")" << endl;
01157 
01158   if (!e) {
01159     
01160     
01161     return;
01162   }
01163 
01164   if (e->m_status == NonExistent) {
01165     kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
01166     return;
01167   }
01168 
01169   if (e->isDir)
01170     switch (fe->code)
01171     {
01172       case FAMDeleted:
01173        
01174         if (fe->filename[0] == '/')
01175         {
01176           
01177 
01178           e->m_status = NonExistent;
01179           FAMCancelMonitor(&fc, &(e->fr) ); 
01180           kdDebug(7001) << "Cancelled FAMReq "
01181                         << FAMREQUEST_GETREQNUM(&(e->fr))
01182                         << " for " << e->path << endl;
01183           
01184           addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01185         }
01186         emitEvent(e, Deleted, QFile::decodeName(fe->filename));
01187         break;
01188 
01189       case FAMCreated: {
01190           
01191           Entry *sub_entry = e->m_entries.first();
01192           for(;sub_entry; sub_entry = e->m_entries.next())
01193             if (sub_entry->path == e->path + "/" + fe->filename) break;
01194           if (sub_entry && sub_entry->isDir) {
01195             QString path = e->path;
01196             removeEntry(0,e->path,sub_entry); 
01197             sub_entry->m_status = Normal;
01198             if (!useFAM(sub_entry))
01199               useStat(sub_entry);
01200 
01201             emitEvent(sub_entry, Created);
01202           }
01203           else emitEvent(e, Created, QFile::decodeName(fe->filename));
01204           break;
01205         }
01206 
01207       case FAMChanged:
01208         emitEvent(e, Changed, QFile::decodeName(fe->filename));
01209 
01210       default:
01211         break;
01212     }
01213   else switch (fe->code)
01214     {
01215       case FAMCreated: emitEvent(e, Created);
01216                        break;
01217       case FAMDeleted: emitEvent(e, Deleted);
01218                        break;
01219       case FAMChanged: emitEvent(e, Changed);
01220                        break;
01221       default: break;
01222     }
01223 }
01224 #else
01225 void KDirWatchPrivate::famEventReceived() {}
01226 #endif
01227 
01228 
01229 void KDirWatchPrivate::statistics()
01230 {
01231   EntryMap::Iterator it;
01232 
01233   kdDebug(7001) << "Entries watched:" << endl;
01234   if (m_mapEntries.count()==0) {
01235     kdDebug(7001) << "  None." << endl;
01236   }
01237   else {
01238     it = m_mapEntries.begin();
01239     for( ; it != m_mapEntries.end(); ++it ) {
01240       Entry* e = &(*it);
01241       kdDebug(7001) << "  " << e->path << " ("
01242             << ((e->m_status==Normal)?"":"Nonexistent ")
01243             << (e->isDir ? "Dir":"File") << ", using "
01244             << ((e->m_mode == FAMMode) ? "FAM" :
01245             (e->m_mode == DNotifyMode) ? "DNotify" :
01246             (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01247             << ")" << endl;
01248 
01249       Client* c = e->m_clients.first();
01250       for(;c; c = e->m_clients.next()) {
01251     QString pending;
01252     if (c->watchingStopped) {
01253       if (c->pending & Deleted) pending += "deleted ";
01254       if (c->pending & Created) pending += "created ";
01255       if (c->pending & Changed) pending += "changed ";
01256       if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
01257       pending = ", stopped" + pending;
01258     }
01259     kdDebug(7001) << "    by " << c->instance->name()
01260               << " (" << c->count << " times)"
01261               << pending << endl;
01262       }
01263       if (e->m_entries.count()>0) {
01264     kdDebug(7001) << "    dependent entries:" << endl;
01265     Entry* d = e->m_entries.first();
01266     for(;d; d = e->m_entries.next()) {
01267       kdDebug(7001) << "      " << d->path << endl;
01268     }
01269       }
01270     }
01271   }
01272 }
01273 
01274 
01275 
01276 
01277 
01278 
01279 static KStaticDeleter<KDirWatch> sd_dw;
01280 KDirWatch* KDirWatch::s_pSelf = 0L;
01281 
01282 KDirWatch* KDirWatch::self()
01283 {
01284   if ( !s_pSelf ) {
01285     sd_dw.setObject( s_pSelf, new KDirWatch );
01286   }
01287 
01288   return s_pSelf;
01289 }
01290 
01291 bool KDirWatch::exists()
01292 {
01293   return s_pSelf != 0;
01294 }
01295 
01296 KDirWatch::KDirWatch (QObject* parent, const char* name)
01297   : QObject(parent,name)
01298 {
01299   if (!name) {
01300     static int nameCounter = 0;
01301 
01302     nameCounter++;
01303     setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
01304   }
01305 
01306   if (!dwp_self)
01307     dwp_self = new KDirWatchPrivate;
01308   d = dwp_self;
01309   d->ref();
01310 
01311   _isStopped = false;
01312 }
01313 
01314 KDirWatch::~KDirWatch()
01315 {
01316   if (d) d->removeEntries(this);
01317   if ( d->deref() )
01318   {
01319     
01320     delete d;
01321     dwp_self = 0L;
01322   }
01323 }
01324 
01325 
01326 
01327 void KDirWatch::addDir( const QString& _path,
01328             bool watchFiles, bool recursive)
01329 {
01330   if (watchFiles || recursive) {
01331     kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
01332           << endl;
01333   }
01334   if (d) d->addEntry(this, _path, 0, true);
01335 }
01336 
01337 void KDirWatch::addFile( const QString& _path )
01338 {
01339   if (d) d->addEntry(this, _path, 0, false);
01340 }
01341 
01342 QDateTime KDirWatch::ctime( const QString &_path )
01343 {
01344   KDirWatchPrivate::Entry* e = d->entry(_path);
01345 
01346   if (!e)
01347     return QDateTime();
01348 
01349   QDateTime result;
01350   result.setTime_t(e->m_ctime);
01351   return result;
01352 }
01353 
01354 void KDirWatch::removeDir( const QString& _path )
01355 {
01356   if (d) d->removeEntry(this, _path, 0);
01357 }
01358 
01359 void KDirWatch::removeFile( const QString& _path )
01360 {
01361   if (d) d->removeEntry(this, _path, 0);
01362 }
01363 
01364 bool KDirWatch::stopDirScan( const QString& _path )
01365 {
01366   if (d) {
01367     KDirWatchPrivate::Entry *e = d->entry(_path);
01368     if (e && e->isDir) return d->stopEntryScan(this, e);
01369   }
01370   return false;
01371 }
01372 
01373 bool KDirWatch::restartDirScan( const QString& _path )
01374 {
01375   if (d) {
01376     KDirWatchPrivate::Entry *e = d->entry(_path);
01377     if (e && e->isDir)
01378       
01379       return d->restartEntryScan(this, e, false);
01380   }
01381   return false;
01382 }
01383 
01384 void KDirWatch::stopScan()
01385 {
01386   if (d) d->stopScan(this);
01387   _isStopped = true;
01388 }
01389 
01390 void KDirWatch::startScan( bool notify, bool skippedToo )
01391 {
01392   _isStopped = false;
01393   if (d) d->startScan(this, notify, skippedToo);
01394 }
01395 
01396 
01397 bool KDirWatch::contains( const QString& _path ) const
01398 {
01399   KDirWatchPrivate::Entry* e = d->entry(_path);
01400   if (!e)
01401      return false;
01402 
01403   KDirWatchPrivate::Client* c = e->m_clients.first();
01404   for(;c;c=e->m_clients.next())
01405     if (c->instance == this) return true;
01406 
01407   return false;
01408 }
01409 
01410 void KDirWatch::statistics()
01411 {
01412   if (!dwp_self) {
01413     kdDebug(7001) << "KDirWatch not used" << endl;
01414     return;
01415   }
01416   dwp_self->statistics();
01417 }
01418 
01419 
01420 void KDirWatch::setCreated( const QString & _file )
01421 {
01422   kdDebug(7001) << name() << " emitting created " << _file << endl;
01423   emit created( _file );
01424 }
01425 
01426 void KDirWatch::setDirty( const QString & _file )
01427 {
01428   kdDebug(7001) << name() << " emitting dirty " << _file << endl;
01429   emit dirty( _file );
01430 }
01431 
01432 void KDirWatch::setDeleted( const QString & _file )
01433 {
01434   kdDebug(7001) << name() << " emitting deleted " << _file << endl;
01435   emit deleted( _file );
01436 }
01437 
01438 KDirWatch::Method KDirWatch::internalMethod()
01439 {
01440 #ifdef HAVE_FAM
01441   if (d->use_fam)
01442      return KDirWatch::FAM;
01443 #endif
01444 #ifdef HAVE_DNOTIFY
01445   if (d->supports_dnotify)
01446      return KDirWatch::DNotify;
01447 #endif      
01448   return KDirWatch::Stat;
01449 }
01450 
01451 
01452 #include "kdirwatch.moc"
01453 #include "kdirwatch_p.moc"
01454 
01455 
01456 
01457