• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdecore
 

tdecore

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

tdecore

Skip menu "tdecore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

tdecore

Skip menu "tdecore"
  • 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 tdecore by doxygen 1.8.1.2
This website is maintained by Timothy Pearson.