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->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
00185 rescan_all = false;
00186 connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
00187
00188 #ifdef HAVE_FAM
00189
00190 if (FAMOpen(&fc) ==0) {
00191 available += ", FAM";
00192 use_fam=true;
00193 sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
00194 QSocketNotifier::Read, this);
00195 connect( sn, SIGNAL(activated(int)),
00196 this, SLOT(famEventReceived()) );
00197 }
00198 else {
00199 kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
00200 use_fam=false;
00201 }
00202 #endif
00203
00204 #ifdef HAVE_DNOTIFY
00205 supports_dnotify = true;
00206 struct utsname uts;
00207 int major, minor, patch;
00208 if (uname(&uts) < 0)
00209 supports_dnotify = false;
00210 else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
00211 supports_dnotify = false;
00212 else if( major * 1000000 + minor * 1000 + patch < 2004019 ) {
00213 kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
00214 supports_dnotify = false;
00215 }
00216
00217 if( supports_dnotify ) {
00218 available += ", DNotify";
00219
00220 pipe(mPipe);
00221 fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
00222 fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
00223 mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
00224 connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
00225
00226 if ( dnotify_signal == 0 )
00227 {
00228 dnotify_signal = SIGRTMIN + 8;
00229
00230 struct sigaction act;
00231 act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
00232 sigemptyset(&act.sa_mask);
00233 act.sa_flags = SA_SIGINFO;
00234 #ifdef SA_RESTART
00235 act.sa_flags |= SA_RESTART;
00236 #endif
00237 sigaction(dnotify_signal, &act, NULL);
00238
00239 act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
00240 sigaction(SIGIO, &act, &old_sigio_act);
00241 }
00242 }
00243 else
00244 {
00245 mPipe[0] = -1;
00246 mPipe[1] = -1;
00247 }
00248 #endif
00249
00250 kdDebug(7001) << "Available methods: " << available << endl;
00251 }
00252
00253
00254 KDirWatchPrivate::~KDirWatchPrivate()
00255 {
00256 timer->stop();
00257
00258
00259 removeEntries(0);
00260
00261 #ifdef HAVE_FAM
00262 if (use_fam) {
00263 FAMClose(&fc);
00264 kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
00265 }
00266 #endif
00267 #ifdef HAVE_DNOTIFY
00268 close(mPipe[0]);
00269 close(mPipe[1]);
00270 #endif
00271 }
00272
00273 void KDirWatchPrivate::slotActivated()
00274 {
00275 #ifdef HAVE_DNOTIFY
00276 char dummy_buf[100];
00277 read(mPipe[0], &dummy_buf, 100);
00278
00279 if (!rescan_timer.isActive())
00280 rescan_timer.start(m_PollInterval, true);
00281 #endif
00282 }
00283
00284
00285
00286
00287
00288 void KDirWatchPrivate::Entry::propagate_dirty()
00289 {
00290 Entry* sub_entry;
00291 for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next())
00292 {
00293 if (!sub_entry->dirty)
00294 {
00295 sub_entry->dirty = true;
00296 sub_entry->propagate_dirty();
00297 }
00298 }
00299 }
00300
00301
00302
00303
00304
00305 void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
00306 {
00307 Client* client = m_clients.first();
00308 for(;client; client = m_clients.next())
00309 if (client->instance == instance) break;
00310
00311 if (client) {
00312 client->count++;
00313 return;
00314 }
00315
00316 client = new Client;
00317 client->instance = instance;
00318 client->count = 1;
00319 client->watchingStopped = instance->isStopped();
00320 client->pending = NoChange;
00321
00322 m_clients.append(client);
00323 }
00324
00325 void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
00326 {
00327 Client* client = m_clients.first();
00328 for(;client; client = m_clients.next())
00329 if (client->instance == instance) break;
00330
00331 if (client) {
00332 client->count--;
00333 if (client->count == 0) {
00334 m_clients.removeRef(client);
00335 delete client;
00336 }
00337 }
00338 }
00339
00340
00341 int KDirWatchPrivate::Entry::clients()
00342 {
00343 int clients = 0;
00344 Client* client = m_clients.first();
00345 for(;client; client = m_clients.next())
00346 clients += client->count;
00347
00348 return clients;
00349 }
00350
00351
00352 KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
00353 {
00354
00355 if (_path.left(1) != "/") {
00356 return 0;
00357 }
00358
00359 QString path = _path;
00360
00361 if ( path.length() > 1 && path.right(1) == "/" )
00362 path.truncate( path.length() - 1 );
00363
00364 EntryMap::Iterator it = m_mapEntries.find( path );
00365 if ( it == m_mapEntries.end() )
00366 return 0;
00367 else
00368 return &(*it);
00369 }
00370
00371
00372 void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
00373 {
00374 e->freq = newFreq;
00375
00376
00377 if (e->freq < freq) {
00378 freq = e->freq;
00379 if (timer->isActive()) timer->changeInterval(freq);
00380 kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
00381 }
00382 }
00383
00384
00385 #if defined(HAVE_FAM)
00386
00387 bool KDirWatchPrivate::useFAM(Entry* e)
00388 {
00389 if (!use_fam) return false;
00390
00391 e->m_mode = FAMMode;
00392 e->dirty = false;
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->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 ( e->path.startsWith("/mnt/") || (e->path == "/mnt")
00517 || (KIO::probably_slow_mounted(e->path)) )
00518 useFreq(e, m_nfsPollInterval);
00519 else
00520 useFreq(e, m_PollInterval);
00521
00522 if (e->m_mode != StatMode) {
00523 e->m_mode = StatMode;
00524 statEntries++;
00525
00526 if ( statEntries == 1 ) {
00527
00528 timer->start(freq);
00529 kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
00530 }
00531 }
00532
00533 kdDebug(7001) << " Setup Stat (freq " << e->freq
00534 << ") for " << e->path << endl;
00535
00536 return true;
00537 }
00538
00539
00540
00541
00542
00543
00544
00545 void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
00546 Entry* sub_entry, bool isDir)
00547 {
00548 QString path = _path;
00549 if (path.startsWith("/dev/") || (path == "/dev"))
00550 return;
00551
00552 if ( path.length() > 1 && path.right(1) == "/" )
00553 path.truncate( path.length() - 1 );
00554
00555 EntryMap::Iterator it = m_mapEntries.find( path );
00556 if ( it != m_mapEntries.end() )
00557 {
00558 if (sub_entry) {
00559 (*it).m_entries.append(sub_entry);
00560 kdDebug(7001) << "Added already watched Entry " << path
00561 << " (for " << sub_entry->path << ")" << endl;
00562 #ifdef HAVE_DNOTIFY
00563 Entry* e = &(*it);
00564 if( e->dn_fd > 0 ) {
00565 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
00566
00567 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
00568 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
00569 if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) {
00570 ::close(e->dn_fd);
00571 e->m_mode = UnknownMode;
00572 fd_Entry.remove(e->dn_fd);
00573 e->dn_fd = 0;
00574 useStat( e );
00575 }
00576 }
00577 #endif
00578 }
00579 else {
00580 (*it).addClient(instance);
00581 kdDebug(7001) << "Added already watched Entry " << path
00582 << " (now " << (*it).clients() << " clients)"
00583 << QString(" [%1]").arg(instance->name()) << endl;
00584 }
00585 return;
00586 }
00587
00588
00589
00590 struct stat stat_buf;
00591 QCString tpath = QFile::encodeName(path);
00592 bool exists = (stat(tpath, &stat_buf) == 0);
00593
00594 Entry newEntry;
00595 m_mapEntries.insert( path, newEntry );
00596
00597 Entry* e = &(m_mapEntries[path]);
00598
00599 if (exists) {
00600 e->isDir = S_ISDIR(stat_buf.st_mode);
00601
00602 if (e->isDir && !isDir)
00603 qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
00604 else if (!e->isDir && isDir)
00605 qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
00606
00607 e->m_ctime = stat_buf.st_ctime;
00608 e->m_status = Normal;
00609 e->m_nlink = stat_buf.st_nlink;
00610 }
00611 else {
00612 e->isDir = isDir;
00613 e->m_ctime = invalid_ctime;
00614 e->m_status = NonExistent;
00615 e->m_nlink = 0;
00616 }
00617
00618 e->path = path;
00619 if (sub_entry)
00620 e->m_entries.append(sub_entry);
00621 else
00622 e->addClient(instance);
00623
00624 kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
00625 << (e->m_status == NonExistent ? " NotExisting" : "")
00626 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00627 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00628 << endl;
00629
00630
00631
00632 e->m_mode = UnknownMode;
00633 e->msecLeft = 0;
00634
00635 if ( isNoisyFile( tpath ) )
00636 return;
00637
00638 #if defined(HAVE_FAM)
00639 if ( ! (path.startsWith("/mnt/") || (path == "/mnt")) )
00640 if (useFAM(e)) return;
00641 #endif
00642
00643 #ifdef HAVE_DNOTIFY
00644 if ( ! (path.startsWith("/mnt/") || (path == "/mnt")) )
00645 if (useDNotify(e)) return;
00646 #endif
00647
00648 useStat(e);
00649 }
00650
00651
00652 void KDirWatchPrivate::removeEntry( KDirWatch* instance,
00653 const QString& _path, Entry* sub_entry )
00654 {
00655 Entry* e = entry(_path);
00656 if (!e) {
00657 kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
00658 return;
00659 }
00660
00661 if (sub_entry)
00662 e->m_entries.removeRef(sub_entry);
00663 else
00664 e->removeClient(instance);
00665
00666 if (e->m_clients.count() || e->m_entries.count())
00667 return;
00668
00669 if (delayRemove) {
00670
00671 if (removeList.findRef(e)==-1)
00672 removeList.append(e);
00673
00674 return;
00675 }
00676
00677 #ifdef HAVE_FAM
00678 if (e->m_mode == FAMMode) {
00679 if ( e->m_status == Normal) {
00680 FAMCancelMonitor(&fc, &(e->fr) );
00681 kdDebug(7001) << "Cancelled FAM (Req "
00682 << FAMREQUEST_GETREQNUM(&(e->fr))
00683 << ") for " << e->path << endl;
00684 }
00685 else {
00686 if (e->isDir)
00687 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00688 else
00689 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00690 }
00691 }
00692 #endif
00693
00694 #ifdef HAVE_DNOTIFY
00695 if (e->m_mode == DNotifyMode) {
00696 if (!e->isDir) {
00697 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
00698 }
00699 else {
00700
00701 if ( e->m_status == Normal) {
00702 if (e->dn_fd) {
00703 ::close(e->dn_fd);
00704 fd_Entry.remove(e->dn_fd);
00705
00706 kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
00707 << ") for " << e->path << endl;
00708 e->dn_fd = 0;
00709
00710 }
00711 }
00712 else {
00713 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
00714 }
00715 }
00716 }
00717 #endif
00718
00719 if (e->m_mode == StatMode) {
00720 statEntries--;
00721 if ( statEntries == 0 ) {
00722 timer->stop();
00723 kdDebug(7001) << " Stopped Polling Timer" << endl;
00724 }
00725 }
00726
00727 kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
00728 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
00729 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
00730 << endl;
00731 m_mapEntries.remove( e->path );
00732 }
00733
00734
00735
00736
00737
00738 void KDirWatchPrivate::removeEntries( KDirWatch* instance )
00739 {
00740 QPtrList<Entry> list;
00741 int minfreq = 3600000;
00742
00743
00744 EntryMap::Iterator it = m_mapEntries.begin();
00745 for( ; it != m_mapEntries.end(); ++it ) {
00746 Client* c = (*it).m_clients.first();
00747 for(;c;c=(*it).m_clients.next())
00748 if (c->instance == instance) break;
00749 if (c) {
00750 c->count = 1;
00751 list.append(&(*it));
00752 }
00753 else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
00754 minfreq = (*it).freq;
00755 }
00756
00757 for(Entry* e=list.first();e;e=list.next())
00758 removeEntry(instance, e->path, 0);
00759
00760 if (minfreq > freq) {
00761
00762 freq = minfreq;
00763 if (timer->isActive()) timer->changeInterval(freq);
00764 kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
00765 }
00766 }
00767
00768
00769 bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
00770 {
00771 int stillWatching = 0;
00772 Client* c = e->m_clients.first();
00773 for(;c;c=e->m_clients.next()) {
00774 if (!instance || instance == c->instance)
00775 c->watchingStopped = true;
00776 else if (!c->watchingStopped)
00777 stillWatching += c->count;
00778 }
00779
00780 kdDebug(7001) << instance->name() << " stopped scanning " << e->path
00781 << " (now " << stillWatching << " watchers)" << endl;
00782
00783 if (stillWatching == 0) {
00784
00785 e->m_ctime = invalid_ctime;
00786
00787 }
00788 return true;
00789 }
00790
00791
00792 bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
00793 bool notify)
00794 {
00795 int wasWatching = 0, newWatching = 0;
00796 Client* c = e->m_clients.first();
00797 for(;c;c=e->m_clients.next()) {
00798 if (!c->watchingStopped)
00799 wasWatching += c->count;
00800 else if (!instance || instance == c->instance) {
00801 c->watchingStopped = false;
00802 newWatching += c->count;
00803 }
00804 }
00805 if (newWatching == 0)
00806 return false;
00807
00808 kdDebug(7001) << instance->name() << " restarted scanning " << e->path
00809 << " (now " << wasWatching+newWatching << " watchers)" << endl;
00810
00811
00812
00813 int ev = NoChange;
00814 if (wasWatching == 0) {
00815 if (!notify) {
00816 struct stat stat_buf;
00817 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
00818 if (exists) {
00819 e->m_ctime = stat_buf.st_ctime;
00820 e->m_status = Normal;
00821 e->m_nlink = stat_buf.st_nlink;
00822 }
00823 else {
00824 e->m_ctime = invalid_ctime;
00825 e->m_status = NonExistent;
00826 e->m_nlink = 0;
00827 }
00828 }
00829 e->msecLeft = 0;
00830 ev = scanEntry(e);
00831 }
00832 emitEvent(e,ev);
00833
00834 return true;
00835 }
00836
00837
00838 void KDirWatchPrivate::stopScan(KDirWatch* instance)
00839 {
00840 EntryMap::Iterator it = m_mapEntries.begin();
00841 for( ; it != m_mapEntries.end(); ++it )
00842 stopEntryScan(instance, &(*it));
00843 }
00844
00845
00846 void KDirWatchPrivate::startScan(KDirWatch* instance,
00847 bool notify, bool skippedToo )
00848 {
00849 if (!notify)
00850 resetList(instance,skippedToo);
00851
00852 EntryMap::Iterator it = m_mapEntries.begin();
00853 for( ; it != m_mapEntries.end(); ++it )
00854 restartEntryScan(instance, &(*it), notify);
00855
00856
00857 }
00858
00859
00860
00861 void KDirWatchPrivate::resetList( KDirWatch* ,
00862 bool skippedToo )
00863 {
00864 EntryMap::Iterator it = m_mapEntries.begin();
00865 for( ; it != m_mapEntries.end(); ++it ) {
00866
00867 Client* c = (*it).m_clients.first();
00868 for(;c;c=(*it).m_clients.next())
00869 if (!c->watchingStopped || skippedToo)
00870 c->pending = NoChange;
00871 }
00872 }
00873
00874
00875
00876 int KDirWatchPrivate::scanEntry(Entry* e)
00877 {
00878 #ifdef HAVE_FAM
00879 if (e->m_mode == FAMMode) {
00880
00881 if(!e->dirty) return NoChange;
00882 e->dirty = false;
00883 }
00884 #endif
00885
00886
00887 if (e->m_mode == UnknownMode) return NoChange;
00888
00889 #ifdef HAVE_DNOTIFY
00890 if (e->m_mode == DNotifyMode) {
00891
00892 if(!e->dirty) return NoChange;
00893 e->dirty = false;
00894 }
00895 #endif
00896
00897 if (e->m_mode == StatMode) {
00898
00899
00900
00901
00902 e->msecLeft -= freq;
00903 if (e->msecLeft>0) return NoChange;
00904 e->msecLeft += e->freq;
00905 }
00906
00907 struct stat stat_buf;
00908 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
00909 if (exists) {
00910
00911 if (e->m_status == NonExistent) {
00912 e->m_ctime = stat_buf.st_ctime;
00913 e->m_status = Normal;
00914 e->m_nlink = stat_buf.st_nlink;
00915 return Created;
00916 }
00917
00918 if ( (e->m_ctime != invalid_ctime) &&
00919 ((stat_buf.st_ctime != e->m_ctime) ||
00920 (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
00921 e->m_ctime = stat_buf.st_ctime;
00922 e->m_nlink = stat_buf.st_nlink;
00923 return Changed;
00924 }
00925
00926 return NoChange;
00927 }
00928
00929
00930
00931 if (e->m_ctime == invalid_ctime)
00932 return NoChange;
00933
00934 e->m_ctime = invalid_ctime;
00935 e->m_nlink = 0;
00936 e->m_status = NonExistent;
00937
00938 return Deleted;
00939 }
00940
00941
00942
00943
00944
00945 void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
00946 {
00947 QString path = e->path;
00948 if (!fileName.isEmpty()) {
00949 if (fileName[0] == '/')
00950 path = fileName;
00951 else
00952 path += "/" + fileName;
00953 }
00954
00955 Client* c = e->m_clients.first();
00956 for(;c;c=e->m_clients.next()) {
00957 if (c->instance==0 || c->count==0) continue;
00958
00959 if (c->watchingStopped) {
00960
00961 if (event == Changed)
00962 c->pending |= event;
00963 else if (event == Created || event == Deleted)
00964 c->pending = event;
00965 continue;
00966 }
00967
00968 if (event == NoChange || event == Changed)
00969 event |= c->pending;
00970 c->pending = NoChange;
00971 if (event == NoChange) continue;
00972
00973 if (event & Deleted) {
00974 c->instance->setDeleted(path);
00975
00976 continue;
00977 }
00978
00979 if (event & Created) {
00980 c->instance->setCreated(path);
00981
00982 }
00983
00984 if (event & Changed)
00985 c->instance->setDirty(path);
00986 }
00987 }
00988
00989
00990 void KDirWatchPrivate::slotRemoveDelayed()
00991 {
00992 Entry* e;
00993 delayRemove = false;
00994 for(e=removeList.first();e;e=removeList.next())
00995 removeEntry(0, e->path, 0);
00996 removeList.clear();
00997 }
00998
00999
01000
01001
01002 void KDirWatchPrivate::slotRescan()
01003 {
01004 EntryMap::Iterator it;
01005
01006
01007
01008
01009 bool timerRunning = timer->isActive();
01010 if ( timerRunning )
01011 timer->stop();
01012
01013
01014
01015 delayRemove = true;
01016
01017 #ifdef HAVE_DNOTIFY
01018 QPtrList<Entry> dList, cList;
01019 #endif
01020
01021 if (rescan_all)
01022 {
01023
01024 it = m_mapEntries.begin();
01025 for( ; it != m_mapEntries.end(); ++it )
01026 (*it).dirty = true;
01027 rescan_all = false;
01028 }
01029 else
01030 {
01031
01032 it = m_mapEntries.begin();
01033 for( ; it != m_mapEntries.end(); ++it )
01034 if ( ((*it).m_mode == DNotifyMode) && (*it).dirty )
01035 (*it).propagate_dirty();
01036 }
01037
01038 it = m_mapEntries.begin();
01039 for( ; it != m_mapEntries.end(); ++it ) {
01040
01041 if (!(*it).isValid()) continue;
01042
01043 int ev = scanEntry( &(*it) );
01044
01045 #ifdef HAVE_DNOTIFY
01046 if ((*it).m_mode == DNotifyMode) {
01047 if ((*it).isDir && (ev == Deleted)) {
01048 dList.append( &(*it) );
01049
01050
01051 if ((*it).dn_fd) {
01052 ::close((*it).dn_fd);
01053 fd_Entry.remove((*it).dn_fd);
01054 (*it).dn_fd = 0;
01055 }
01056 }
01057
01058 else if ((*it).isDir && (ev == Created)) {
01059
01060 if ( (*it).dn_fd == 0) {
01061 cList.append( &(*it) );
01062 if (! useDNotify( &(*it) )) {
01063
01064 useStat( &(*it) );
01065 }
01066 }
01067 }
01068 }
01069 #endif
01070
01071 if ( ev != NoChange )
01072 emitEvent( &(*it), ev);
01073 }
01074
01075
01076 #ifdef HAVE_DNOTIFY
01077
01078 Entry* e;
01079 for(e=dList.first();e;e=dList.next())
01080 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01081
01082
01083 for(e=cList.first();e;e=cList.next())
01084 removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
01085 #endif
01086
01087 if ( timerRunning )
01088 timer->start(freq);
01089
01090 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01091 }
01092
01093 bool KDirWatchPrivate::isNoisyFile( const char * filename )
01094 {
01095
01096 if ( *filename == '.') {
01097 if (strncmp(filename, ".X.err", 6) == 0) return true;
01098 if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
01099
01100
01101 if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
01102 }
01103
01104 return false;
01105 }
01106
01107 #ifdef HAVE_FAM
01108 void KDirWatchPrivate::famEventReceived()
01109 {
01110 static FAMEvent fe;
01111
01112 delayRemove = true;
01113
01114 while(use_fam && FAMPending(&fc)) {
01115 if (FAMNextEvent(&fc, &fe) == -1) {
01116 kdWarning(7001) << "FAM connection problem, switching to polling."
01117 << endl;
01118 use_fam = false;
01119 delete sn; sn = 0;
01120
01121
01122 EntryMap::Iterator it;
01123 it = m_mapEntries.begin();
01124 for( ; it != m_mapEntries.end(); ++it )
01125 if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
01126 #ifdef HAVE_DNOTIFY
01127 if (useDNotify( &(*it) )) continue;
01128 #endif
01129 useStat( &(*it) );
01130 }
01131 }
01132 else
01133 checkFAMEvent(&fe);
01134 }
01135
01136 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
01137 }
01138
01139 void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
01140 {
01141
01142 if ((fe->code == FAMExists) ||
01143 (fe->code == FAMEndExist) ||
01144 (fe->code == FAMAcknowledge)) return;
01145
01146 if ( isNoisyFile( fe->filename ) )
01147 return;
01148
01149 Entry* e = 0;
01150 EntryMap::Iterator it = m_mapEntries.begin();
01151 for( ; it != m_mapEntries.end(); ++it )
01152 if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
01153 FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
01154 e = &(*it);
01155 break;
01156 }
01157
01158
01159
01160 kdDebug(7001) << "Processing FAM event ("
01161 << ((fe->code == FAMChanged) ? "FAMChanged" :
01162 (fe->code == FAMDeleted) ? "FAMDeleted" :
01163 (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
01164 (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
01165 (fe->code == FAMCreated) ? "FAMCreated" :
01166 (fe->code == FAMMoved) ? "FAMMoved" :
01167 (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
01168 (fe->code == FAMExists) ? "FAMExists" :
01169 (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
01170 << ", " << fe->filename
01171 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
01172 << ")" << endl;
01173
01174 if (!e) {
01175
01176
01177 return;
01178 }
01179
01180 if (e->m_status == NonExistent) {
01181 kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
01182 return;
01183 }
01184
01185
01186 e->dirty = true;
01187 if (!rescan_timer.isActive())
01188 rescan_timer.start(m_PollInterval, true);
01189
01190
01191 if (e->isDir)
01192 switch (fe->code)
01193 {
01194 case FAMDeleted:
01195
01196 if (fe->filename[0] == '/')
01197 {
01198
01199
01200 e->m_status = NonExistent;
01201 FAMCancelMonitor(&fc, &(e->fr) );
01202 kdDebug(7001) << "Cancelled FAMReq "
01203 << FAMREQUEST_GETREQNUM(&(e->fr))
01204 << " for " << e->path << endl;
01205
01206 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
01207 }
01208 break;
01209
01210 case FAMCreated: {
01211
01212 Entry *sub_entry = e->m_entries.first();
01213 for(;sub_entry; sub_entry = e->m_entries.next())
01214 if (sub_entry->path == e->path + "/" + fe->filename) break;
01215 if (sub_entry && sub_entry->isDir) {
01216 QString path = e->path;
01217 removeEntry(0,e->path,sub_entry);
01218 sub_entry->m_status = Normal;
01219 if (!useFAM(sub_entry))
01220 useStat(sub_entry);
01221 }
01222 break;
01223 }
01224
01225 default:
01226 break;
01227 }
01228 }
01229 #else
01230 void KDirWatchPrivate::famEventReceived() {}
01231 #endif
01232
01233
01234 void KDirWatchPrivate::statistics()
01235 {
01236 EntryMap::Iterator it;
01237
01238 kdDebug(7001) << "Entries watched:" << endl;
01239 if (m_mapEntries.count()==0) {
01240 kdDebug(7001) << " None." << endl;
01241 }
01242 else {
01243 it = m_mapEntries.begin();
01244 for( ; it != m_mapEntries.end(); ++it ) {
01245 Entry* e = &(*it);
01246 kdDebug(7001) << " " << e->path << " ("
01247 << ((e->m_status==Normal)?"":"Nonexistent ")
01248 << (e->isDir ? "Dir":"File") << ", using "
01249 << ((e->m_mode == FAMMode) ? "FAM" :
01250 (e->m_mode == DNotifyMode) ? "DNotify" :
01251 (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
01252 << ")" << endl;
01253
01254 Client* c = e->m_clients.first();
01255 for(;c; c = e->m_clients.next()) {
01256 QString pending;
01257 if (c->watchingStopped) {
01258 if (c->pending & Deleted) pending += "deleted ";
01259 if (c->pending & Created) pending += "created ";
01260 if (c->pending & Changed) pending += "changed ";
01261 if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
01262 pending = ", stopped" + pending;
01263 }
01264 kdDebug(7001) << " by " << c->instance->name()
01265 << " (" << c->count << " times)"
01266 << pending << endl;
01267 }
01268 if (e->m_entries.count()>0) {
01269 kdDebug(7001) << " dependent entries:" << endl;
01270 Entry* d = e->m_entries.first();
01271 for(;d; d = e->m_entries.next()) {
01272 kdDebug(7001) << " " << d->path << endl;
01273 }
01274 }
01275 }
01276 }
01277 }
01278
01279
01280
01281
01282
01283
01284 static KStaticDeleter<KDirWatch> sd_dw;
01285 KDirWatch* KDirWatch::s_pSelf = 0L;
01286
01287 KDirWatch* KDirWatch::self()
01288 {
01289 if ( !s_pSelf ) {
01290 sd_dw.setObject( s_pSelf, new KDirWatch );
01291 }
01292
01293 return s_pSelf;
01294 }
01295
01296 bool KDirWatch::exists()
01297 {
01298 return s_pSelf != 0;
01299 }
01300
01301 KDirWatch::KDirWatch (QObject* parent, const char* name)
01302 : QObject(parent,name)
01303 {
01304 if (!name) {
01305 static int nameCounter = 0;
01306
01307 nameCounter++;
01308 setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
01309 }
01310
01311 if (!dwp_self)
01312 dwp_self = new KDirWatchPrivate;
01313 d = dwp_self;
01314 d->ref();
01315
01316 _isStopped = false;
01317 }
01318
01319 KDirWatch::~KDirWatch()
01320 {
01321 if (d) d->removeEntries(this);
01322 if ( d->deref() )
01323 {
01324
01325 delete d;
01326 dwp_self = 0L;
01327 }
01328 }
01329
01330
01331
01332 void KDirWatch::addDir( const QString& _path,
01333 bool watchFiles, bool recursive)
01334 {
01335 if (watchFiles || recursive) {
01336 kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
01337 << endl;
01338 }
01339 if (d) d->addEntry(this, _path, 0, true);
01340 }
01341
01342 void KDirWatch::addFile( const QString& _path )
01343 {
01344 if (d) d->addEntry(this, _path, 0, false);
01345 }
01346
01347 QDateTime KDirWatch::ctime( const QString &_path )
01348 {
01349 KDirWatchPrivate::Entry* e = d->entry(_path);
01350
01351 if (!e)
01352 return QDateTime();
01353
01354 QDateTime result;
01355 result.setTime_t(e->m_ctime);
01356 return result;
01357 }
01358
01359 void KDirWatch::removeDir( const QString& _path )
01360 {
01361 if (d) d->removeEntry(this, _path, 0);
01362 }
01363
01364 void KDirWatch::removeFile( const QString& _path )
01365 {
01366 if (d) d->removeEntry(this, _path, 0);
01367 }
01368
01369 bool KDirWatch::stopDirScan( const QString& _path )
01370 {
01371 if (d) {
01372 KDirWatchPrivate::Entry *e = d->entry(_path);
01373 if (e && e->isDir) return d->stopEntryScan(this, e);
01374 }
01375 return false;
01376 }
01377
01378 bool KDirWatch::restartDirScan( const QString& _path )
01379 {
01380 if (d) {
01381 KDirWatchPrivate::Entry *e = d->entry(_path);
01382 if (e && e->isDir)
01383
01384 return d->restartEntryScan(this, e, false);
01385 }
01386 return false;
01387 }
01388
01389 void KDirWatch::stopScan()
01390 {
01391 if (d) d->stopScan(this);
01392 _isStopped = true;
01393 }
01394
01395 void KDirWatch::startScan( bool notify, bool skippedToo )
01396 {
01397 _isStopped = false;
01398 if (d) d->startScan(this, notify, skippedToo);
01399 }
01400
01401
01402 bool KDirWatch::contains( const QString& _path ) const
01403 {
01404 KDirWatchPrivate::Entry* e = d->entry(_path);
01405 if (!e)
01406 return false;
01407
01408 KDirWatchPrivate::Client* c = e->m_clients.first();
01409 for(;c;c=e->m_clients.next())
01410 if (c->instance == this) return true;
01411
01412 return false;
01413 }
01414
01415 void KDirWatch::statistics()
01416 {
01417 if (!dwp_self) {
01418 kdDebug(7001) << "KDirWatch not used" << endl;
01419 return;
01420 }
01421 dwp_self->statistics();
01422 }
01423
01424
01425 void KDirWatch::setCreated( const QString & _file )
01426 {
01427 kdDebug(7001) << name() << " emitting created " << _file << endl;
01428 emit created( _file );
01429 }
01430
01431 void KDirWatch::setDirty( const QString & _file )
01432 {
01433 kdDebug(7001) << name() << " emitting dirty " << _file << endl;
01434 emit dirty( _file );
01435 }
01436
01437 void KDirWatch::setDeleted( const QString & _file )
01438 {
01439 kdDebug(7001) << name() << " emitting deleted " << _file << endl;
01440 emit deleted( _file );
01441 }
01442
01443 KDirWatch::Method KDirWatch::internalMethod()
01444 {
01445 #ifdef HAVE_FAM
01446 if (d->use_fam)
01447 return KDirWatch::FAM;
01448 #endif
01449 #ifdef HAVE_DNOTIFY
01450 if (d->supports_dnotify)
01451 return KDirWatch::DNotify;
01452 #endif
01453 return KDirWatch::Stat;
01454 }
01455
01456
01457 #include "kdirwatch.moc"
01458 #include "kdirwatch_p.moc"
01459
01460
01461
01462