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