summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--microkde/kdirwatch.h14
-rw-r--r--microkde/kio/kio/kdirwatch.cpp1442
-rw-r--r--microkde/kio/kio/kdirwatch.h288
-rw-r--r--microkde/kio/kio/kdirwatch_p.h153
4 files changed, 1883 insertions, 14 deletions
diff --git a/microkde/kdirwatch.h b/microkde/kdirwatch.h
deleted file mode 100644
index 22b4dec..0000000
--- a/microkde/kdirwatch.h
+++ b/dev/null
@@ -1,14 +0,0 @@
1#ifndef MICROKDE_KDIRWATCH_H
2#define MICROKDE_KDIRWATCH_H
3
4#include <qobject.h>
5
6class KDirWatch : public QObject
7{
8 public:
9 KDirWatch() {}
10
11 void addDir( const QString & ) {}
12};
13
14#endif
diff --git a/microkde/kio/kio/kdirwatch.cpp b/microkde/kio/kio/kdirwatch.cpp
new file mode 100644
index 0000000..98d24e0
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch.cpp
@@ -0,0 +1,1442 @@
1// -*- c-basic-offset: 2 -*-
2/* This file is part of the KDE libraries
3 Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 Boston, MA 02111-1307, USA.
18*/
19
20/*
21Enhanced Version of the file for platform independent KDE tools.
22Copyright (c) 2004 Ulf Schenk
23
24$Id$
25*/
26
27
28// CHANGES:
29// Februar 2002 - Add file watching and remote mount check for STAT
30// Mar 30, 2001 - Native support for Linux dir change notification.
31// Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
32// May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)
33// May 23. 1998 - Removed static pointer - you can have more instances.
34// It was Needed for KRegistry. KDirWatch now emits signals and doesn't
35// call (or need) KFM. No more URL's - just plain paths. (sven)
36// Mar 29. 1998 - added docs, stop/restart for particular Dirs and
37// deep copies for list of dirs. (sven)
38// Mar 28. 1998 - Created. (sven)
39
40
41//US #include <config.h>
42
43#ifdef HAVE_DNOTIFY
44#include <unistd.h>
45#include <time.h>
46#include <fcntl.h>
47#include <signal.h>
48#include <errno.h>
49#endif
50
51#include <sys/stat.h>
52#include <assert.h>
53#include <qdir.h>
54#include <qfile.h>
55#include <qintdict.h>
56#include <qptrlist.h>
57#include <qsocketnotifier.h>
58#include <qstringlist.h>
59#include <qtimer.h>
60
61#include <kapplication.h>
62#include <kdebug.h>
63#include <kconfig.h>
64#include <kconfigbase.h>
65#include <kglobal.h>
66#include <kstaticdeleter.h>
67
68#include "kdirwatch.h"
69#include "kdirwatch_p.h"
70//US #include "global.h" // KIO::probably_slow_mounted
71
72#define NO_NOTIFY (time_t) 0
73
74static KDirWatchPrivate* dwp_self = 0;
75
76#ifdef HAVE_DNOTIFY
77
78#include <sys/utsname.h>
79
80static int dnotify_signal = 0;
81
82/* DNOTIFY signal handler
83 *
84 * As this is called asynchronously, only a flag is set and
85 * a rescan is requested.
86 * This is done by writing into a pipe to trigger a QSocketNotifier
87 * watching on this pipe: a timer is started and after a timeout,
88 * the rescan is done.
89 */
90void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
91{
92 // write might change errno, we have to save it and restore it
93 // (Richard Stevens, Advanced programming in the Unix Environment)
94 int saved_errno = errno;
95
96 Entry* e = (dwp_self) ? dwp_self->fd_Entry.find(si->si_fd) :0;
97
98// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
99 // << QString(e ? e->path:"unknown") << endl;
100
101 if(!e || e->dn_fd != si->si_fd) {
102 qDebug("fatal error in KDirWatch");
103 } else
104 e->dn_dirty = true;
105
106 char c = 0;
107 write(dwp_self->mPipe[1], &c, 1);
108 errno = saved_errno;
109}
110
111static struct sigaction old_sigio_act;
112/* DNOTIFY SIGIO signal handler
113 *
114 * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
115 */
116void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
117{
118 // write might change errno, we have to save it and restore it
119 // (Richard Stevens, Advanced programming in the Unix Environment)
120 int saved_errno = errno;
121
122 if (dwp_self)
123 dwp_self->rescan_all = true;
124
125 char c = 0;
126 write(dwp_self->mPipe[1], &c, 1);
127
128 errno = saved_errno;
129
130 // Call previous signal handler
131 if (old_sigio_act.sa_flags & SA_SIGINFO)
132 {
133 if (old_sigio_act.sa_sigaction)
134 (*old_sigio_act.sa_sigaction)(sig, si, p);
135 }
136 else
137 {
138 if ((old_sigio_act.sa_handler != SIG_DFL) &&
139 (old_sigio_act.sa_handler != SIG_IGN))
140 (*old_sigio_act.sa_handler)(sig);
141 }
142}
143#endif
144
145
146//
147// Class KDirWatchPrivate (singleton)
148//
149
150/* All entries (files/directories) to be watched in the
151 * application (coming from multiple KDirWatch instances)
152 * are registered in a single KDirWatchPrivate instance.
153 *
154 * At the moment, the following methods for file watching
155 * are supported:
156 * - Polling: All files to be watched are polled regularly
157 * using stat (more precise: QFileInfo.lastModified()).
158 * The polling frequency is determined from global kconfig
159 * settings, defaulting to 500 ms for local directories
160 * and 5000 ms for remote mounts
161 * - FAM (File Alternation Monitor): first used on IRIX, SGI
162 * has ported this method to LINUX. It uses a kernel part
163 * (IMON, sending change events to /dev/imon) and a user
164 * level damon (fam), to which applications connect for
165 * notification of file changes. For NFS, the fam damon
166 * on the NFS server machine is used; if IMON is not built
167 * into the kernel, fam uses polling for local files.
168 * - DNOTIFY: In late LINUX 2.3.x, directory notification was
169 * introduced. By opening a directory, you can request for
170 * UNIX signals to be sent to the process when a directory
171 * is changed.
172 */
173
174KDirWatchPrivate::KDirWatchPrivate()
175{
176 timer = new QTimer(this);
177 connect (timer, SIGNAL(timeout()), this, SLOT(slotRescan()));
178 freq = 3600000; // 1 hour as upper bound
179 statEntries = 0;
180 delayRemove = false;
181 m_ref = 0;
182
183//US KConfigGroup config(KGlobal::config(), QCString("DirWatch"));
184//US m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
185//US m_PollInterval = config.readNumEntry("PollInterval", 500);
186 KConfig *config = KGlobal::config();
187 KConfigGroupSaver saver( config, QCString("DirWatch") );
188
189 m_nfsPollInterval = config->readNumEntry("NFSPollInterval", 5000);
190 m_PollInterval = config->readNumEntry("PollInterval", 500);
191
192
193 QString available("Stat");
194
195#ifdef HAVE_FAM
196 // It's possible that FAM server can't be started
197 if (FAMOpen(&fc) ==0) {
198 available += ", FAM";
199 use_fam=true;
200 sn = new QSocketNotifier( FAMCONNECTION_GETFD(&fc),
201 QSocketNotifier::Read, this);
202 connect( sn, SIGNAL(activated(int)),
203 this, SLOT(famEventReceived()) );
204 }
205 else {
206 kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
207 use_fam=false;
208 }
209#endif
210
211#ifdef HAVE_DNOTIFY
212 supports_dnotify = true; // not guilty until proven guilty
213 rescan_all = false;
214 struct utsname uts;
215 int major, minor, patch;
216 if (uname(&uts) < 0)
217 supports_dnotify = false; // *shrug*
218 else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
219 supports_dnotify = false; // *shrug*
220 else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
221 kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
222 supports_dnotify = false;
223 }
224
225 if( supports_dnotify ) {
226 available += ", DNotify";
227
228 pipe(mPipe);
229 fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
230 fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
231 mSn = new QSocketNotifier( mPipe[0], QSocketNotifier::Read, this);
232 connect(mSn, SIGNAL(activated(int)), this, SLOT(slotActivated()));
233 connect(&mTimer, SIGNAL(timeout()), this, SLOT(slotRescan()));
234 struct sigaction act;
235 act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
236 sigemptyset(&act.sa_mask);
237 act.sa_flags = SA_SIGINFO;
238#ifdef SA_RESTART
239 act.sa_flags |= SA_RESTART;
240#endif
241 if( dnotify_signal == 0 )
242 dnotify_signal = SIGRTMIN + 8;
243 sigaction(dnotify_signal, &act, NULL);
244
245 act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
246 sigaction(SIGIO, &act, &old_sigio_act);
247 }
248#endif
249
250 kdDebug(7001) << "Available methods: " << available << endl;
251}
252
253/* This should never be called, but doesn't harm */
254KDirWatchPrivate::~KDirWatchPrivate()
255{
256 timer->stop();
257
258 /* remove all entries being watched */
259 removeEntries(0);
260
261#ifdef HAVE_FAM
262 if (use_fam) {
263 FAMClose(&fc);
264 kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
265 }
266#endif
267
268}
269
270#ifdef HAVE_DNOTIFY
271void KDirWatchPrivate::slotActivated()
272{
273 char dummy_buf[100];
274 read(mPipe[0], &dummy_buf, 100);
275
276 if (!mTimer.isActive())
277 mTimer.start(200, true);
278}
279
280/* In DNOTIFY mode, only entries which are marked dirty are scanned.
281 * We first need to mark all yet nonexistant, but possible created
282 * entries as dirty...
283 */
284void KDirWatchPrivate::Entry::propagate_dirty()
285{
286 Entry* sub_entry;
287 for(sub_entry = m_entries.first(); sub_entry; sub_entry = m_entries.next())
288 {
289 if (!sub_entry->dn_dirty)
290 {
291 sub_entry->dn_dirty = true;
292 sub_entry->propagate_dirty();
293 }
294 }
295}
296
297#else // !HAVE_DNOTIFY
298// slots always have to be defined...
299void KDirWatchPrivate::slotActivated() {}
300#endif
301
302/* A KDirWatch instance is interested in getting events for
303 * this file/Dir entry.
304 */
305void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
306{
307 Client* client = m_clients.first();
308 for(;client; client = m_clients.next())
309 if (client->instance == instance) break;
310
311 if (client) {
312 client->count++;
313 return;
314 }
315
316 client = new Client;
317 client->instance = instance;
318 client->count = 1;
319 client->watchingStopped = instance->isStopped();
320 client->pending = NoChange;
321
322 m_clients.append(client);
323}
324
325void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
326{
327 Client* client = m_clients.first();
328 for(;client; client = m_clients.next())
329 if (client->instance == instance) break;
330
331 if (client) {
332 client->count--;
333 if (client->count == 0) {
334 m_clients.removeRef(client);
335 delete client;
336 }
337 }
338}
339
340/* get number of clients */
341int KDirWatchPrivate::Entry::clients()
342{
343 int clients = 0;
344 Client* client = m_clients.first();
345 for(;client; client = m_clients.next())
346 clients += client->count;
347
348 return clients;
349}
350
351
352KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const QString& _path)
353{
354// we only support absolute paths
355 if (_path.left(1) != "/") {
356 return 0;
357 }
358
359 QString path = _path;
360
361 if ( path.length() > 1 && path.right(1) == "/" )
362 path.truncate( path.length() - 1 );
363
364 EntryMap::Iterator it = m_mapEntries.find( path );
365 if ( it == m_mapEntries.end() )
366 return 0;
367 else
368 return &(*it);
369}
370
371// set polling frequency for a entry and adjust global freq if needed
372void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
373{
374 e->freq = newFreq;
375
376 // a reasonable frequency for the global polling timer
377 if (e->freq < freq) {
378 freq = e->freq;
379 if (timer->isActive()) timer->changeInterval(freq);
380 kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
381 }
382}
383
384
385#if defined(HAVE_FAM)
386// setup FAM notification, returns false if not possible
387bool KDirWatchPrivate::useFAM(Entry* e)
388{
389 if (!use_fam) return false;
390
391 e->m_mode = FAMMode;
392
393 if (e->isDir) {
394 if (e->m_status == NonExistent) {
395 // If the directory does not exist we watch the parent directory
396 addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
397 }
398 else {
399 int res =FAMMonitorDirectory(&fc, QFile::encodeName(e->path),
400 &(e->fr), e);
401 if (res<0) {
402 e->m_mode = UnknownMode;
403 use_fam=false;
404 return false;
405 }
406 kdDebug(7001) << " Setup FAM (Req "
407 << FAMREQUEST_GETREQNUM(&(e->fr))
408 << ") for " << e->path << endl;
409 }
410 }
411 else {
412 if (e->m_status == NonExistent) {
413 // If the file does not exist we watch the directory
414 addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
415 }
416 else {
417 int res = FAMMonitorFile(&fc, QFile::encodeName(e->path),
418 &(e->fr), e);
419 if (res<0) {
420 e->m_mode = UnknownMode;
421 use_fam=false;
422 return false;
423 }
424
425 kdDebug(7001) << " Setup FAM (Req "
426 << FAMREQUEST_GETREQNUM(&(e->fr))
427 << ") for " << e->path << endl;
428 }
429 }
430
431 // handle FAM events to avoid deadlock
432 // (FAM sends back all files in a directory when monitoring)
433 famEventReceived();
434
435 return true;
436}
437#endif
438
439
440#ifdef HAVE_DNOTIFY
441// setup DNotify notification, returns false if not possible
442bool KDirWatchPrivate::useDNotify(Entry* e)
443{
444 e->dn_fd = 0;
445 if (!supports_dnotify) return false;
446
447 e->m_mode = DNotifyMode;
448
449 if (e->isDir) {
450 e->dn_dirty = false;
451 if (e->m_status == Normal) {
452 int fd = open(QFile::encodeName(e->path).data(), O_RDONLY);
453 // Migrate fd to somewhere above 128. Some libraries have
454 // constructs like:
455 // fd = socket(...)
456 // if (fd > ARBITRARY_LIMIT)
457 // return error;
458 //
459 // Since programs might end up using a lot of KDirWatch objects
460 // for a rather long time the above braindamage could get
461 // triggered.
462 //
463 // By moving the kdirwatch fd's to > 128, calls like socket() will keep
464 // returning fd's < ARBITRARY_LIMIT for a bit longer.
465 int fd2 = fcntl(fd, F_DUPFD, 128);
466 if (fd2 >= 0)
467 {
468 close(fd);
469 fd = fd2;
470 }
471 if (fd<0) {
472 e->m_mode = UnknownMode;
473 return false;
474 }
475
476 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
477 // if dependant is a file watch, we check for MODIFY & ATTRIB too
478 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
479 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
480
481 if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
482 fcntl(fd, F_NOTIFY, mask) < 0) {
483
484 kdDebug(7001) << "Not using Linux Directory Notifications."
485 << endl;
486 supports_dnotify = false;
487 ::close(fd);
488 e->m_mode = UnknownMode;
489 return false;
490 }
491
492 fd_Entry.replace(fd, e);
493 e->dn_fd = fd;
494
495 kdDebug(7001) << " Setup DNotify (fd " << fd
496 << ") for " << e->path << endl;
497 }
498 else { // NotExisting
499 addEntry(0, QDir::cleanDirPath(e->path+"/.."), e, true);
500 }
501 }
502 else { // File
503 // we always watch the directory (DNOTIFY can't watch files alone)
504 // this notifies us about changes of files therein
505 addEntry(0, QFileInfo(e->path).dirPath(true), e, true);
506 }
507
508 return true;
509}
510#endif
511
512
513bool KDirWatchPrivate::useStat(Entry* e)
514{
515//US we have no KIO::probably_slow_mounted. So disable this part
516//US if (KIO::probably_slow_mounted(e->path))
517//US useFreq(e, m_nfsPollInterval);
518//US else
519 useFreq(e, m_PollInterval);
520
521 if (e->m_mode != StatMode) {
522 e->m_mode = StatMode;
523 statEntries++;
524
525 if ( statEntries == 1 ) {
526 // if this was first STAT entry (=timer was stopped)
527 timer->start(freq); // then start the timer
528 kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
529 }
530 }
531
532 kdDebug(7001) << " Setup Stat (freq " << e->freq
533 << ") for " << e->path << endl;
534
535 return true;
536}
537
538
539/* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
540 * providing in <isDir> the type of the entry to be watched.
541 * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
542 * this entry needs another entry to watch himself (when notExistent).
543 */
544void KDirWatchPrivate::addEntry(KDirWatch* instance, const QString& _path,
545 Entry* sub_entry, bool isDir)
546{
547 QString path = _path;
548 if (path.startsWith("/dev/") || (path == "/dev"))
549 return; // Don't even go there.
550
551 if ( path.length() > 1 && path.right(1) == "/" )
552 path.truncate( path.length() - 1 );
553
554 EntryMap::Iterator it = m_mapEntries.find( path );
555 if ( it != m_mapEntries.end() )
556 {
557 if (sub_entry) {
558 (*it).m_entries.append(sub_entry);
559 kdDebug(7001) << "Added already watched Entry " << path
560 << " (for " << sub_entry->path << ")" << endl;
561#ifdef HAVE_DNOTIFY
562 Entry* e = &(*it);
563 if( e->dn_fd > 0 ) {
564 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
565 // if dependant is a file watch, we check for MODIFY & ATTRIB too
566 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
567 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
568 if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
569 ::close(e->dn_fd);
570 e->m_mode = UnknownMode;
571 fd_Entry.remove(e->dn_fd);
572 e->dn_fd = 0;
573 useStat( e );
574 }
575 }
576#endif
577 }
578 else {
579 (*it).addClient(instance);
580 kdDebug(7001) << "Added already watched Entry " << path
581 << " (now " << (*it).clients() << " clients)"
582 << QString(" [%1]").arg(instance->name()) << endl;
583 }
584 return;
585 }
586
587 // we have a new path to watch
588
589 struct stat stat_buf;
590 bool exists = (stat(QFile::encodeName(path), &stat_buf) == 0);
591
592 Entry newEntry;
593 m_mapEntries.insert( path, newEntry );
594 // the insert does a copy, so we have to use <e> now
595 Entry* e = &(m_mapEntries[path]);
596
597 if (exists) {
598 e->isDir = S_ISDIR(stat_buf.st_mode);
599
600 if (e->isDir && !isDir)
601 qWarning("KDirWatch: %s is a directory. Use addDir!", path.ascii());
602 else if (!e->isDir && isDir)
603 qWarning("KDirWatch: %s is a file. Use addFile!", path.ascii());
604
605 e->m_ctime = stat_buf.st_ctime;
606 e->m_status = Normal;
607 e->m_nlink = stat_buf.st_nlink;
608 }
609 else {
610 e->isDir = isDir;
611 e->m_ctime = invalid_ctime;
612 e->m_status = NonExistent;
613 e->m_nlink = 0;
614 }
615
616 e->path = path;
617 if (sub_entry)
618 e->m_entries.append(sub_entry);
619 else
620 e->addClient(instance);
621
622 kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
623 << (e->m_status == NonExistent ? " NotExisting" : "")
624 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
625 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
626 << endl;
627
628
629 // now setup the notification method
630 e->m_mode = UnknownMode;
631 e->msecLeft = 0;
632
633#if defined(HAVE_FAM)
634 if (useFAM(e)) return;
635#endif
636
637#ifdef HAVE_DNOTIFY
638 if (useDNotify(e)) return;
639#endif
640
641 useStat(e);
642}
643
644
645void KDirWatchPrivate::removeEntry( KDirWatch* instance,
646 const QString& _path, Entry* sub_entry )
647{
648 Entry* e = entry(_path);
649 if (!e) {
650 kdWarning(7001) << "KDirWatch::removeDir can't handle '" << _path << "'" << endl;
651 return;
652 }
653
654 if (sub_entry)
655 e->m_entries.removeRef(sub_entry);
656 else
657 e->removeClient(instance);
658
659 if (e->m_clients.count() || e->m_entries.count())
660 return;
661
662 if (delayRemove) {
663 // removeList is allowed to contain any entry at most once
664 if (removeList.findRef(e)==-1)
665 removeList.append(e);
666 // now e->isValid() is false
667 return;
668 }
669
670#ifdef HAVE_FAM
671 if (e->m_mode == FAMMode) {
672 if ( e->m_status == Normal) {
673 FAMCancelMonitor(&fc, &(e->fr) );
674 kdDebug(7001) << "Cancelled FAM (Req "
675 << FAMREQUEST_GETREQNUM(&(e->fr))
676 << ") for " << e->path << endl;
677 }
678 else {
679 if (e->isDir)
680 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
681 else
682 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
683 }
684 }
685#endif
686
687#ifdef HAVE_DNOTIFY
688 if (e->m_mode == DNotifyMode) {
689 if (!e->isDir) {
690 removeEntry(0, QFileInfo(e->path).dirPath(true), e);
691 }
692 else { // isDir
693 // must close the FD.
694 if ( e->m_status == Normal) {
695 if (e->dn_fd) {
696 ::close(e->dn_fd);
697 fd_Entry.remove(e->dn_fd);
698
699 kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
700 << ") for " << e->path << endl;
701 e->dn_fd = 0;
702
703 }
704 }
705 else {
706 removeEntry(0, QDir::cleanDirPath(e->path+"/.."), e);
707 }
708 }
709 }
710#endif
711
712 if (e->m_mode == StatMode) {
713 statEntries--;
714 if ( statEntries == 0 ) {
715 timer->stop(); // stop timer if lists are empty
716 kdDebug(7001) << " Stopped Polling Timer" << endl;
717 }
718 }
719
720 kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path
721 << (sub_entry ? QString(" for %1").arg(sub_entry->path) : QString(""))
722 << (instance ? QString(" [%1]").arg(instance->name()) : QString(""))
723 << endl;
724 m_mapEntries.remove( e->path ); // <e> not valid any more
725}
726
727
728/* Called from KDirWatch destructor:
729 * remove <instance> as client from all entries
730 */
731void KDirWatchPrivate::removeEntries( KDirWatch* instance )
732{
733 QPtrList<Entry> list;
734 int minfreq = 3600000;
735
736 // put all entries where instance is a client in list
737 EntryMap::Iterator it = m_mapEntries.begin();
738 for( ; it != m_mapEntries.end(); ++it ) {
739 Client* c = (*it).m_clients.first();
740 for(;c;c=(*it).m_clients.next())
741 if (c->instance == instance) break;
742 if (c) {
743 c->count = 1; // forces deletion of instance as client
744 list.append(&(*it));
745 }
746 else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
747 minfreq = (*it).freq;
748 }
749
750 for(Entry* e=list.first();e;e=list.next())
751 removeEntry(instance, e->path, 0);
752
753 if (minfreq > freq) {
754 // we can decrease the global polling frequency
755 freq = minfreq;
756 if (timer->isActive()) timer->changeInterval(freq);
757 kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
758 }
759}
760
761// instance ==0: stop scanning for all instances
762bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
763{
764 int stillWatching = 0;
765 Client* c = e->m_clients.first();
766 for(;c;c=e->m_clients.next()) {
767 if (!instance || instance == c->instance)
768 c->watchingStopped = true;
769 else if (!c->watchingStopped)
770 stillWatching += c->count;
771 }
772
773 kdDebug(7001) << instance->name() << " stopped scanning " << e->path
774 << " (now " << stillWatching << " watchers)" << endl;
775
776 if (stillWatching == 0) {
777 // if nobody is interested, we don't watch
778 e->m_ctime = invalid_ctime; // invalid
779 // e->m_status = Normal;
780 }
781 return true;
782}
783
784// instance ==0: start scanning for all instances
785bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
786 bool notify)
787{
788 int wasWatching = 0, newWatching = 0;
789 Client* c = e->m_clients.first();
790 for(;c;c=e->m_clients.next()) {
791 if (!c->watchingStopped)
792 wasWatching += c->count;
793 else if (!instance || instance == c->instance) {
794 c->watchingStopped = false;
795 newWatching += c->count;
796 }
797 }
798 if (newWatching == 0)
799 return false;
800
801 kdDebug(7001) << instance->name() << " restarted scanning " << e->path
802 << " (now " << wasWatching+newWatching << " watchers)" << endl;
803
804 // restart watching and emit pending events
805
806 int ev = NoChange;
807 if (wasWatching == 0) {
808 if (!notify) {
809 struct stat stat_buf;
810 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
811 if (exists) {
812 e->m_ctime = stat_buf.st_ctime;
813 e->m_status = Normal;
814 e->m_nlink = stat_buf.st_nlink;
815 }
816 else {
817 e->m_ctime = invalid_ctime;
818 e->m_status = NonExistent;
819 e->m_nlink = 0;
820 }
821 }
822 e->msecLeft = 0;
823 ev = scanEntry(e);
824 }
825 emitEvent(e,ev);
826
827 return true;
828}
829
830// instance ==0: stop scanning for all instances
831void KDirWatchPrivate::stopScan(KDirWatch* instance)
832{
833 EntryMap::Iterator it = m_mapEntries.begin();
834 for( ; it != m_mapEntries.end(); ++it )
835 stopEntryScan(instance, &(*it));
836}
837
838
839void KDirWatchPrivate::startScan(KDirWatch* instance,
840 bool notify, bool skippedToo )
841{
842 if (!notify)
843 resetList(instance,skippedToo);
844
845 EntryMap::Iterator it = m_mapEntries.begin();
846 for( ; it != m_mapEntries.end(); ++it )
847 restartEntryScan(instance, &(*it), notify);
848
849 // timer should still be running when in polling mode
850}
851
852
853// clear all pending events, also from stopped
854void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
855 bool skippedToo )
856{
857 EntryMap::Iterator it = m_mapEntries.begin();
858 for( ; it != m_mapEntries.end(); ++it ) {
859
860 Client* c = (*it).m_clients.first();
861 for(;c;c=(*it).m_clients.next())
862 if (!c->watchingStopped || skippedToo)
863 c->pending = NoChange;
864 }
865}
866
867// Return event happened on <e>
868//
869int KDirWatchPrivate::scanEntry(Entry* e)
870{
871#ifdef HAVE_FAM
872 // we do not stat entries using FAM
873 if (e->m_mode == FAMMode) return NoChange;
874#endif
875
876 // Shouldn't happen: Ignore "unknown" notification method
877 if (e->m_mode == UnknownMode) return NoChange;
878
879#ifdef HAVE_DNOTIFY
880 if (e->m_mode == DNotifyMode) {
881 // we know nothing has changed, no need to stat
882 if(!e->dn_dirty) return NoChange;
883 e->dn_dirty = false;
884 }
885#endif
886
887 if (e->m_mode == StatMode) {
888 // only scan if timeout on entry timer happens;
889 // e.g. when using 500msec global timer, a entry
890 // with freq=5000 is only watched every 10th time
891
892 e->msecLeft -= freq;
893 if (e->msecLeft>0) return NoChange;
894 e->msecLeft += e->freq;
895 }
896
897 struct stat stat_buf;
898 bool exists = (stat(QFile::encodeName(e->path), &stat_buf) == 0);
899 if (exists) {
900
901 if (e->m_status == NonExistent) {
902 e->m_ctime = stat_buf.st_ctime;
903 e->m_status = Normal;
904 e->m_nlink = stat_buf.st_nlink;
905 return Created;
906 }
907
908 if ( (e->m_ctime != invalid_ctime) &&
909 ((stat_buf.st_ctime != e->m_ctime) ||
910 // (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
911 (stat_buf.st_nlink != e->m_nlink)) ) {
912 e->m_ctime = stat_buf.st_ctime;
913 e->m_nlink = stat_buf.st_nlink;
914 return Changed;
915 }
916
917 return NoChange;
918 }
919
920 // dir/file doesn't exist
921
922 if (e->m_ctime == invalid_ctime)
923 return NoChange;
924
925 e->m_ctime = invalid_ctime;
926 e->m_nlink = 0;
927 e->m_status = NonExistent;
928
929 return Deleted;
930}
931
932/* Notify all interested KDirWatch instances about a given event on an entry
933 * and stored pending events. When watching is stopped, the event is
934 * added to the pending events.
935 */
936void KDirWatchPrivate::emitEvent(Entry* e, int event, const QString &fileName)
937{
938 QString path = e->path;
939 if (!fileName.isEmpty()) {
940 if (fileName[0] == '/')
941 path = fileName;
942 else
943 path += "/" + fileName;
944 }
945
946 Client* c = e->m_clients.first();
947 for(;c;c=e->m_clients.next()) {
948 if (c->instance==0 || c->count==0) continue;
949
950 if (c->watchingStopped) {
951 // add event to pending...
952 if (event == Changed)
953 c->pending |= event;
954 else if (event == Created || event == Deleted)
955 c->pending = event;
956 continue;
957 }
958 // not stopped
959 if (event == NoChange || event == Changed)
960 event |= c->pending;
961 c->pending = NoChange;
962 if (event == NoChange) continue;
963
964 if (event & Deleted) {
965 c->instance->setDeleted(path);
966 // emit only Deleted event...
967 continue;
968 }
969
970 if (event & Created) {
971 c->instance->setCreated(path);
972 // possible emit Change event after creation
973 }
974
975 if (event & Changed)
976 c->instance->setDirty(path);
977 }
978}
979
980// Remove entries which were marked to be removed
981void KDirWatchPrivate::slotRemoveDelayed()
982{
983 Entry* e;
984 delayRemove = false;
985 for(e=removeList.first();e;e=removeList.next())
986 removeEntry(0, e->path, 0);
987 removeList.clear();
988}
989
990/* Scan all entries to be watched for changes. This is done regularly
991 * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
992 */
993void KDirWatchPrivate::slotRescan()
994{
995 EntryMap::Iterator it;
996
997 // People can do very long things in the slot connected to dirty(),
998 // like showing a message box. We don't want to keep polling during
999 // that time, otherwise the value of 'delayRemove' will be reset.
1000 bool timerRunning = timer->isActive();
1001 if ( timerRunning )
1002 timer->stop();
1003
1004 // We delay deletions of entries this way.
1005 // removeDir(), when called in slotDirty(), can cause a crash otherwise
1006 delayRemove = true;
1007
1008#ifdef HAVE_DNOTIFY
1009 QPtrList<Entry> dList, cList;
1010
1011 // for DNotify method,
1012 if (rescan_all)
1013 {
1014 // mark all as dirty
1015 it = m_mapEntries.begin();
1016 for( ; it != m_mapEntries.end(); ++it )
1017 (*it).dn_dirty = true;
1018 rescan_all = false;
1019 }
1020 else
1021 {
1022 // progate dirty flag to dependant entries (e.g. file watches)
1023 it = m_mapEntries.begin();
1024 for( ; it != m_mapEntries.end(); ++it )
1025 if ( ((*it).m_mode == DNotifyMode) && (*it).dn_dirty )
1026 (*it).propagate_dirty();
1027 }
1028
1029#endif
1030
1031 it = m_mapEntries.begin();
1032 for( ; it != m_mapEntries.end(); ++it ) {
1033 // we don't check invalid entries (i.e. remove delayed)
1034 if (!(*it).isValid()) continue;
1035
1036 int ev = scanEntry( &(*it) );
1037
1038#ifdef HAVE_DNOTIFY
1039 if ((*it).m_mode == DNotifyMode) {
1040 if ((*it).isDir && (ev == Deleted)) {
1041 dList.append( &(*it) );
1042
1043 // must close the FD.
1044 if ((*it).dn_fd) {
1045 ::close((*it).dn_fd);
1046 fd_Entry.remove((*it).dn_fd);
1047 (*it).dn_fd = 0;
1048 }
1049 }
1050
1051 else if ((*it).isDir && (ev == Created)) {
1052 // For created, but yet without DNOTIFYing ...
1053 if ( (*it).dn_fd == 0) {
1054 cList.append( &(*it) );
1055 if (! useDNotify( &(*it) )) {
1056 // if DNotify setup fails...
1057 useStat( &(*it) );
1058 }
1059 }
1060 }
1061 }
1062#endif
1063
1064 if ( ev != NoChange )
1065 emitEvent( &(*it), ev);
1066 }
1067
1068
1069#ifdef HAVE_DNOTIFY
1070 // Scan parent of deleted directories for new creation
1071 Entry* e;
1072 for(e=dList.first();e;e=dList.next())
1073 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
1074
1075 // Remove watch of parent of new created directories
1076 for(e=cList.first();e;e=cList.next())
1077 removeEntry(0, QDir::cleanDirPath( e->path+"/.."), e);
1078#endif
1079
1080 if ( timerRunning )
1081 timer->start(freq);
1082
1083 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
1084}
1085
1086#ifdef HAVE_FAM
1087void KDirWatchPrivate::famEventReceived()
1088{
1089 static FAMEvent fe;
1090
1091 delayRemove = true;
1092
1093 while(use_fam && FAMPending(&fc)) {
1094 if (FAMNextEvent(&fc, &fe) == -1) {
1095 kdWarning(7001) << "FAM connection problem, switching to polling."
1096 << endl;
1097 use_fam = false;
1098 delete sn; sn = 0;
1099
1100 // Replace all FAMMode entries with DNotify/Stat
1101 EntryMap::Iterator it;
1102 it = m_mapEntries.begin();
1103 for( ; it != m_mapEntries.end(); ++it )
1104 if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
1105#ifdef HAVE_DNOTIFY
1106 if (useDNotify( &(*it) )) continue;
1107#endif
1108 useStat( &(*it) );
1109 }
1110 }
1111 else
1112 checkFAMEvent(&fe);
1113 }
1114
1115 QTimer::singleShot(0, this, SLOT(slotRemoveDelayed()));
1116}
1117
1118void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
1119{
1120 // Don't be too verbose ;-)
1121 if ((fe->code == FAMExists) ||
1122 (fe->code == FAMEndExist) ||
1123 (fe->code == FAMAcknowledge)) return;
1124
1125 // $HOME/.X.err grows with debug output, so don't notify change
1126 if ( *(fe->filename) == '.') {
1127 if (strncmp(fe->filename, ".X.err", 6) == 0) return;
1128 if (strncmp(fe->filename, ".xsession-errors", 16) == 0) return;
1129 }
1130
1131 Entry* e = 0;
1132 EntryMap::Iterator it = m_mapEntries.begin();
1133 for( ; it != m_mapEntries.end(); ++it )
1134 if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
1135 FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
1136 e = &(*it);
1137 break;
1138 }
1139
1140 // Entry* e = static_cast<Entry*>(fe->userdata);
1141
1142 kdDebug(7001) << "Processing FAM event ("
1143 << ((fe->code == FAMChanged) ? "FAMChanged" :
1144 (fe->code == FAMDeleted) ? "FAMDeleted" :
1145 (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
1146 (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
1147 (fe->code == FAMCreated) ? "FAMCreated" :
1148 (fe->code == FAMMoved) ? "FAMMoved" :
1149 (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
1150 (fe->code == FAMExists) ? "FAMExists" :
1151 (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
1152 << ", " << fe->filename
1153 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
1154 << ")" << endl;
1155
1156 if (!e) {
1157 // this happens e.g. for FAMAcknowledge after deleting a dir...
1158 // kdDebug(7001) << "No entry for FAM event ?!" << endl;
1159 return;
1160 }
1161
1162 if (e->m_status == NonExistent) {
1163 kdDebug(7001) << "FAM event for nonExistent entry " << e->path << endl;
1164 return;
1165 }
1166
1167 if (e->isDir)
1168 switch (fe->code)
1169 {
1170 case FAMDeleted:
1171 // file absolute: watched dir
1172 if (fe->filename[0] == '/')
1173 {
1174 // a watched directory was deleted
1175
1176 e->m_status = NonExistent;
1177 FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
1178 kdDebug(7001) << "Cancelled FAMReq "
1179 << FAMREQUEST_GETREQNUM(&(e->fr))
1180 << " for " << e->path << endl;
1181 // Scan parent for a new creation
1182 addEntry(0, QDir::cleanDirPath( e->path+"/.."), e, true);
1183 }
1184 emitEvent(e, Deleted, QFile::decodeName(fe->filename));
1185 break;
1186
1187 case FAMCreated: {
1188 // check for creation of a directory we have to watch
1189 Entry *sub_entry = e->m_entries.first();
1190 for(;sub_entry; sub_entry = e->m_entries.next())
1191 if (sub_entry->path == e->path + "/" + fe->filename) break;
1192 if (sub_entry && sub_entry->isDir) {
1193 QString path = e->path;
1194 removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
1195 sub_entry->m_status = Normal;
1196 if (!useFAM(sub_entry))
1197 useStat(sub_entry);
1198
1199 emitEvent(sub_entry, Created);
1200 }
1201 else emitEvent(e, Created, QFile::decodeName(fe->filename));
1202 break;
1203 }
1204
1205 case FAMChanged:
1206 emitEvent(e, Changed, QFile::decodeName(fe->filename));
1207
1208 default:
1209 break;
1210 }
1211 else switch (fe->code)
1212 {
1213 case FAMCreated: emitEvent(e, Created);
1214 break;
1215 case FAMDeleted: emitEvent(e, Deleted);
1216 break;
1217 case FAMChanged: emitEvent(e, Changed);
1218 break;
1219 default: break;
1220 }
1221}
1222#else
1223void KDirWatchPrivate::famEventReceived() {}
1224#endif
1225
1226
1227void KDirWatchPrivate::statistics()
1228{
1229 EntryMap::Iterator it;
1230
1231 kdDebug(7001) << "Entries watched:" << endl;
1232 if (m_mapEntries.count()==0) {
1233 kdDebug(7001) << " None." << endl;
1234 }
1235 else {
1236 it = m_mapEntries.begin();
1237 for( ; it != m_mapEntries.end(); ++it ) {
1238 Entry* e = &(*it);
1239 kdDebug(7001) << " " << e->path << " ("
1240 << ((e->m_status==Normal)?"":"Nonexistent ")
1241 << (e->isDir ? "Dir":"File") << ", using "
1242 << ((e->m_mode == FAMMode) ? "FAM" :
1243 (e->m_mode == DNotifyMode) ? "DNotify" :
1244 (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
1245 << ")" << endl;
1246
1247 Client* c = e->m_clients.first();
1248 for(;c; c = e->m_clients.next()) {
1249 QString pending;
1250 if (c->watchingStopped) {
1251 if (c->pending & Deleted) pending += "deleted ";
1252 if (c->pending & Created) pending += "created ";
1253 if (c->pending & Changed) pending += "changed ";
1254 if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
1255 pending = ", stopped" + pending;
1256 }
1257 kdDebug(7001) << " by " << c->instance->name()
1258 << " (" << c->count << " times)"
1259 << pending << endl;
1260 }
1261 if (e->m_entries.count()>0) {
1262 kdDebug(7001) << " dependent entries:" << endl;
1263 Entry* d = e->m_entries.first();
1264 for(;d; d = e->m_entries.next()) {
1265 kdDebug(7001) << " " << d->path << endl;
1266 }
1267 }
1268 }
1269 }
1270}
1271
1272
1273//
1274// Class KDirWatch
1275//
1276
1277static KStaticDeleter<KDirWatch> sd_dw;
1278KDirWatch* KDirWatch::s_pSelf = 0L;
1279
1280KDirWatch* KDirWatch::self()
1281{
1282 if ( !s_pSelf ) {
1283//US sd_dw.setObject( s_pSelf, new KDirWatch );
1284 s_pSelf = sd_dw.setObject( new KDirWatch );
1285 }
1286
1287 return s_pSelf;
1288}
1289
1290bool KDirWatch::exists()
1291{
1292 return s_pSelf != 0;
1293}
1294
1295KDirWatch::KDirWatch (QObject* parent, const char* name)
1296 : QObject(parent,name)
1297{
1298 if (!name) {
1299 static int nameCounter = 0;
1300
1301 nameCounter++;
1302 setName(QString("KDirWatch-%1").arg(nameCounter).ascii());
1303 }
1304
1305 if (!dwp_self)
1306 dwp_self = new KDirWatchPrivate;
1307 d = dwp_self;
1308 d->ref();
1309
1310 _isStopped = false;
1311}
1312
1313KDirWatch::~KDirWatch()
1314{
1315 if (d) d->removeEntries(this);
1316 if ( d->deref() )
1317 {
1318 // delete it if it's the last one
1319 delete d;
1320 dwp_self = 0L;
1321 }
1322}
1323
1324
1325// TODO: add watchFiles/recursive support
1326void KDirWatch::addDir( const QString& _path,
1327 bool watchFiles, bool recursive)
1328{
1329 if (watchFiles || recursive) {
1330 kdDebug(7001) << "addDir - recursive/watchFiles not supported in KDE 3.0"
1331 << endl;
1332 }
1333 if (d) d->addEntry(this, _path, 0, true);
1334}
1335
1336void KDirWatch::addFile( const QString& _path )
1337{
1338 if (d) d->addEntry(this, _path, 0, false);
1339}
1340
1341QDateTime KDirWatch::ctime( const QString &_path )
1342{
1343 KDirWatchPrivate::Entry* e = d->entry(_path);
1344
1345 if (!e)
1346 return QDateTime();
1347
1348 QDateTime result;
1349 result.setTime_t(e->m_ctime);
1350 return result;
1351}
1352
1353void KDirWatch::removeDir( const QString& _path )
1354{
1355 if (d) d->removeEntry(this, _path, 0);
1356}
1357
1358void KDirWatch::removeFile( const QString& _path )
1359{
1360 if (d) d->removeEntry(this, _path, 0);
1361}
1362
1363bool KDirWatch::stopDirScan( const QString& _path )
1364{
1365 if (d) {
1366 KDirWatchPrivate::Entry *e = d->entry(_path);
1367 if (e && e->isDir) return d->stopEntryScan(this, e);
1368 }
1369 return false;
1370}
1371
1372bool KDirWatch::restartDirScan( const QString& _path )
1373{
1374 if (d) {
1375 KDirWatchPrivate::Entry *e = d->entry(_path);
1376 if (e && e->isDir)
1377 // restart without notifying pending events
1378 return d->restartEntryScan(this, e, false);
1379 }
1380 return false;
1381}
1382
1383void KDirWatch::stopScan()
1384{
1385 if (d) d->stopScan(this);
1386 _isStopped = true;
1387}
1388
1389void KDirWatch::startScan( bool notify, bool skippedToo )
1390{
1391 _isStopped = false;
1392 if (d) d->startScan(this, notify, skippedToo);
1393}
1394
1395
1396bool KDirWatch::contains( const QString& _path ) const
1397{
1398 KDirWatchPrivate::Entry* e = d->entry(_path);
1399 if (!e)
1400 return false;
1401
1402 KDirWatchPrivate::Client* c = e->m_clients.first();
1403 for(;c;c=e->m_clients.next())
1404 if (c->instance == this) return true;
1405
1406 return false;
1407}
1408
1409void KDirWatch::statistics()
1410{
1411 if (!dwp_self) {
1412 kdDebug(7001) << "KDirWatch not used" << endl;
1413 return;
1414 }
1415 dwp_self->statistics();
1416}
1417
1418
1419void KDirWatch::setCreated( const QString & _file )
1420{
1421 kdDebug(7001) << name() << " emitting created " << _file << endl;
1422 emit created( _file );
1423}
1424
1425void KDirWatch::setDirty( const QString & _file )
1426{
1427 kdDebug(7001) << name() << " emitting dirty " << _file << endl;
1428 emit dirty( _file );
1429}
1430
1431void KDirWatch::setDeleted( const QString & _file )
1432{
1433 kdDebug(7001) << name() << " emitting deleted " << _file << endl;
1434 emit deleted( _file );
1435}
1436
1437//US #include "kdirwatch.moc"
1438//US #include "kdirwatch_p.moc"
1439
1440//sven
1441
1442// vim: sw=2 ts=8 et
diff --git a/microkde/kio/kio/kdirwatch.h b/microkde/kio/kio/kdirwatch.h
new file mode 100644
index 0000000..bee30c2
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch.h
@@ -0,0 +1,288 @@
1/* This file is part of the KDE libraries
2 Copyright (C) 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 Boston, MA 02111-1307, USA.
17*/
18
19/*
20Enhanced Version of the file for platform independent KDE tools.
21Copyright (c) 2004 Ulf Schenk
22
23$Id$
24*/
25
26#ifndef _KDIRWATCH_H
27#define _KDIRWATCH_H
28
29#include <qtimer.h>
30#include <qdatetime.h>
31#include <qmap.h>
32
33#define kdirwatch KDirWatch::self()
34
35class KDirWatchPrivate;
36
37 /**
38 * Watch directories and files for changes.
39 * The watched directories or files don't have to exist yet.
40 *
41 * When a watched directory is changed, i.e. when files therein are
42 * created or deleted, KDirWatch will emit the signal @ref dirty().
43 *
44 * When a watched, but previously not existing directory gets created,
45 * KDirWatch will emit the signal @ref created().
46 *
47 * When a watched directory gets deleted, KDirWatch will emit the
48 * signal @ref deleted(). The directory is still watched for new
49 * creation.
50 *
51 * When a watched file is changed, i.e. attributes changed or written
52 * to, KDirWatch will emit the signal @ref dirty().
53 *
54 * Scanning of particular directories or files can be stopped temporarily
55 * and restarted. The whole class can be stopped and restarted.
56 * Directories and files can be added/removed from the list in any state.
57 *
58 * The implementation uses the FAM service when available;
59 * if FAM is not available, the DNOTIFY functionality is used on LINUX.
60 * As a last resort, a regular polling for change of modification times
61 * is done; the polling interval is a global config option:
62 * DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted
63 * directories.
64 *
65 * @see self()
66 * @short Class for watching directory and file changes.
67 * @author Sven Radej <sven@lisa.exp.univie.ac.at>
68 */
69class KDirWatch : public QObject
70{
71 Q_OBJECT
72
73 public:
74 /**
75 * Constructor.
76 *
77 * Scanning begins immediatly when a dir/file watch
78 * is added.
79 * @param parent the parent of the QObject (or 0 for parent-less KDataTools)
80 * @param name the name of the QObject, can be 0
81 */
82 KDirWatch (QObject* parent = 0, const char* name = 0);
83
84 /**
85 * Destructor.
86 *
87 * Stops scanning and cleans up.
88 */
89 ~KDirWatch();
90
91 /**
92 * Adds a directory to be watched.
93 *
94 * The directory does not have to exist. When @p watchFiles is
95 * false (the default), the signals dirty(), created(), deleted()
96 * can be emitted, all for the watched directory.
97 * When @p watchFiles is true, all files in the watched directory
98 * are watched for changes, too. Thus, the signals dirty(),
99 * created(), deleted() can be emitted.
100 *
101 * @param path the path to watch
102 * @param watchFiles if true, the KDirWatch will also watch files
103 * @param recursive if true, all sub directories are also watched
104 */
105 void addDir(const QString& path,
106 bool watchFiles = false, bool recursive = false);
107
108 /**
109 * Adds a file to be watched.
110 * @param file the file to watch
111 */
112 void addFile(const QString& file);
113
114 /**
115 * Returns the time the directory/file was last changed.
116 * @param file the file to check
117 * @return the date of the last modification
118 */
119 QDateTime ctime(const QString& path);
120
121 /**
122 * Removes a directory from the list of scanned directories.
123 *
124 * If specified path is not in the list this does nothing.
125 * @param dir the path of the dir to be removed from the list
126 */
127 void removeDir(const QString& path);
128
129 /**
130 * Removes a file from the list of watched files.
131 *
132 * If specified path is not in the list this does nothing.
133 * @param file the file to be removed from the list
134 */
135 void removeFile(const QString& file);
136
137 /**
138 * Stops scanning the specified path.
139 *
140 * The @p path is not deleted from the interal just, it is just skipped.
141 * Call this function when you perform an huge operation
142 * on this directory (copy/move big files or many files). When finished,
143 * call @ref restartDirScan(path).
144 *
145 * @param path the path to skip
146 * @return true if the @p path is being watched, otherwise false
147 * @see restartDirScanning()
148 */
149 bool stopDirScan(const QString& path);
150
151 /**
152 * Restarts scanning for specified path.
153 *
154 * Resets ctime. It doesn't notify
155 * the change (by emitted a signal), since the ctime value is reset.
156 *
157 * Call it when you are finished with big operations on that path,
158 * @em and when @em you have refreshed that path.
159 *
160 * @param path the path to restart scanning
161 * @return true if the @p path is being watched, otherwise false
162 * @see stopDirScanning()
163 */
164 bool restartDirScan(const QString& path);
165
166 /**
167 * Starts scanning of all dirs in list.
168 *
169 * @param notify If true, all changed directories (since @ref
170 * stopScan() call) will be notified for refresh. If notify is
171 * false, all ctimes will be reset (except those who are stopped,
172 * but only if @p skippedToo is false) and changed dirs won't be
173 * notified. You can start scanning even if the list is
174 * empty. First call should be called with @p false or else all
175 * directories
176 * in list will be notified.
177 * @param skippedToo if true, the skipped directoris (scanning of which was
178 * stopped with @ref stopDirScan() ) will be reset and notified
179 * for change. Otherwise, stopped directories will continue to be
180 * unnotified.
181 */
182 void startScan( bool notify=false, bool skippedToo=false );
183
184 /**
185 * Stops scanning of all directories in internal list.
186 *
187 * The timer is stopped, but the list is not cleared.
188 */
189 void stopScan();
190
191 /**
192 * Is scanning stopped?
193 * After creation of a KDirWatch instance, this is false.
194 * @return true when scanning stopped
195 */
196 bool isStopped() { return _isStopped; }
197
198 /**
199 * Check if a directory is being watched by this KDirWatch instance
200 * @param path the directory to check
201 * @return true if the directory is being watched
202 */
203 bool contains( const QString& path ) const;
204
205 /**
206 * Dump statistic information about all KDirWatch instances.
207 * This checks for consistency, too.
208 */
209 static void statistics();
210
211 /**
212 * Emits @ref created().
213 * @param path the path of the file or directory
214 */
215 void setCreated( const QString &path );
216 /**
217 * Emits @ref dirty().
218 * @param path the path of the file or directory
219 */
220 void setDirty( const QString &path );
221 /**
222 * Emits @ref deleted().
223 * @param path the path of the file or directory
224 */
225 void setDeleted( const QString &path );
226
227 /**
228 * The KDirWatch instance usually globally used in an application.
229 * It is automatically deleted when the application exits.
230 *
231 * However, you can create an arbitrary number of KDirWatch instances
232 * aside from this one - for those you have to take care of memory management.
233 *
234 * This function returns an instance of KDirWatch. If there is none, it
235 * will be created.
236 *
237 * @return a KDirWatch instance
238 */
239 static KDirWatch* self();
240 /**
241 * Returns true if there is an instance of KDirWatch.
242 * @return true if there is an instance of KDirWatch.
243 * @see KDirWatch::self()
244 * @since 3.1
245 */
246 static bool exists();
247
248 signals:
249
250 /**
251 * Emitted when a watched object is changed.
252 * For a directory this signal is emitted when files
253 * therein are created or deleted.
254 * For a file this signal is emitted when its size or attributes change.
255 *
256 * When you watch a directory, changes in the size or attributes of
257 * contained files may or may not trigger this signal to be emitted
258 * depending on which backend is used by KDirWatch.
259 *
260 * The new ctime is set before the signal is emitted.
261 * @param path the path of the file or directory
262 */
263 void dirty (const QString &path);
264
265 /**
266 * Emitted when a file or directory is created.
267 * @param path the path of the file or directory
268 */
269 void created (const QString &path );
270
271 /**
272 * Emitted when a file or directory is deleted.
273 *
274 * The object is still watched for new creation.
275 * @param path the path of the file or directory
276 */
277 void deleted (const QString &path );
278
279 private:
280 bool _isStopped;
281
282 KDirWatchPrivate *d;
283 static KDirWatch* s_pSelf;
284};
285
286#endif
287
288// vim: sw=3 et
diff --git a/microkde/kio/kio/kdirwatch_p.h b/microkde/kio/kio/kdirwatch_p.h
new file mode 100644
index 0000000..0ab482f
--- a/dev/null
+++ b/microkde/kio/kio/kdirwatch_p.h
@@ -0,0 +1,153 @@
1/* Private Header for class of KDirWatchPrivate
2 *
3 * this separate header file is needed for MOC processing
4 * because KDirWatchPrivate has signals and slots
5 */
6
7/*
8Enhanced Version of the file for platform independent KDE tools.
9Copyright (c) 2004 Ulf Schenk
10
11$Id$
12*/
13
14#ifndef _KDIRWATCH_P_H
15#define _KDIRWATCH_P_H
16
17#ifdef HAVE_FAM
18#include <fam.h>
19#endif
20
21#include <qptrlist.h>
22
23#include <kdirwatch.h>
24
25#include <ctime>
26
27#define invalid_ctime ((time_t)-1)
28
29/* KDirWatchPrivate is a singleton and does the watching
30 * for every KDirWatch instance in the application.
31 */
32class KDirWatchPrivate : public QObject
33{
34 Q_OBJECT
35public:
36
37 enum entryStatus { Normal = 0, NonExistent };
38 enum entryMode { UnknownMode = 0, StatMode, DNotifyMode, FAMMode };
39 enum { NoChange=0, Changed=1, Created=2, Deleted=4 };
40
41 struct Client {
42 KDirWatch* instance;
43 int count;
44 // did the instance stop watching
45 bool watchingStopped;
46 // events blocked when stopped
47 int pending;
48 };
49
50 class Entry
51 {
52 public:
53 // the last observed modification time
54 time_t m_ctime;
55 // the last observed link count
56 int m_nlink;
57 entryStatus m_status;
58 entryMode m_mode;
59 bool isDir;
60 // instances interested in events
61 QPtrList<Client> m_clients;
62 // nonexistent entries of this directory
63 QPtrList<Entry> m_entries;
64 QString path;
65
66 int msecLeft, freq;
67
68 void addClient(KDirWatch*);
69 void removeClient(KDirWatch*);
70 int clients();
71 bool isValid() { return m_clients.count() || m_entries.count(); }
72
73#ifdef HAVE_FAM
74 FAMRequest fr;
75#endif
76
77#ifdef HAVE_DNOTIFY
78 int dn_fd;
79 bool dn_dirty;
80 void propagate_dirty();
81#endif
82 };
83
84 typedef QMap<QString,Entry> EntryMap;
85
86 KDirWatchPrivate();
87 ~KDirWatchPrivate();
88
89 void resetList (KDirWatch*,bool);
90 void useFreq(Entry* e, int newFreq);
91 void addEntry(KDirWatch*,const QString&, Entry*, bool);
92 void removeEntry(KDirWatch*,const QString&, Entry*);
93 bool stopEntryScan(KDirWatch*, Entry*);
94 bool restartEntryScan(KDirWatch*, Entry*, bool );
95 void stopScan(KDirWatch*);
96 void startScan(KDirWatch*, bool, bool);
97
98 void removeEntries(KDirWatch*);
99 void statistics();
100
101 Entry* entry(const QString&);
102 int scanEntry(Entry* e);
103 void emitEvent(Entry* e, int event, const QString &fileName = QString::null);
104
105 // Memory management - delete when last KDirWatch gets deleted
106 void ref() { m_ref++; }
107 bool deref() { return ( --m_ref == 0 ); }
108
109public slots:
110 void slotRescan();
111 void famEventReceived(); // for FAM
112 void slotActivated(); // for DNOTIFY
113 void slotRemoveDelayed();
114
115public:
116 QTimer *timer;
117 EntryMap m_mapEntries;
118
119private:
120 int freq;
121 int statEntries;
122 int m_nfsPollInterval, m_PollInterval;
123 int m_ref;
124 bool useStat(Entry*);
125
126 bool delayRemove;
127 QPtrList<Entry> removeList;
128
129#ifdef HAVE_FAM
130 QSocketNotifier *sn;
131 FAMConnection fc;
132 bool use_fam;
133
134 void checkFAMEvent(FAMEvent*);
135 bool useFAM(Entry*);
136#endif
137
138#ifdef HAVE_DNOTIFY
139 bool supports_dnotify;
140 bool rescan_all;
141 int mPipe[2];
142 QTimer mTimer;
143 QSocketNotifier *mSn;
144 QIntDict<Entry> fd_Entry;
145
146 static void dnotify_handler(int, siginfo_t *si, void *);
147 static void dnotify_sigio_handler(int, siginfo_t *si, void *);
148 bool useDNotify(Entry*);
149#endif
150};
151
152#endif // KDIRWATCH_P_H
153