• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdeio/tdeio
 

tdeio/tdeio

  • tdeio
  • tdeio
kdirwatch.cpp
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., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*/
18
19
20// CHANGES:
21// Oct 4, 2005 - Inotify support (Dirk Mueller)
22// Februar 2002 - Add file watching and remote mount check for STAT
23// Mar 30, 2001 - Native support for Linux dir change notification.
24// Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de)
25// May 24. 1998 - List of times introduced, and some bugs are fixed. (sven)1
26// May 23. 1998 - Removed static pointer - you can have more instances.
27// It was Needed for KRegistry. KDirWatch now emits signals and doesn't
28// call (or need) KFM. No more URL's - just plain paths. (sven)
29// Mar 29. 1998 - added docs, stop/restart for particular Dirs and
30// deep copies for list of dirs. (sven)
31// Mar 28. 1998 - Created. (sven)
32
33
34#include <config.h>
35#include <errno.h>
36
37#ifdef HAVE_DNOTIFY
38#include <unistd.h>
39#include <time.h>
40#include <fcntl.h>
41#include <signal.h>
42#include <errno.h>
43#endif
44
45
46#include <sys/stat.h>
47#include <assert.h>
48#include <tqdir.h>
49#include <tqfile.h>
50#include <tqintdict.h>
51#include <tqptrlist.h>
52#include <tqsocketnotifier.h>
53#include <tqstringlist.h>
54#include <tqtimer.h>
55
56#include <tdeapplication.h>
57#include <kdebug.h>
58#include <tdeconfig.h>
59#include <tdeglobal.h>
60#include <kstaticdeleter.h>
61#include <kde_file.h>
62#include <kurl.h>
63
64// debug
65#include <sys/ioctl.h>
66
67#ifdef Q_OS_SOLARIS
68#include <sys/filio.h> /* FIONREAD */
69#endif /* solaris */
70
71#ifdef HAVE_INOTIFY
72#include <unistd.h>
73#include <fcntl.h>
74#include <sys/syscall.h>
75#ifdef Q_OS_LINUX
76#include <linux/types.h>
77#endif /* Linux */
78// Linux kernel headers are documented to not compile
79#define _S390_BITOPS_H
80#include <sys/inotify.h>
81
82#ifndef __NR_inotify_init
83#if defined(__i386__)
84#define __NR_inotify_init 291
85#define __NR_inotify_add_watch 292
86#define __NR_inotify_rm_watch 293
87#endif
88#if defined(__PPC__)
89#define __NR_inotify_init 275
90#define __NR_inotify_add_watch 276
91#define __NR_inotify_rm_watch 277
92#endif
93#if defined(__x86_64__)
94#define __NR_inotify_init 253
95#define __NR_inotify_add_watch 254
96#define __NR_inotify_rm_watch 255
97#endif
98#endif
99
100#ifndef IN_ONLYDIR
101#define IN_ONLYDIR 0x01000000
102#endif
103
104#ifndef IN_DONT_FOLLOW
105#define IN_DONT_FOLLOW 0x02000000
106#endif
107
108#ifndef IN_MOVE_SELF
109#define IN_MOVE_SELF 0x00000800
110#endif
111
112#endif
113
114#include <sys/utsname.h>
115
116#include "kdirwatch.h"
117#include "kdirwatch_p.h"
118#include "global.h" // TDEIO::probably_slow_mounted
119
120#define NO_NOTIFY (time_t) 0
121
122static KDirWatchPrivate* dwp_self = 0;
123
124#ifdef HAVE_DNOTIFY
125
126static int dnotify_signal = 0;
127
128/* DNOTIFY signal handler
129 *
130 * As this is called asynchronously, only a flag is set and
131 * a rescan is requested.
132 * This is done by writing into a pipe to trigger a TQSocketNotifier
133 * watching on this pipe: a timer is started and after a timeout,
134 * the rescan is done.
135 */
136void KDirWatchPrivate::dnotify_handler(int, siginfo_t *si, void *)
137{
138 if (!dwp_self) return;
139
140 // write might change errno, we have to save it and restore it
141 // (Richard Stevens, Advanced programming in the Unix Environment)
142 int saved_errno = errno;
143
144 Entry* e = dwp_self->fd_Entry.find(si->si_fd);
145
146// kdDebug(7001) << "DNOTIFY Handler: fd " << si->si_fd << " path "
147// << TQString(e ? e->path:"unknown") << endl;
148
149 if(e && e->dn_fd == si->si_fd)
150 e->dirty = true;
151
152 char c = 0;
153 write(dwp_self->mPipe[1], &c, 1);
154 errno = saved_errno;
155}
156
157static struct sigaction old_sigio_act;
158/* DNOTIFY SIGIO signal handler
159 *
160 * When the kernel queue for the dnotify_signal overflows, a SIGIO is send.
161 */
162void KDirWatchPrivate::dnotify_sigio_handler(int sig, siginfo_t *si, void *p)
163{
164 if (dwp_self)
165 {
166 // write might change errno, we have to save it and restore it
167 // (Richard Stevens, Advanced programming in the Unix Environment)
168 int saved_errno = errno;
169
170 dwp_self->rescan_all = true;
171 char c = 0;
172 write(dwp_self->mPipe[1], &c, 1);
173
174 errno = saved_errno;
175 }
176
177 // Call previous signal handler
178 if (old_sigio_act.sa_flags & SA_SIGINFO)
179 {
180 if (old_sigio_act.sa_sigaction)
181 (*old_sigio_act.sa_sigaction)(sig, si, p);
182 }
183 else
184 {
185 if ((old_sigio_act.sa_handler != SIG_DFL) &&
186 (old_sigio_act.sa_handler != SIG_IGN))
187 (*old_sigio_act.sa_handler)(sig);
188 }
189}
190#endif
191
192
193//
194// Class KDirWatchPrivate (singleton)
195//
196
197/* All entries (files/directories) to be watched in the
198 * application (coming from multiple KDirWatch instances)
199 * are registered in a single KDirWatchPrivate instance.
200 *
201 * At the moment, the following methods for file watching
202 * are supported:
203 * - Polling: All files to be watched are polled regularly
204 * using stat (more precise: TQFileInfo.lastModified()).
205 * The polling frequency is determined from global tdeconfig
206 * settings, defaulting to 500 ms for local directories
207 * and 5000 ms for remote mounts
208 * - FAM (File Alteration Monitor): first used on IRIX, SGI
209 * has ported this method to LINUX. It uses a kernel part
210 * (IMON, sending change events to /dev/imon) and a user
211 * level damon (fam), to which applications connect for
212 * notification of file changes. For NFS, the fam damon
213 * on the NFS server machine is used; if IMON is not built
214 * into the kernel, fam uses polling for local files.
215 * - DNOTIFY: In late LINUX 2.3.x, directory notification was
216 * introduced. By opening a directory, you can request for
217 * UNIX signals to be sent to the process when a directory
218 * is changed.
219 * - INOTIFY: In LINUX 2.6.13, inode change notification was
220 * introduced. You're now able to watch arbitrary inode's
221 * for changes, and even get notification when they're
222 * unmounted.
223 */
224
225KDirWatchPrivate::KDirWatchPrivate()
226 : rescan_timer(0, "KDirWatchPrivate::rescan_timer")
227{
228 timer = new TQTimer(this, "KDirWatchPrivate::timer");
229 connect (timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotRescan()));
230 freq = 3600000; // 1 hour as upper bound
231 statEntries = 0;
232 delayRemove = false;
233 m_ref = 0;
234
235 TDEConfigGroup config(TDEGlobal::config(), TQCString("DirWatch"));
236 m_nfsPollInterval = config.readNumEntry("NFSPollInterval", 5000);
237 m_PollInterval = config.readNumEntry("PollInterval", 500);
238
239 TQString available("Stat");
240
241 // used for FAM and DNOTIFY
242 rescan_all = false;
243 connect(&rescan_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotRescan()));
244
245#ifdef HAVE_FAM
246 // It's possible that FAM server can't be started
247 if (FAMOpen(&fc) ==0) {
248 available += ", FAM";
249 use_fam=true;
250 sn = new TQSocketNotifier( FAMCONNECTION_GETFD(&fc),
251 TQSocketNotifier::Read, this);
252 connect( sn, TQ_SIGNAL(activated(int)),
253 this, TQ_SLOT(famEventReceived()) );
254 }
255 else {
256 kdDebug(7001) << "Can't use FAM (fam daemon not running?)" << endl;
257 use_fam=false;
258 }
259#endif
260
261#ifdef HAVE_INOTIFY
262 supports_inotify = true;
263
264 m_inotify_fd = inotify_init();
265
266 if ( m_inotify_fd <= 0 ) {
267 kdDebug(7001) << "Can't use Inotify, kernel doesn't support it" << endl;
268 supports_inotify = false;
269 }
270
271 {
272 struct utsname uts;
273 int major, minor, patch;
274 if (uname(&uts) < 0)
275 supports_inotify = false; // *shrug*
276 else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
277 supports_inotify = false; // *shrug*
278 else if( major * 1000000 + minor * 1000 + patch < 2006014 ) { // <2.6.14
279 kdDebug(7001) << "Can't use INotify, Linux kernel too old" << endl;
280 supports_inotify = false;
281 }
282 }
283
284 if ( supports_inotify ) {
285 available += ", Inotify";
286 fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
287
288 mSn = new TQSocketNotifier( m_inotify_fd, TQSocketNotifier::Read, this );
289 connect( mSn, TQ_SIGNAL(activated( int )), this, TQ_SLOT( slotActivated() ) );
290 }
291#endif
292
293#ifdef HAVE_DNOTIFY
294
295 // if we have inotify, disable dnotify.
296#ifdef HAVE_INOTIFY
297 supports_dnotify = !supports_inotify;
298#else
299 // otherwise, not guilty until proven guilty.
300 supports_dnotify = true;
301#endif
302
303 struct utsname uts;
304 int major, minor, patch;
305 if (uname(&uts) < 0)
306 supports_dnotify = false; // *shrug*
307 else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
308 supports_dnotify = false; // *shrug*
309 else if( major * 1000000 + minor * 1000 + patch < 2004019 ) { // <2.4.19
310 kdDebug(7001) << "Can't use DNotify, Linux kernel too old" << endl;
311 supports_dnotify = false;
312 }
313
314 if( supports_dnotify ) {
315 available += ", DNotify";
316
317 pipe(mPipe);
318 fcntl(mPipe[0], F_SETFD, FD_CLOEXEC);
319 fcntl(mPipe[1], F_SETFD, FD_CLOEXEC);
320 fcntl(mPipe[0], F_SETFL, O_NONBLOCK | fcntl(mPipe[0], F_GETFL));
321 fcntl(mPipe[1], F_SETFL, O_NONBLOCK | fcntl(mPipe[1], F_GETFL));
322 mSn = new TQSocketNotifier( mPipe[0], TQSocketNotifier::Read, this);
323 connect(mSn, TQ_SIGNAL(activated(int)), this, TQ_SLOT(slotActivated()));
324 // Install the signal handler only once
325 if ( dnotify_signal == 0 )
326 {
327 dnotify_signal = SIGRTMIN + 8;
328
329 struct sigaction act;
330 act.sa_sigaction = KDirWatchPrivate::dnotify_handler;
331 sigemptyset(&act.sa_mask);
332 act.sa_flags = SA_SIGINFO;
333#ifdef SA_RESTART
334 act.sa_flags |= SA_RESTART;
335#endif
336 sigaction(dnotify_signal, &act, NULL);
337
338 act.sa_sigaction = KDirWatchPrivate::dnotify_sigio_handler;
339 sigaction(SIGIO, &act, &old_sigio_act);
340 }
341 }
342 else
343 {
344 mPipe[0] = -1;
345 mPipe[1] = -1;
346 }
347#endif
348
349 kdDebug(7001) << "Available methods: " << available << endl;
350}
351
352/* This is called on app exit (KStaticDeleter) */
353KDirWatchPrivate::~KDirWatchPrivate()
354{
355 timer->stop();
356
357 /* remove all entries being watched */
358 removeEntries(0);
359
360#ifdef HAVE_FAM
361 if (use_fam) {
362 FAMClose(&fc);
363 kdDebug(7001) << "KDirWatch deleted (FAM closed)" << endl;
364 }
365#endif
366#ifdef HAVE_INOTIFY
367 if ( supports_inotify )
368 ::close( m_inotify_fd );
369#endif
370#ifdef HAVE_DNOTIFY
371 close(mPipe[0]);
372 close(mPipe[1]);
373#endif
374}
375
376#include <stdlib.h>
377
378void KDirWatchPrivate::slotActivated()
379{
380#ifdef HAVE_DNOTIFY
381 if ( supports_dnotify )
382 {
383 char dummy_buf[4096];
384 read(mPipe[0], &dummy_buf, 4096);
385
386 if (!rescan_timer.isActive())
387 rescan_timer.start(m_PollInterval, true /* singleshot */);
388
389 return;
390 }
391#endif
392
393#ifdef HAVE_INOTIFY
394 if ( !supports_inotify )
395 return;
396
397 int pending = -1;
398 int offset = 0;
399 char buf[4096];
400 assert( m_inotify_fd > -1 );
401 ioctl( m_inotify_fd, FIONREAD, &pending );
402
403 while ( pending > 0 ) {
404
405 if ( pending > (int)sizeof( buf ) )
406 pending = sizeof( buf );
407
408 pending = read( m_inotify_fd, buf, pending);
409
410 while ( pending > 0 ) {
411 struct inotify_event *event = (struct inotify_event *) &buf[offset];
412 pending -= sizeof( struct inotify_event ) + event->len;
413 offset += sizeof( struct inotify_event ) + event->len;
414
415 TQString path;
416 if ( event->len )
417 path = TQFile::decodeName( TQCString( event->name, event->len ) );
418
419 if ( path.length() && isNoisyFile( path.latin1() ) )
420 continue;
421
422 kdDebug(7001) << "ev wd: " << event->wd << " mask " << event->mask << " path: " << path << endl;
423
424 // now we're in deep trouble of finding the
425 // associated entries
426 // for now, we suck and iterate
427 for ( EntryMap::Iterator it = m_mapEntries.begin();
428 it != m_mapEntries.end(); ++it ) {
429 Entry* e = &( *it );
430 if ( e->wd == event->wd ) {
431 e->dirty = true;
432
433 if ( 1 || e->isDir) {
434 if( event->mask & IN_DELETE_SELF) {
435 kdDebug(7001) << "-->got deleteself signal for " << e->path << endl;
436 e->m_status = NonExistent;
437 if (e->isDir)
438 addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
439 else
440 addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
441 }
442 if ( event->mask & IN_IGNORED ) {
443 e->wd = 0;
444 }
445 if ( event->mask & (IN_CREATE|IN_MOVED_TO) ) {
446 Entry *sub_entry = e->m_entries.first();
447 for(;sub_entry; sub_entry = e->m_entries.next())
448 if (sub_entry->path == e->path.path() + "/" + path) break;
449
450 if (sub_entry /*&& sub_entry->isDir*/) {
451 removeEntry(0,e->path.path(), sub_entry);
452 KDE_struct_stat stat_buf;
453 TQCString tpath = TQFile::encodeName(path);
454 KDE_stat(tpath, &stat_buf);
455
456 //sub_entry->isDir = S_ISDIR(stat_buf.st_mode);
457 //sub_entry->m_ctime = stat_buf.st_ctime;
458 //sub_entry->m_status = Normal;
459 //sub_entry->m_nlink = stat_buf.st_nlink;
460
461 if(!useINotify(sub_entry))
462 useStat(sub_entry);
463 sub_entry->dirty = true;
464 }
465 }
466 }
467
468 if (!rescan_timer.isActive())
469 rescan_timer.start(m_PollInterval, true /* singleshot */);
470
471 break; // there really should be only one matching wd
472 }
473 }
474
475 }
476 }
477#endif
478}
479
480/* In DNOTIFY/FAM mode, only entries which are marked dirty are scanned.
481 * We first need to mark all yet nonexistent, but possible created
482 * entries as dirty...
483 */
484void KDirWatchPrivate::Entry::propagate_dirty()
485{
486 for (TQPtrListIterator<Entry> sub_entry (m_entries);
487 sub_entry.current(); ++sub_entry)
488 {
489 if (!sub_entry.current()->dirty)
490 {
491 sub_entry.current()->dirty = true;
492 sub_entry.current()->propagate_dirty();
493 }
494 }
495}
496
497
498/* A KDirWatch instance is interested in getting events for
499 * this file/Dir entry.
500 */
501void KDirWatchPrivate::Entry::addClient(KDirWatch* instance)
502{
503 Client* client = m_clients.first();
504 for(;client; client = m_clients.next())
505 if (client->instance == instance) break;
506
507 if (client) {
508 client->count++;
509 return;
510 }
511
512 client = new Client;
513 client->instance = instance;
514 client->count = 1;
515 client->watchingStopped = instance->isStopped();
516 client->pending = NoChange;
517
518 m_clients.append(client);
519}
520
521void KDirWatchPrivate::Entry::removeClient(KDirWatch* instance)
522{
523 Client* client = m_clients.first();
524 for(;client; client = m_clients.next())
525 if (client->instance == instance) break;
526
527 if (client) {
528 client->count--;
529 if (client->count == 0) {
530 m_clients.removeRef(client);
531 delete client;
532 }
533 }
534}
535
536/* get number of clients */
537int KDirWatchPrivate::Entry::clients()
538{
539 int clients = 0;
540 Client* client = m_clients.first();
541 for(;client; client = m_clients.next())
542 clients += client->count;
543
544 return clients;
545}
546
547
548KDirWatchPrivate::Entry* KDirWatchPrivate::entry(const KURL& _path)
549{
550// we only support absolute paths
551 if (TQDir::isRelativePath(_path.path())) {
552 return 0;
553 }
554
555 TQString path = _path.path();
556
557 if ( path.length() > 1 && path.right(1) == "/" )
558 path.truncate( path.length() - 1 );
559
560 EntryMap::Iterator it = m_mapEntries.find( _path );
561 if ( it == m_mapEntries.end() )
562 return 0;
563 else
564 return &(*it);
565}
566
567// set polling frequency for a entry and adjust global freq if needed
568void KDirWatchPrivate::useFreq(Entry* e, int newFreq)
569{
570 e->freq = newFreq;
571
572 // a reasonable frequency for the global polling timer
573 if (e->freq < freq) {
574 freq = e->freq;
575 if (timer->isActive()) timer->changeInterval(freq);
576 kdDebug(7001) << "Global Poll Freq is now " << freq << " msec" << endl;
577 }
578}
579
580
581#ifdef HAVE_FAM
582// setup FAM notification, returns false if not possible
583bool KDirWatchPrivate::useFAM(Entry* e)
584{
585 if (!use_fam) return false;
586
587 // handle FAM events to avoid deadlock
588 // (FAM sends back all files in a directory when monitoring)
589 famEventReceived();
590
591 e->m_mode = FAMMode;
592 e->dirty = false;
593
594 if (e->isDir) {
595 if (e->m_status == NonExistent) {
596 // If the directory does not exist we watch the parent directory
597 addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
598 }
599 else {
600 int res =FAMMonitorDirectory(&fc, TQFile::encodeName(e->path.path()),
601 &(e->fr), e);
602 if (res<0) {
603 e->m_mode = UnknownMode;
604 use_fam=false;
605 return false;
606 }
607 kdDebug(7001) << " Setup FAM (Req "
608 << FAMREQUEST_GETREQNUM(&(e->fr))
609 << ") for " << e->path.path() << endl;
610 }
611 }
612 else {
613 if (e->m_status == NonExistent) {
614 // If the file does not exist we watch the directory
615 addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
616 }
617 else {
618 int res = FAMMonitorFile(&fc, TQFile::encodeName(e->path.path()),
619 &(e->fr), e);
620 if (res<0) {
621 e->m_mode = UnknownMode;
622 use_fam=false;
623 return false;
624 }
625
626 kdDebug(7001) << " Setup FAM (Req "
627 << FAMREQUEST_GETREQNUM(&(e->fr))
628 << ") for " << e->path.path() << endl;
629 }
630 }
631
632 // handle FAM events to avoid deadlock
633 // (FAM sends back all files in a directory when monitoring)
634 famEventReceived();
635
636 return true;
637}
638#endif
639
640
641#ifdef HAVE_DNOTIFY
642// setup DNotify notification, returns false if not possible
643bool KDirWatchPrivate::useDNotify(Entry* e)
644{
645 e->dn_fd = 0;
646 e->dirty = false;
647 if (!supports_dnotify) return false;
648
649 e->m_mode = DNotifyMode;
650
651 if (e->isDir) {
652 if (e->m_status == Normal) {
653 int fd = KDE_open(TQFile::encodeName(e->path.path()).data(), O_RDONLY);
654 // Migrate fd to somewhere above 128. Some libraries have
655 // constructs like:
656 // fd = socket(...)
657 // if (fd > ARBITRARY_LIMIT)
658 // return error;
659 //
660 // Since programs might end up using a lot of KDirWatch objects
661 // for a rather long time the above braindamage could get
662 // triggered.
663 //
664 // By moving the kdirwatch fd's to > 128, calls like socket() will keep
665 // returning fd's < ARBITRARY_LIMIT for a bit longer.
666 int fd2 = fcntl(fd, F_DUPFD, 128);
667 if (fd2 >= 0)
668 {
669 close(fd);
670 fd = fd2;
671 }
672 if (fd<0) {
673 e->m_mode = UnknownMode;
674 return false;
675 }
676
677 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
678 // if dependant is a file watch, we check for MODIFY & ATTRIB too
679 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
680 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
681
682 if(fcntl(fd, F_SETSIG, dnotify_signal) < 0 ||
683 fcntl(fd, F_NOTIFY, mask) < 0) {
684
685 kdDebug(7001) << "Not using Linux Directory Notifications."
686 << endl;
687 supports_dnotify = false;
688 ::close(fd);
689 e->m_mode = UnknownMode;
690 return false;
691 }
692
693 fd_Entry.replace(fd, e);
694 e->dn_fd = fd;
695
696 kdDebug(7001) << " Setup DNotify (fd " << fd
697 << ") for " << e->path.path() << endl;
698 }
699 else { // NotExisting
700 addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
701 }
702 }
703 else { // File
704 // we always watch the directory (DNOTIFY can't watch files alone)
705 // this notifies us about changes of files therein
706 addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
707 }
708
709 return true;
710}
711#endif
712
713#ifdef HAVE_INOTIFY
714// setup INotify notification, returns false if not possible
715bool KDirWatchPrivate::useINotify( Entry* e )
716{
717 e->wd = 0;
718 e->dirty = false;
719 if (!supports_inotify) return false;
720
721 e->m_mode = INotifyMode;
722
723 int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
724 if(!e->isDir)
725 mask |= IN_MODIFY|IN_ATTRIB;
726 else
727 mask |= IN_ONLYDIR;
728
729 // if dependant is a file watch, we check for MODIFY & ATTRIB too
730 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next()) {
731 if (!dep->isDir) { mask |= IN_MODIFY|IN_ATTRIB; break; }
732 }
733
734 if ( ( e->wd = inotify_add_watch( m_inotify_fd,
735 TQFile::encodeName( e->path.path() ), mask) ) > 0 )
736 return true;
737
738 if ( e->m_status == NonExistent ) {
739 if (e->isDir)
740 addEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e, true);
741 else
742 addEntry(0, TQFileInfo(e->path.path()).dirPath(true), e, true);
743 return true;
744 }
745
746 return false;
747}
748#endif
749
750bool KDirWatchPrivate::useStat(Entry* e)
751{
752 if ( e->path.path().startsWith("/media/") || e->path.path().startsWith("/run/") || (e->path.path() == "/media")
753 || (TDEIO::probably_slow_mounted(e->path.path())) )
754 useFreq(e, m_nfsPollInterval);
755 else
756 useFreq(e, m_PollInterval);
757
758 if (e->m_mode != StatMode) {
759 e->m_mode = StatMode;
760 statEntries++;
761
762 if ( statEntries == 1 ) {
763 // if this was first STAT entry (=timer was stopped)
764 timer->start(freq); // then start the timer
765 kdDebug(7001) << " Started Polling Timer, freq " << freq << endl;
766 }
767 }
768
769 kdDebug(7001) << " Setup Stat (freq " << e->freq
770 << ") for " << e->path.path() << endl;
771
772 return true;
773}
774
775
776/* If <instance> !=0, this KDirWatch instance wants to watch at <_path>,
777 * providing in <isDir> the type of the entry to be watched.
778 * Sometimes, entries are dependant on each other: if <sub_entry> !=0,
779 * this entry needs another entry to watch himself (when notExistent).
780 */
781void KDirWatchPrivate::addEntry(KDirWatch* instance, const KURL& _path,
782 Entry* sub_entry, bool isDir)
783{
784 TQString path = _path.path();
785 if (path.startsWith("/dev/") || (path == "/dev"))
786 return; // Don't even go there.
787
788 if ( path.length() > 1 && path.right(1) == "/" ) {
789 path.truncate( path.length() - 1 );
790 }
791
792 EntryMap::Iterator it = m_mapEntries.find( _path );
793 if ( it != m_mapEntries.end() )
794 {
795 if (sub_entry) {
796 (*it).m_entries.append(sub_entry);
797 kdDebug(7001) << "Added already watched Entry " << path
798 << " (for " << sub_entry->path << ")" << endl;
799
800#ifdef HAVE_DNOTIFY
801 {
802 Entry* e = &(*it);
803 if( (e->m_mode == DNotifyMode) && (e->dn_fd > 0) ) {
804 int mask = DN_DELETE|DN_CREATE|DN_RENAME|DN_MULTISHOT;
805 // if dependant is a file watch, we check for MODIFY & ATTRIB too
806 for(Entry* dep=e->m_entries.first();dep;dep=e->m_entries.next())
807 if (!dep->isDir) { mask |= DN_MODIFY|DN_ATTRIB; break; }
808 if( fcntl(e->dn_fd, F_NOTIFY, mask) < 0) { // shouldn't happen
809 ::close(e->dn_fd);
810 e->m_mode = UnknownMode;
811 fd_Entry.remove(e->dn_fd);
812 e->dn_fd = 0;
813 useStat( e );
814 }
815 }
816 }
817#endif
818
819#ifdef HAVE_INOTIFY
820 {
821 Entry* e = &(*it);
822 if( (e->m_mode == INotifyMode) && (e->wd > 0) ) {
823 int mask = IN_DELETE|IN_DELETE_SELF|IN_CREATE|IN_MOVE|IN_MOVE_SELF|IN_DONT_FOLLOW;
824 if(!e->isDir)
825 mask |= IN_MODIFY|IN_ATTRIB;
826 else
827 mask |= IN_ONLYDIR;
828
829 inotify_rm_watch (m_inotify_fd, e->wd);
830 e->wd = inotify_add_watch( m_inotify_fd, TQFile::encodeName( e->path.path() ), mask);
831 }
832 }
833#endif
834
835 }
836 else {
837 (*it).addClient(instance);
838 kdDebug(7001) << "Added already watched Entry " << path
839 << " (now " << (*it).clients() << " clients)"
840 << TQString(TQString(" [%1]").arg(instance->name())) << endl;
841 }
842 return;
843 }
844
845 // we have a new path to watch
846
847 KDE_struct_stat stat_buf;
848 TQCString tpath = TQFile::encodeName(path);
849 bool exists = (KDE_stat(tpath, &stat_buf) == 0);
850
851 Entry newEntry;
852 m_mapEntries.insert( _path, newEntry );
853 // the insert does a copy, so we have to use <e> now
854 Entry* e = &(m_mapEntries[_path]);
855
856 if (exists) {
857 e->isDir = S_ISDIR(stat_buf.st_mode);
858
859 if (e->isDir && !isDir)
860 kdWarning() << "KDirWatch: " << path << " is a directory. Use addDir!" << endl;
861 else if (!e->isDir && isDir)
862 kdWarning() << "KDirWatch: " << path << " is a file. Use addFile!" << endl;
863
864 e->m_ctime = stat_buf.st_ctime;
865 e->m_mtime = stat_buf.st_mtime;
866 e->m_status = Normal;
867 e->m_nlink = stat_buf.st_nlink;
868 }
869 else {
870 e->isDir = isDir;
871 e->m_ctime = invalid_ctime;
872 e->m_mtime = invalid_mtime;
873 e->m_status = NonExistent;
874 e->m_nlink = 0;
875 }
876
877 e->path = _path;
878 if (sub_entry)
879 e->m_entries.append(sub_entry);
880 else
881 e->addClient(instance);
882
883 kdDebug(7001) << "Added " << (e->isDir ? "Dir ":"File ") << path
884 << (e->m_status == NonExistent ? " NotExisting" : "")
885 << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path.path())) : TQString(""))
886 << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
887 << endl;
888
889
890 // now setup the notification method
891 e->m_mode = UnknownMode;
892 e->msecLeft = 0;
893
894 if ( isNoisyFile( tpath ) ) {
895 return;
896 }
897
898#ifdef HAVE_FAM
899 if (useFAM(e)) return;
900#endif
901
902#ifdef HAVE_INOTIFY
903 if (useINotify(e)) return;
904#endif
905
906#ifdef HAVE_DNOTIFY
907 if (useDNotify(e)) return;
908#endif
909
910 useStat(e);
911}
912
913
914void KDirWatchPrivate::removeEntry( KDirWatch* instance,
915 const KURL& _path, Entry* sub_entry )
916{
917 kdDebug(7001) << "KDirWatchPrivate::removeEntry for '" << _path << "' sub_entry: " << sub_entry << endl;
918 Entry* e = entry(_path);
919 if (!e) {
920 kdDebug(7001) << "KDirWatchPrivate::removeEntry can't handle '" << _path << "'" << endl;
921 return;
922 }
923
924 if (sub_entry)
925 e->m_entries.removeRef(sub_entry);
926 else
927 e->removeClient(instance);
928
929 if (e->m_clients.count() || e->m_entries.count()) {
930 kdDebug(7001) << "removeEntry: unwatched " << e->path.path() << " " << _path << endl;
931 return;
932 }
933
934 if (delayRemove) {
935 // removeList is allowed to contain any entry at most once
936 if (removeList.findRef(e)==-1)
937 removeList.append(e);
938 // now e->isValid() is false
939 return;
940 }
941
942#ifdef HAVE_FAM
943 if (e->m_mode == FAMMode) {
944 if ( e->m_status == Normal) {
945 FAMCancelMonitor(&fc, &(e->fr) );
946 kdDebug(7001) << "Cancelled FAM (Req "
947 << FAMREQUEST_GETREQNUM(&(e->fr))
948 << ") for " << e->path.path() << endl;
949 }
950 else {
951 if (e->isDir)
952 removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
953 else
954 removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
955 }
956 }
957#endif
958
959#ifdef HAVE_INOTIFY
960 kdDebug(7001) << "inotify remove " << ( e->m_mode == INotifyMode ) << " " << ( e->m_status == Normal ) << endl;
961 if (e->m_mode == INotifyMode) {
962 if ( e->m_status == Normal ) {
963 (void) inotify_rm_watch( m_inotify_fd, e->wd );
964 kdDebug(7001) << "Cancelled INotify (fd " <<
965 m_inotify_fd << ", " << e->wd <<
966 ") for " << e->path.path() << endl;
967 }
968 else {
969 if (e->isDir)
970 removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
971 else
972 removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
973 }
974 }
975#endif
976
977#ifdef HAVE_DNOTIFY
978 if (e->m_mode == DNotifyMode) {
979 if (!e->isDir) {
980 removeEntry(0, TQFileInfo(e->path.path()).dirPath(true), e);
981 }
982 else { // isDir
983 // must close the FD.
984 if ( e->m_status == Normal) {
985 if (e->dn_fd) {
986 ::close(e->dn_fd);
987 fd_Entry.remove(e->dn_fd);
988
989 kdDebug(7001) << "Cancelled DNotify (fd " << e->dn_fd
990 << ") for " << e->path.path() << endl;
991 e->dn_fd = 0;
992
993 }
994 }
995 else {
996 removeEntry(0, TQDir::cleanDirPath(e->path.path()+"/.."), e);
997 }
998 }
999 }
1000#endif
1001
1002 if (e->m_mode == StatMode) {
1003 statEntries--;
1004 if ( statEntries == 0 ) {
1005 timer->stop(); // stop timer if lists are empty
1006 kdDebug(7001) << " Stopped Polling Timer" << endl;
1007 }
1008 }
1009
1010 kdDebug(7001) << "Removed " << (e->isDir ? "Dir ":"File ") << e->path.path()
1011 << (sub_entry ? TQString(TQString(" for %1").arg(sub_entry->path.path())) : TQString(""))
1012 << (instance ? TQString(TQString(" [%1]").arg(instance->name())) : TQString(""))
1013 << endl;
1014 m_mapEntries.remove( e->path ); // <e> not valid any more
1015}
1016
1017
1018/* Called from KDirWatch destructor:
1019 * remove <instance> as client from all entries
1020 */
1021void KDirWatchPrivate::removeEntries( KDirWatch* instance )
1022{
1023 TQPtrList<Entry> list;
1024 int minfreq = 3600000;
1025
1026 // put all entries where instance is a client in list
1027 EntryMap::Iterator it = m_mapEntries.begin();
1028 for( ; it != m_mapEntries.end(); ++it ) {
1029 Client* c = (*it).m_clients.first();
1030 for(;c;c=(*it).m_clients.next())
1031 if (c->instance == instance) break;
1032 if (c) {
1033 c->count = 1; // forces deletion of instance as client
1034 list.append(&(*it));
1035 }
1036 else if ( (*it).m_mode == StatMode && (*it).freq < minfreq )
1037 minfreq = (*it).freq;
1038 }
1039
1040 for(Entry* e=list.first();e;e=list.next())
1041 removeEntry(instance, e->path, 0);
1042
1043 if (minfreq > freq) {
1044 // we can decrease the global polling frequency
1045 freq = minfreq;
1046 if (timer->isActive()) timer->changeInterval(freq);
1047 kdDebug(7001) << "Poll Freq now " << freq << " msec" << endl;
1048 }
1049}
1050
1051// instance ==0: stop scanning for all instances
1052bool KDirWatchPrivate::stopEntryScan( KDirWatch* instance, Entry* e)
1053{
1054 int stillWatching = 0;
1055 Client* c = e->m_clients.first();
1056 for(;c;c=e->m_clients.next()) {
1057 if (!instance || instance == c->instance)
1058 c->watchingStopped = true;
1059 else if (!c->watchingStopped)
1060 stillWatching += c->count;
1061 }
1062
1063 kdDebug(7001) << instance->name() << " stopped scanning " << e->path.path()
1064 << " (now " << stillWatching << " watchers)" << endl;
1065
1066 if (stillWatching == 0) {
1067 // if nobody is interested, we don't watch
1068 e->m_ctime = invalid_ctime; // invalid
1069 e->m_mtime = invalid_mtime; // invalid
1070 e->m_status = NonExistent;
1071 // e->m_status = Normal;
1072 }
1073 return true;
1074}
1075
1076// instance ==0: start scanning for all instances
1077bool KDirWatchPrivate::restartEntryScan( KDirWatch* instance, Entry* e,
1078 bool notify)
1079{
1080 int wasWatching = 0, newWatching = 0;
1081 Client* c = e->m_clients.first();
1082 for(;c;c=e->m_clients.next()) {
1083 if (!c->watchingStopped)
1084 wasWatching += c->count;
1085 else if (!instance || instance == c->instance) {
1086 c->watchingStopped = false;
1087 newWatching += c->count;
1088 }
1089 }
1090 if (newWatching == 0)
1091 return false;
1092
1093 kdDebug(7001) << (instance ? instance->name() : "all") << " restarted scanning " << e->path.path()
1094 << " (now " << wasWatching+newWatching << " watchers)" << endl;
1095
1096 // restart watching and emit pending events
1097
1098 int ev = NoChange;
1099 if (wasWatching == 0) {
1100 if (!notify) {
1101 KDE_struct_stat stat_buf;
1102 bool exists = (KDE_stat(TQFile::encodeName(e->path.path()), &stat_buf) == 0);
1103 if (exists) {
1104 e->m_ctime = stat_buf.st_ctime;
1105 e->m_mtime = stat_buf.st_mtime;
1106 e->m_status = Normal;
1107 e->m_nlink = stat_buf.st_nlink;
1108 }
1109 else {
1110 e->m_ctime = invalid_ctime;
1111 e->m_mtime = invalid_mtime;
1112 e->m_status = NonExistent;
1113 e->m_nlink = 0;
1114 }
1115 }
1116 e->msecLeft = 0;
1117 ev = scanEntry(e);
1118 }
1119 emitEvent(e,ev);
1120
1121 return true;
1122}
1123
1124// instance ==0: stop scanning for all instances
1125void KDirWatchPrivate::stopScan(KDirWatch* instance)
1126{
1127 EntryMap::Iterator it = m_mapEntries.begin();
1128 for( ; it != m_mapEntries.end(); ++it )
1129 stopEntryScan(instance, &(*it));
1130}
1131
1132
1133void KDirWatchPrivate::startScan(KDirWatch* instance,
1134 bool notify, bool skippedToo )
1135{
1136 if (!notify)
1137 resetList(instance,skippedToo);
1138
1139 EntryMap::Iterator it = m_mapEntries.begin();
1140 for( ; it != m_mapEntries.end(); ++it )
1141 restartEntryScan(instance, &(*it), notify);
1142
1143 // timer should still be running when in polling mode
1144}
1145
1146
1147// clear all pending events, also from stopped
1148void KDirWatchPrivate::resetList( KDirWatch* /*instance*/,
1149 bool skippedToo )
1150{
1151 EntryMap::Iterator it = m_mapEntries.begin();
1152 for( ; it != m_mapEntries.end(); ++it ) {
1153
1154 Client* c = (*it).m_clients.first();
1155 for(;c;c=(*it).m_clients.next())
1156 if (!c->watchingStopped || skippedToo)
1157 c->pending = NoChange;
1158 }
1159}
1160
1161// Return event happened on <e>
1162//
1163int KDirWatchPrivate::scanEntry(Entry* e)
1164{
1165#ifdef HAVE_FAM
1166 if (e->m_mode == FAMMode) {
1167 // we know nothing has changed, no need to stat
1168 if(!e->dirty) return NoChange;
1169 e->dirty = false;
1170 }
1171 if (e->isDir) return Changed;
1172#endif
1173
1174 // Shouldn't happen: Ignore "unknown" notification method
1175 if (e->m_mode == UnknownMode) return NoChange;
1176
1177#if defined ( HAVE_DNOTIFY ) || defined( HAVE_INOTIFY )
1178 if (e->m_mode == DNotifyMode || e->m_mode == INotifyMode ) {
1179 // we know nothing has changed, no need to stat
1180 if(!e->dirty) return NoChange;
1181 kdDebug(7001) << "scanning " << e->path.path() << " " << e->m_status << " " << e->m_ctime << " " << e->m_mtime << endl;
1182 e->dirty = false;
1183 }
1184#endif
1185
1186 if (e->m_mode == StatMode) {
1187 // only scan if timeout on entry timer happens;
1188 // e.g. when using 500msec global timer, a entry
1189 // with freq=5000 is only watched every 10th time
1190
1191 e->msecLeft -= freq;
1192 if (e->msecLeft>0) return NoChange;
1193 e->msecLeft += e->freq;
1194 }
1195
1196 KDE_struct_stat stat_buf;
1197 bool exists = (KDE_stat(TQFile::encodeName(e->path.path()), &stat_buf) == 0);
1198 if (exists) {
1199
1200 if (e->m_status == NonExistent) {
1201 // ctime is the 'creation time' on windows, but with qMax
1202 // we get the latest change of any kind, on any platform.
1203 e->m_ctime = stat_buf.st_ctime;
1204 e->m_mtime = stat_buf.st_mtime;
1205 e->m_status = Normal;
1206 e->m_nlink = stat_buf.st_nlink;
1207 return Created;
1208 }
1209
1210 if ( (e->m_ctime != invalid_ctime) &&
1211 ((stat_buf.st_ctime != e->m_ctime) ||
1212 (stat_buf.st_mtime != e->m_mtime) ||
1213 (stat_buf.st_nlink != (nlink_t) e->m_nlink)) ) {
1214 e->m_ctime = stat_buf.st_ctime;
1215 e->m_mtime = stat_buf.st_mtime;
1216 e->m_nlink = stat_buf.st_nlink;
1217 return Changed;
1218 }
1219
1220 return NoChange;
1221 }
1222
1223 // dir/file doesn't exist
1224
1225 if (e->m_ctime == invalid_ctime && e->m_status == NonExistent) {
1226 e->m_nlink = 0;
1227 e->m_status = NonExistent;
1228 return NoChange;
1229 }
1230
1231 e->m_ctime = invalid_ctime;
1232 e->m_mtime = invalid_mtime;
1233 e->m_nlink = 0;
1234 e->m_status = NonExistent;
1235
1236 return Deleted;
1237}
1238
1239/* Notify all interested KDirWatch instances about a given event on an entry
1240 * and stored pending events. When watching is stopped, the event is
1241 * added to the pending events.
1242 */
1243void KDirWatchPrivate::emitEvent(Entry* e, int event, const KURL &fileName)
1244{
1245 TQString path = e->path.path();
1246 if (!fileName.isEmpty()) {
1247 if (!TQDir::isRelativePath(fileName.path()))
1248 path = fileName.path();
1249 else
1250#ifdef Q_OS_UNIX
1251 path += "/" + fileName.path();
1252#elif defined(TQ_WS_WIN)
1253 //current drive is passed instead of /
1254 path += TQDir::currentDirPath().left(2) + "/" + fileName.path();
1255#endif
1256 }
1257
1258 TQPtrListIterator<Client> cit( e->m_clients );
1259 for ( ; cit.current(); ++cit )
1260 {
1261 Client* c = cit.current();
1262
1263 if (c->instance==0 || c->count==0) continue;
1264
1265 if (c->watchingStopped) {
1266 // add event to pending...
1267 if (event == Changed)
1268 c->pending |= event;
1269 else if (event == Created || event == Deleted)
1270 c->pending = event;
1271 continue;
1272 }
1273 // not stopped
1274 if (event == NoChange || event == Changed)
1275 event |= c->pending;
1276 c->pending = NoChange;
1277 if (event == NoChange) continue;
1278
1279 if (event & Deleted) {
1280 c->instance->setDeleted(path);
1281 // emit only Deleted event...
1282 continue;
1283 }
1284
1285 if (event & Created) {
1286 c->instance->setCreated(path);
1287 // possible emit Change event after creation
1288 }
1289
1290 if (event & Changed) {
1291 c->instance->setDirty(path);
1292 c->instance->setDirty(e->path);
1293 }
1294 }
1295}
1296
1297// Remove entries which were marked to be removed
1298void KDirWatchPrivate::slotRemoveDelayed()
1299{
1300 Entry* e;
1301 delayRemove = false;
1302 for(e=removeList.first();e;e=removeList.next())
1303 removeEntry(0, e->path, 0);
1304 removeList.clear();
1305}
1306
1307/* Scan all entries to be watched for changes. This is done regularly
1308 * when polling and once after a DNOTIFY signal. This is NOT used by FAM.
1309 */
1310void KDirWatchPrivate::slotRescan()
1311{
1312 EntryMap::Iterator it;
1313
1314 // People can do very long things in the slot connected to dirty(),
1315 // like showing a message box. We don't want to keep polling during
1316 // that time, otherwise the value of 'delayRemove' will be reset.
1317 bool timerRunning = timer->isActive();
1318 if ( timerRunning ) {
1319 timer->stop();
1320 }
1321
1322 // We delay deletions of entries this way.
1323 // removeDir(), when called in slotDirty(), can cause a crash otherwise
1324 delayRemove = true;
1325
1326#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
1327 TQPtrList<Entry> dList, cList;
1328#endif
1329
1330 if (rescan_all)
1331 {
1332 // mark all as dirty
1333 it = m_mapEntries.begin();
1334 for( ; it != m_mapEntries.end(); ++it ) {
1335 (*it).dirty = true;
1336 }
1337 rescan_all = false;
1338 }
1339 else
1340 {
1341 // progate dirty flag to dependant entries (e.g. file watches)
1342 it = m_mapEntries.begin();
1343 for( ; it != m_mapEntries.end(); ++it ) {
1344 if (((*it).m_mode == INotifyMode || (*it).m_mode == DNotifyMode) && (*it).dirty ) {
1345 (*it).propagate_dirty();
1346 }
1347 }
1348 }
1349
1350 it = m_mapEntries.begin();
1351 for( ; it != m_mapEntries.end(); ++it ) {
1352 // we don't check invalid entries (i.e. remove delayed)
1353 if (!(*it).isValid()) continue;
1354
1355 int ev = scanEntry( &(*it) );
1356
1357
1358#ifdef HAVE_INOTIFY
1359 if ((*it).m_mode == INotifyMode && ev == Created && (*it).wd == 0) {
1360 cList.append( &(*it) );
1361 if (! useINotify( &(*it) )) {
1362 useStat( &(*it) );
1363 }
1364 }
1365#endif
1366
1367#ifdef HAVE_DNOTIFY
1368 if ((*it).m_mode == DNotifyMode) {
1369 if ((*it).isDir && (ev == Deleted)) {
1370 dList.append( &(*it) );
1371
1372 // must close the FD.
1373 if ((*it).dn_fd) {
1374 ::close((*it).dn_fd);
1375 fd_Entry.remove((*it).dn_fd);
1376 (*it).dn_fd = 0;
1377 }
1378 }
1379
1380 else if ((*it).isDir && (ev == Created)) {
1381 // For created, but yet without DNOTIFYing ...
1382 if ( (*it).dn_fd == 0) {
1383 cList.append( &(*it) );
1384 if (! useDNotify( &(*it) )) {
1385 // if DNotify setup fails...
1386 useStat( &(*it) );
1387 }
1388 }
1389 }
1390 }
1391#endif
1392
1393 if ( ev != NoChange ) {
1394 // Emit events for any entries with the same path as the changed entry
1395 EntryMap::Iterator it2;
1396 it2 = m_mapEntries.begin();
1397 for( ; it2 != m_mapEntries.end(); ++it2 ) {
1398 if ((*it).path.url() == (*it2).path.url()) {
1399 emitEvent( &(*it2), ev);
1400 }
1401 }
1402 }
1403 }
1404
1405
1406#if defined(HAVE_DNOTIFY) || defined(HAVE_INOTIFY)
1407 // Scan parent of deleted directories for new creation
1408 Entry* e;
1409 for(e=dList.first();e;e=dList.next()) {
1410 addEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e, true);
1411 }
1412
1413 // Remove watch of parent of new created directories
1414 for(e=cList.first();e;e=cList.next()) {
1415 removeEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e);
1416 }
1417#endif
1418
1419 if ( timerRunning ) {
1420 timer->start(freq);
1421 }
1422
1423 TQTimer::singleShot(0, this, TQ_SLOT(slotRemoveDelayed()));
1424}
1425
1426bool KDirWatchPrivate::isNoisyFile( const char * filename )
1427{
1428 // $HOME/.X.err grows with debug output, so don't notify change
1429 if ( *filename == '.') {
1430 if (strncmp(filename, ".X.err", 6) == 0) return true;
1431 if (strncmp(filename, ".xsession-errors", 16) == 0) return true;
1432 // fontconfig updates the cache on every KDE app start
1433 // (inclusive tdeio_thumbnail slaves)
1434 if (strncmp(filename, ".fonts.cache", 12) == 0) return true;
1435 }
1436
1437 return false;
1438}
1439
1440#ifdef HAVE_FAM
1441void KDirWatchPrivate::famEventReceived()
1442{
1443 static FAMEvent fe;
1444
1445 delayRemove = true;
1446
1447 while(use_fam && FAMPending(&fc)) {
1448 if (FAMNextEvent(&fc, &fe) == -1) {
1449 kdWarning(7001) << "FAM connection problem, switching to polling."
1450 << endl;
1451 use_fam = false;
1452 delete sn; sn = 0;
1453
1454 // Replace all FAMMode entries with DNotify/Stat
1455 EntryMap::Iterator it;
1456 it = m_mapEntries.begin();
1457 for( ; it != m_mapEntries.end(); ++it )
1458 if ((*it).m_mode == FAMMode && (*it).m_clients.count()>0) {
1459#ifdef HAVE_INOTIFY
1460 if (useINotify( &(*it) )) continue;
1461#endif
1462#ifdef HAVE_DNOTIFY
1463 if (useDNotify( &(*it) )) continue;
1464#endif
1465 useStat( &(*it) );
1466 }
1467 }
1468 else
1469 checkFAMEvent(&fe);
1470 }
1471
1472 TQTimer::singleShot(0, this, TQ_SLOT(slotRemoveDelayed()));
1473}
1474
1475void KDirWatchPrivate::checkFAMEvent(FAMEvent* fe)
1476{
1477 // Don't be too verbose ;-)
1478 if ((fe->code == FAMExists) ||
1479 (fe->code == FAMEndExist) ||
1480 (fe->code == FAMAcknowledge)) return;
1481
1482 if ( isNoisyFile( fe->filename ) )
1483 return;
1484
1485 Entry* e = 0;
1486 EntryMap::Iterator it = m_mapEntries.begin();
1487 for( ; it != m_mapEntries.end(); ++it )
1488 if (FAMREQUEST_GETREQNUM(&( (*it).fr )) ==
1489 FAMREQUEST_GETREQNUM(&(fe->fr)) ) {
1490 e = &(*it);
1491 break;
1492 }
1493
1494 // Entry* e = static_cast<Entry*>(fe->userdata);
1495
1496#if 0 // #88538
1497 kdDebug(7001) << "Processing FAM event ("
1498 << ((fe->code == FAMChanged) ? "FAMChanged" :
1499 (fe->code == FAMDeleted) ? "FAMDeleted" :
1500 (fe->code == FAMStartExecuting) ? "FAMStartExecuting" :
1501 (fe->code == FAMStopExecuting) ? "FAMStopExecuting" :
1502 (fe->code == FAMCreated) ? "FAMCreated" :
1503 (fe->code == FAMMoved) ? "FAMMoved" :
1504 (fe->code == FAMAcknowledge) ? "FAMAcknowledge" :
1505 (fe->code == FAMExists) ? "FAMExists" :
1506 (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code")
1507 << ", " << fe->filename
1508 << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr))
1509 << ")" << endl;
1510#endif
1511
1512 if (!e) {
1513 // this happens e.g. for FAMAcknowledge after deleting a dir...
1514 // kdDebug(7001) << "No entry for FAM event ?!" << endl;
1515 return;
1516 }
1517
1518 if (e->m_status == NonExistent) {
1519 kdDebug(7001) << "FAM event for nonExistent entry " << e->path.path() << endl;
1520 return;
1521 }
1522
1523 // Delayed handling. This rechecks changes with own stat calls.
1524 e->dirty = true;
1525 if (!rescan_timer.isActive())
1526 rescan_timer.start(m_PollInterval, true);
1527
1528 // needed FAM control actions on FAM events
1529 if (e->isDir)
1530 switch (fe->code)
1531 {
1532 case FAMDeleted:
1533 // file absolute: watched dir
1534 if (!TQDir::isRelativePath(fe->filename))
1535 {
1536 // a watched directory was deleted
1537
1538 e->m_status = NonExistent;
1539 FAMCancelMonitor(&fc, &(e->fr) ); // needed ?
1540 kdDebug(7001) << "Cancelled FAMReq "
1541 << FAMREQUEST_GETREQNUM(&(e->fr))
1542 << " for " << e->path.path() << endl;
1543 // Scan parent for a new creation
1544 addEntry(0, TQDir::cleanDirPath( e->path.path()+"/.."), e, true);
1545 }
1546 break;
1547
1548 case FAMCreated: {
1549 // check for creation of a directory we have to watch
1550 Entry *sub_entry = e->m_entries.first();
1551 for(;sub_entry; sub_entry = e->m_entries.next())
1552 if (sub_entry->path.path() == e->path.path() + "/" + fe->filename) break;
1553 if (sub_entry && sub_entry->isDir) {
1554 KURL path = e->path;
1555 removeEntry(0,e->path,sub_entry); // <e> can be invalid here!!
1556 sub_entry->m_status = Normal;
1557 if (!useFAM(sub_entry))
1558 {
1559#ifdef HAVE_INOTIFY
1560 if (!useINotify(sub_entry ))
1561#endif
1562 {
1563 useStat(sub_entry);
1564 }
1565 }
1566 }
1567 break;
1568 }
1569
1570 default:
1571 break;
1572 }
1573}
1574#else
1575void KDirWatchPrivate::famEventReceived() {}
1576#endif
1577
1578
1579void KDirWatchPrivate::statistics()
1580{
1581 EntryMap::Iterator it;
1582
1583 kdDebug(7001) << "Entries watched:" << endl;
1584 if (m_mapEntries.count()==0) {
1585 kdDebug(7001) << " None." << endl;
1586 }
1587 else {
1588 it = m_mapEntries.begin();
1589 for( ; it != m_mapEntries.end(); ++it ) {
1590 Entry* e = &(*it);
1591 kdDebug(7001) << " " << e->path.path() << " ("
1592 << ((e->m_status==Normal)?"":"Nonexistent ")
1593 << (e->isDir ? "Dir":"File") << ", using "
1594 << ((e->m_mode == FAMMode) ? "FAM" :
1595 (e->m_mode == INotifyMode) ? "INotify" :
1596 (e->m_mode == DNotifyMode) ? "DNotify" :
1597 (e->m_mode == StatMode) ? "Stat" : "Unknown Method")
1598 << ")" << endl;
1599
1600 Client* c = e->m_clients.first();
1601 for(;c; c = e->m_clients.next()) {
1602 TQString pending;
1603 if (c->watchingStopped) {
1604 if (c->pending & Deleted) pending += "deleted ";
1605 if (c->pending & Created) pending += "created ";
1606 if (c->pending & Changed) pending += "changed ";
1607 if (!pending.isEmpty()) pending = " (pending: " + pending + ")";
1608 pending = ", stopped" + pending;
1609 }
1610 kdDebug(7001) << " by " << c->instance->name()
1611 << " (" << c->count << " times)"
1612 << pending << endl;
1613 }
1614 if (e->m_entries.count()>0) {
1615 kdDebug(7001) << " dependent entries:" << endl;
1616 Entry* d = e->m_entries.first();
1617 for(;d; d = e->m_entries.next()) {
1618 kdDebug(7001) << " " << d << endl;
1619 kdDebug(7001) << " " << d->path << " (" << d << ") " << endl;
1620 }
1621 }
1622 }
1623 }
1624}
1625
1626
1627//
1628// Class KDirWatch
1629//
1630
1631static KStaticDeleter<KDirWatch> sd_dw;
1632KDirWatch* KDirWatch::s_pSelf = 0L;
1633
1634KDirWatch* KDirWatch::self()
1635{
1636 if ( !s_pSelf ) {
1637 sd_dw.setObject( s_pSelf, new KDirWatch );
1638 }
1639
1640 return s_pSelf;
1641}
1642
1643bool KDirWatch::exists()
1644{
1645 return s_pSelf != 0;
1646}
1647
1648KDirWatch::KDirWatch (TQObject* parent, const char* name)
1649 : TQObject(parent,name)
1650{
1651 if (!name) {
1652 static int nameCounter = 0;
1653
1654 nameCounter++;
1655 setName(TQString(TQString("KDirWatch-%1").arg(nameCounter)).ascii());
1656 }
1657
1658 if (!dwp_self)
1659 dwp_self = new KDirWatchPrivate;
1660 d = dwp_self;
1661 d->ref();
1662
1663 _isStopped = false;
1664}
1665
1666KDirWatch::~KDirWatch()
1667{
1668 d->removeEntries(this);
1669 if ( d->deref() )
1670 {
1671 // delete it if it's the last one
1672 delete d;
1673 dwp_self = 0L;
1674 }
1675}
1676
1677
1678// TODO: add watchFiles/recursive support
1679void KDirWatch::addDir( const TQString& _path, bool watchFiles, bool recursive)
1680{
1681 if (watchFiles || recursive) {
1682 kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in TDE 3.x" << endl;
1683 }
1684 if (d) d->addEntry(this, _path, 0, true);
1685}
1686
1687// TODO: add watchFiles/recursive support
1688void KDirWatch::addDir( const KURL& _url, bool watchFiles, bool recursive)
1689{
1690 if (watchFiles || recursive) {
1691 kdDebug(7001) << "addDir - recursive/watchFiles not supported yet in TDE 3.x" << endl;
1692 }
1693 if (d) d->addEntry(this, _url, 0, true);
1694}
1695
1696void KDirWatch::addFile( const TQString& _path )
1697{
1698 if (d) d->addEntry(this, _path, 0, false);
1699}
1700
1701TQDateTime KDirWatch::ctime( const TQString &_path )
1702{
1703 KDirWatchPrivate::Entry* e = d->entry(_path);
1704
1705 if (!e)
1706 return TQDateTime();
1707
1708 TQDateTime result;
1709 result.setTime_t(e->m_ctime);
1710 return result;
1711}
1712
1713void KDirWatch::removeDir( const TQString& _path )
1714{
1715 if (d) d->removeEntry(this, _path, 0);
1716}
1717
1718void KDirWatch::removeDir( const KURL& _url )
1719{
1720 if (d) d->removeEntry(this, _url, 0);
1721}
1722
1723void KDirWatch::removeFile( const TQString& _path )
1724{
1725 if (d) d->removeEntry(this, _path, 0);
1726}
1727
1728bool KDirWatch::stopDirScan( const TQString& _path )
1729{
1730 if (d) {
1731 KDirWatchPrivate::Entry *e = d->entry(_path);
1732 if (e && e->isDir) return d->stopEntryScan(this, e);
1733 }
1734 return false;
1735}
1736
1737bool KDirWatch::restartDirScan( const TQString& _path )
1738{
1739 if (d) {
1740 KDirWatchPrivate::Entry *e = d->entry(_path);
1741 if (e && e->isDir)
1742 // restart without notifying pending events
1743 return d->restartEntryScan(this, e, false);
1744 }
1745 return false;
1746}
1747
1748void KDirWatch::stopScan()
1749{
1750 if (d) d->stopScan(this);
1751 _isStopped = true;
1752}
1753
1754void KDirWatch::startScan( bool notify, bool skippedToo )
1755{
1756 _isStopped = false;
1757 if (d) d->startScan(this, notify, skippedToo);
1758}
1759
1760
1761bool KDirWatch::contains( const TQString& _path ) const
1762{
1763 KDirWatchPrivate::Entry* e = d->entry(_path);
1764 if (!e)
1765 return false;
1766
1767 KDirWatchPrivate::Client* c = e->m_clients.first();
1768 for(;c;c=e->m_clients.next())
1769 if (c->instance == this) return true;
1770
1771 return false;
1772}
1773
1774void KDirWatch::statistics()
1775{
1776 if (!dwp_self) {
1777 kdDebug(7001) << "KDirWatch not used" << endl;
1778 return;
1779 }
1780 dwp_self->statistics();
1781}
1782
1783
1784void KDirWatch::setCreated( const TQString & _file )
1785{
1786 kdDebug(7001) << name() << " emitting created " << _file << endl;
1787 emit created( _file );
1788}
1789
1790void KDirWatch::setDirty( const TQString & _file )
1791{
1792 kdDebug(7001) << name() << " emitting dirty " << _file << endl;
1793 emit dirty( _file );
1794}
1795
1796void KDirWatch::setDirty( const KURL & _url )
1797{
1798 kdDebug(7001) << name() << " emitting dirty " << _url << endl;
1799 emit dirty( _url );
1800}
1801
1802void KDirWatch::setDeleted( const TQString & _file )
1803{
1804 kdDebug(7001) << name() << " emitting deleted " << _file << endl;
1805 emit deleted( _file );
1806}
1807
1808KDirWatch::Method KDirWatch::internalMethod()
1809{
1810#ifdef HAVE_FAM
1811 if (d->use_fam)
1812 return KDirWatch::FAM;
1813#endif
1814#ifdef HAVE_INOTIFY
1815 if (d->supports_inotify)
1816 return KDirWatch::INotify;
1817#endif
1818#ifdef HAVE_DNOTIFY
1819 if (d->supports_dnotify)
1820 return KDirWatch::DNotify;
1821#endif
1822 return KDirWatch::Stat;
1823}
1824
1825
1826#include "kdirwatch.moc"
1827#include "kdirwatch_p.moc"
KDirWatch
Watch directories and files for changes.
Definition kdirwatch.h:66
KDirWatch::created
void created(const TQString &path)
Emitted when a file or directory is created.
KDirWatch::stopScan
void stopScan()
Stops scanning of all directories in internal list.
Definition kdirwatch.cpp:1748
KDirWatch::ctime
TQDateTime ctime(const TQString &path)
Returns the time the directory/file was last changed.
Definition kdirwatch.cpp:1701
KDirWatch::restartDirScan
bool restartDirScan(const TQString &path)
Restarts scanning for specified path.
Definition kdirwatch.cpp:1737
KDirWatch::contains
bool contains(const TQString &path) const
Check if a directory is being watched by this KDirWatch instance.
Definition kdirwatch.cpp:1761
KDirWatch::removeDir
void removeDir(const TQString &path)
Removes a directory from the list of scanned directories.
Definition kdirwatch.cpp:1713
KDirWatch::statistics
static void statistics()
Dump statistic information about all KDirWatch instances.
Definition kdirwatch.cpp:1774
KDirWatch::self
static KDirWatch * self()
The KDirWatch instance usually globally used in an application.
Definition kdirwatch.cpp:1634
KDirWatch::internalMethod
Method internalMethod()
Returns the preferred internal method to watch for changes.
Definition kdirwatch.cpp:1808
KDirWatch::setDeleted
void setDeleted(const TQString &path)
Emits deleted().
Definition kdirwatch.cpp:1802
KDirWatch::stopDirScan
bool stopDirScan(const TQString &path)
Stops scanning the specified path.
Definition kdirwatch.cpp:1728
KDirWatch::startScan
void startScan(bool notify=false, bool skippedToo=false)
Starts scanning of all dirs in list.
Definition kdirwatch.cpp:1754
KDirWatch::setDirty
void setDirty(const TQString &path)
Emits dirty().
Definition kdirwatch.cpp:1790
KDirWatch::exists
static bool exists()
Returns true if there is an instance of KDirWatch.
Definition kdirwatch.cpp:1643
KDirWatch::addFile
void addFile(const TQString &file)
Adds a file to be watched.
Definition kdirwatch.cpp:1696
KDirWatch::~KDirWatch
~KDirWatch()
Destructor.
Definition kdirwatch.cpp:1666
KDirWatch::isStopped
bool isStopped()
Is scanning stopped? After creation of a KDirWatch instance, this is false.
Definition kdirwatch.h:195
KDirWatch::removeFile
void removeFile(const TQString &file)
Removes a file from the list of watched files.
Definition kdirwatch.cpp:1723
KDirWatch::deleted
void deleted(const TQString &path)
Emitted when a file or directory is deleted.
KDirWatch::addDir
void addDir(const TQString &path, bool watchFiles=false, bool recursive=false)
Adds a directory to be watched.
Definition kdirwatch.cpp:1679
KDirWatch::KDirWatch
KDirWatch(TQObject *parent=0, const char *name=0)
Constructor.
Definition kdirwatch.cpp:1648
KDirWatch::dirty
void dirty(const TQString &path)
Emitted when a watched object is changed.
KDirWatch::setCreated
void setCreated(const TQString &path)
Emits created().
Definition kdirwatch.cpp:1784
TDEIO::probably_slow_mounted
TDEIO_EXPORT bool probably_slow_mounted(const TQString &filename)
Checks if the path belongs to a filesystem that is probably slow.
Definition global.cpp:1971

tdeio/tdeio

Skip menu "tdeio/tdeio"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

tdeio/tdeio

Skip menu "tdeio/tdeio"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdeio/tdeio by doxygen 1.9.8
This website is maintained by Timothy Pearson.