1 | /* This file is part of the KDE libraries |
2 | SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at> |
3 | SPDX-FileCopyrightText: 2006 Dirk Mueller <mueller@kde.org> |
4 | SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com> |
5 | SPDX-FileCopyrightText: 2008 Rafal Rzepecki <divided.mind@gmail.com> |
6 | SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org> |
7 | SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org> |
8 | |
9 | SPDX-License-Identifier: LGPL-2.0-only |
10 | */ |
11 | |
12 | // CHANGES: |
13 | // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal) |
14 | // Aug 6, 2007 - KDirWatch::WatchModes support complete, flags work fine also |
15 | // when using FAMD (Flavio Castelli) |
16 | // Aug 3, 2007 - Handled KDirWatch::WatchModes flags when using inotify, now |
17 | // recursive and file monitoring modes are implemented (Flavio Castelli) |
18 | // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes |
19 | // flag (Flavio Castelli) |
20 | // Oct 4, 2005 - Inotify support (Dirk Mueller) |
21 | // February 2002 - Add file watching and remote mount check for STAT |
22 | // Mar 30, 2001 - Native support for Linux dir change notification. |
23 | // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de) |
24 | // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven) |
25 | // May 23. 1998 - Removed static pointer - you can have more instances. |
26 | // It was Needed for KRegistry. KDirWatch now emits signals and doesn't |
27 | // call (or need) KFM. No more URL's - just plain paths. (sven) |
28 | // Mar 29. 1998 - added docs, stop/restart for particular Dirs and |
29 | // deep copies for list of dirs. (sven) |
30 | // Mar 28. 1998 - Created. (sven) |
31 | |
32 | #include "kdirwatch.h" |
33 | #include "kcoreaddons_debug.h" |
34 | #include "kdirwatch_p.h" |
35 | #include "kfilesystemtype.h" |
36 | #include "knetworkmounts.h" |
37 | |
38 | #include <io/config-kdirwatch.h> |
39 | |
40 | #include <QCoreApplication> |
41 | #include <QDir> |
42 | #include <QFile> |
43 | #include <QLoggingCategory> |
44 | #include <QSocketNotifier> |
45 | #include <QThread> |
46 | #include <QThreadStorage> |
47 | #include <QTimer> |
48 | #include <assert.h> |
49 | #include <cerrno> |
50 | #include <sys/stat.h> |
51 | |
52 | #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF |
53 | |
54 | #include <stdlib.h> |
55 | #include <string.h> |
56 | |
57 | #if HAVE_SYS_INOTIFY_H |
58 | #include <fcntl.h> |
59 | #include <sys/inotify.h> |
60 | #include <unistd.h> |
61 | |
62 | #ifndef IN_DONT_FOLLOW |
63 | #define IN_DONT_FOLLOW 0x02000000 |
64 | #endif |
65 | |
66 | #ifndef IN_ONLYDIR |
67 | #define IN_ONLYDIR 0x01000000 |
68 | #endif |
69 | |
70 | // debug |
71 | #include <sys/ioctl.h> |
72 | |
73 | #include <sys/utsname.h> |
74 | |
75 | #endif // HAVE_SYS_INOTIFY_H |
76 | |
77 | Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH) |
78 | // logging category for this framework, default: log stuff >= warning |
79 | Q_LOGGING_CATEGORY(KDIRWATCH, "kf.coreaddons.kdirwatch" , QtWarningMsg) |
80 | |
81 | // set this to true for much more verbose debug output |
82 | static bool s_verboseDebug = false; |
83 | |
84 | static QThreadStorage<KDirWatchPrivate *> dwp_self; |
85 | static KDirWatchPrivate *createPrivate() |
86 | { |
87 | if (!dwp_self.hasLocalData()) { |
88 | dwp_self.setLocalData(new KDirWatchPrivate); |
89 | } |
90 | return dwp_self.localData(); |
91 | } |
92 | static void destroyPrivate() |
93 | { |
94 | dwp_self.localData()->deleteLater(); |
95 | dwp_self.setLocalData(nullptr); |
96 | } |
97 | |
98 | // Convert a string into a watch Method |
99 | static KDirWatch::Method methodFromString(const QByteArray &method) |
100 | { |
101 | if (method == "Stat" ) { |
102 | return KDirWatch::Stat; |
103 | } else if (method == "QFSWatch" ) { |
104 | return KDirWatch::QFSWatch; |
105 | } else { |
106 | #if HAVE_SYS_INOTIFY_H |
107 | // inotify supports delete+recreate+modify, which QFSWatch doesn't support |
108 | return KDirWatch::INotify; |
109 | #else |
110 | return KDirWatch::QFSWatch; |
111 | #endif |
112 | } |
113 | } |
114 | |
115 | static const char *methodToString(KDirWatch::Method method) |
116 | { |
117 | switch (method) { |
118 | case KDirWatch::INotify: |
119 | return "INotify" ; |
120 | case KDirWatch::Stat: |
121 | return "Stat" ; |
122 | case KDirWatch::QFSWatch: |
123 | return "QFSWatch" ; |
124 | } |
125 | // not reached |
126 | return nullptr; |
127 | } |
128 | |
129 | static const char s_envNfsPoll[] = "KDIRWATCH_NFSPOLLINTERVAL" ; |
130 | static const char s_envPoll[] = "KDIRWATCH_POLLINTERVAL" ; |
131 | static const char s_envMethod[] = "KDIRWATCH_METHOD" ; |
132 | static const char s_envNfsMethod[] = "KDIRWATCH_NFSMETHOD" ; |
133 | |
134 | // |
135 | // Class KDirWatchPrivate (singleton) |
136 | // |
137 | |
138 | /* All entries (files/directories) to be watched in the |
139 | * application (coming from multiple KDirWatch instances) |
140 | * are registered in a single KDirWatchPrivate instance. |
141 | * |
142 | * At the moment, the following methods for file watching |
143 | * are supported: |
144 | * - Polling: All files to be watched are polled regularly |
145 | * using stat (more precise: QFileInfo.lastModified()). |
146 | * The polling frequency is determined from global kconfig |
147 | * settings, defaulting to 500 ms for local directories |
148 | * and 5000 ms for remote mounts |
149 | * - FAM (File Alternation Monitor): first used on IRIX, SGI |
150 | * has ported this method to LINUX. It uses a kernel part |
151 | * (IMON, sending change events to /dev/imon) and a user |
152 | * level daemon (fam), to which applications connect for |
153 | * notification of file changes. For NFS, the fam daemon |
154 | * on the NFS server machine is used; if IMON is not built |
155 | * into the kernel, fam uses polling for local files. |
156 | * - INOTIFY: In LINUX 2.6.13, inode change notification was |
157 | * introduced. You're now able to watch arbitrary inode's |
158 | * for changes, and even get notification when they're |
159 | * unmounted. |
160 | */ |
161 | |
162 | KDirWatchPrivate::KDirWatchPrivate() |
163 | : m_statRescanTimer() |
164 | , freq(3600000) |
165 | , // 1 hour as upper bound |
166 | statEntries(0) |
167 | , delayRemove(false) |
168 | , rescan_all(false) |
169 | , rescan_timer() |
170 | , |
171 | #if HAVE_SYS_INOTIFY_H |
172 | mSn(nullptr) |
173 | , |
174 | #endif |
175 | _isStopped(false) |
176 | { |
177 | // Debug unittest on CI |
178 | if (qAppName() == QLatin1String("kservicetest" ) || qAppName() == QLatin1String("filetypestest" )) { |
179 | s_verboseDebug = true; |
180 | } |
181 | m_statRescanTimer.setObjectName(QStringLiteral("KDirWatchPrivate::timer" )); |
182 | connect(sender: &m_statRescanTimer, signal: &QTimer::timeout, context: this, slot: &KDirWatchPrivate::slotRescan); |
183 | |
184 | m_nfsPollInterval = qEnvironmentVariableIsSet(varName: s_envNfsPoll) ? qEnvironmentVariableIntValue(varName: s_envNfsPoll) : 5000; |
185 | m_PollInterval = qEnvironmentVariableIsSet(varName: s_envPoll) ? qEnvironmentVariableIntValue(varName: s_envPoll) : 500; |
186 | |
187 | m_preferredMethod = methodFromString(method: qEnvironmentVariableIsSet(varName: s_envMethod) ? qgetenv(varName: s_envMethod) : "inotify" ); |
188 | // The nfs method defaults to the normal (local) method |
189 | m_nfsPreferredMethod = methodFromString(method: qEnvironmentVariableIsSet(varName: s_envNfsMethod) ? qgetenv(varName: s_envNfsMethod) : "Stat" ); |
190 | |
191 | QList<QByteArray> availableMethods; |
192 | |
193 | availableMethods << "Stat" ; |
194 | |
195 | // used for inotify |
196 | rescan_timer.setObjectName(QStringLiteral("KDirWatchPrivate::rescan_timer" )); |
197 | rescan_timer.setSingleShot(true); |
198 | connect(sender: &rescan_timer, signal: &QTimer::timeout, context: this, slot: &KDirWatchPrivate::slotRescan); |
199 | |
200 | #if HAVE_SYS_INOTIFY_H |
201 | m_inotify_fd = inotify_init(); |
202 | supports_inotify = m_inotify_fd > 0; |
203 | |
204 | if (!supports_inotify) { |
205 | qCDebug(KDIRWATCH) << "Can't use Inotify, kernel doesn't support it:" << strerror(errno); |
206 | } else { |
207 | availableMethods << "INotify" ; |
208 | (void)fcntl(fd: m_inotify_fd, F_SETFD, FD_CLOEXEC); |
209 | |
210 | mSn = new QSocketNotifier(m_inotify_fd, QSocketNotifier::Read, this); |
211 | connect(sender: mSn, signal: &QSocketNotifier::activated, context: this, slot: &KDirWatchPrivate::inotifyEventReceived); |
212 | } |
213 | #endif |
214 | #if HAVE_QFILESYSTEMWATCHER |
215 | availableMethods << "QFileSystemWatcher" ; |
216 | fsWatcher = nullptr; |
217 | #endif |
218 | |
219 | qCDebug(KDIRWATCH) << "Available methods: " << availableMethods << "preferred=" << methodToString(method: m_preferredMethod); |
220 | } |
221 | |
222 | // This is called on app exit (deleted by QThreadStorage) |
223 | KDirWatchPrivate::~KDirWatchPrivate() |
224 | { |
225 | m_statRescanTimer.stop(); |
226 | |
227 | // Unset us as d pointer. This indicates to the KDirWatch that the private has already been destroyed and therefore |
228 | // needs no additional cleanup from its destructor. |
229 | for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); it++) { |
230 | auto &entry = it.value(); |
231 | for (auto &client : entry.m_clients) { |
232 | client.instance->d = nullptr; |
233 | } |
234 | } |
235 | // Also cover all instance that hold a reference to us (even when they don't have an entry) |
236 | for (auto &referenceObject : m_referencesObjects) { |
237 | referenceObject->d = nullptr; |
238 | } |
239 | |
240 | #if HAVE_SYS_INOTIFY_H |
241 | if (supports_inotify) { |
242 | QT_CLOSE(fd: m_inotify_fd); |
243 | } |
244 | #endif |
245 | #if HAVE_QFILESYSTEMWATCHER |
246 | delete fsWatcher; |
247 | #endif |
248 | } |
249 | |
250 | void KDirWatchPrivate::inotifyEventReceived() |
251 | { |
252 | #if HAVE_SYS_INOTIFY_H |
253 | if (!supports_inotify) { |
254 | return; |
255 | } |
256 | |
257 | int pending = -1; |
258 | int offsetStartRead = 0; // where we read into buffer |
259 | char buf[8192]; |
260 | assert(m_inotify_fd > -1); |
261 | ioctl(fd: m_inotify_fd, FIONREAD, &pending); |
262 | |
263 | while (pending > 0) { |
264 | const int bytesToRead = qMin<int>(a: pending, b: sizeof(buf) - offsetStartRead); |
265 | |
266 | int bytesAvailable = read(fd: m_inotify_fd, buf: &buf[offsetStartRead], nbytes: bytesToRead); |
267 | pending -= bytesAvailable; |
268 | bytesAvailable += offsetStartRead; |
269 | offsetStartRead = 0; |
270 | |
271 | int offsetCurrent = 0; |
272 | while (bytesAvailable >= int(sizeof(struct inotify_event))) { |
273 | const struct inotify_event *const event = reinterpret_cast<inotify_event *>(&buf[offsetCurrent]); |
274 | |
275 | if (event->mask & IN_Q_OVERFLOW) { |
276 | qCWarning(KDIRWATCH) << "Inotify Event queue overflowed, check max_queued_events value" ; |
277 | return; |
278 | } |
279 | |
280 | const int eventSize = sizeof(struct inotify_event) + event->len; |
281 | if (bytesAvailable < eventSize) { |
282 | break; |
283 | } |
284 | |
285 | bytesAvailable -= eventSize; |
286 | offsetCurrent += eventSize; |
287 | |
288 | QString path; |
289 | // strip trailing null chars, see inotify_event documentation |
290 | // these must not end up in the final QString version of path |
291 | int len = event->len; |
292 | while (len > 1 && !event->name[len - 1]) { |
293 | --len; |
294 | } |
295 | QByteArray cpath(event->name, len); |
296 | if (len) { |
297 | path = QFile::decodeName(localFileName: cpath); |
298 | } |
299 | |
300 | if (!path.isEmpty() && isNoisyFile(filename: cpath.data())) { |
301 | continue; |
302 | } |
303 | |
304 | // Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir |
305 | const bool isDir = (event->mask & (IN_ISDIR)); |
306 | |
307 | Entry *e = m_inotify_wd_to_entry.value(key: event->wd); |
308 | if (!e) { |
309 | continue; |
310 | } |
311 | const bool wasDirty = e->dirty; |
312 | e->dirty = true; |
313 | |
314 | const QString tpath = e->path + QLatin1Char('/') + path; |
315 | |
316 | qCDebug(KDIRWATCH).nospace() << "got event " << inotifyEventName(event) << " for entry " << e->path |
317 | << (event->mask & IN_ISDIR ? " [directory] " : " [file] " ) << path; |
318 | |
319 | if (event->mask & IN_DELETE_SELF) { |
320 | e->m_status = NonExistent; |
321 | m_inotify_wd_to_entry.remove(key: e->wd); |
322 | e->wd = -1; |
323 | e->m_ctime = invalid_ctime; |
324 | emitEvent(e, event: Deleted); |
325 | // If the parent dir was already watched, tell it something changed |
326 | Entry *parentEntry = entry(path: e->parentDirectory()); |
327 | if (parentEntry) { |
328 | parentEntry->dirty = true; |
329 | } |
330 | // Add entry to parent dir to notice if the entry gets recreated |
331 | addEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e, isDir: true /*isDir*/); |
332 | } |
333 | if (event->mask & IN_IGNORED) { |
334 | // Causes bug #207361 with kernels 2.6.31 and 2.6.32! |
335 | // e->wd = -1; |
336 | } |
337 | if (event->mask & (IN_CREATE | IN_MOVED_TO)) { |
338 | Entry *sub_entry = e->findSubEntry(path: tpath); |
339 | |
340 | qCDebug(KDIRWATCH) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry; |
341 | |
342 | if (sub_entry) { |
343 | // We were waiting for this new file/dir to be created |
344 | sub_entry->dirty = true; |
345 | rescan_timer.start(msec: 0); // process this asap, to start watching that dir |
346 | } else if (e->isDir && !e->m_clients.empty()) { |
347 | const QList<const Client *> clients = e->inotifyClientsForFileOrDir(isDir); |
348 | // See discussion in addEntry for why we don't addEntry for individual |
349 | // files in WatchFiles mode with inotify. |
350 | if (isDir) { |
351 | for (const Client *client : clients) { |
352 | addEntry(instance: client->instance, path: tpath, sub_entry: nullptr, isDir, watchModes: isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); |
353 | } |
354 | } |
355 | if (!clients.isEmpty()) { |
356 | emitEvent(e, event: Created, fileName: tpath); |
357 | qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file " ) << tpath; |
358 | } |
359 | e->m_pendingFileChanges.append(t: e->path); |
360 | if (!rescan_timer.isActive()) { |
361 | rescan_timer.start(msec: m_PollInterval); // singleshot |
362 | } |
363 | } |
364 | } |
365 | if (event->mask & (IN_DELETE | IN_MOVED_FROM)) { |
366 | if ((e->isDir) && (!e->m_clients.empty())) { |
367 | // A file in this directory has been removed. It wasn't an explicitly |
368 | // watched file as it would have its own watch descriptor, so |
369 | // no addEntry/ removeEntry bookkeeping should be required. Emit |
370 | // the event immediately if any clients are interested. |
371 | const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; |
372 | int counter = std::count_if(first: e->m_clients.cbegin(), last: e->m_clients.cend(), pred: [flag](const Client &client) { |
373 | return client.m_watchModes & flag; |
374 | }); |
375 | |
376 | if (counter != 0) { |
377 | emitEvent(e, event: Deleted, fileName: tpath); |
378 | } |
379 | } |
380 | } |
381 | if (event->mask & (IN_MODIFY | IN_ATTRIB)) { |
382 | if ((e->isDir) && (!e->m_clients.empty())) { |
383 | // A file in this directory has been changed. No |
384 | // addEntry/ removeEntry bookkeeping should be required. |
385 | // Add the path to the list of pending file changes if |
386 | // there are any interested clients. |
387 | // QT_STATBUF stat_buf; |
388 | // QByteArray tpath = QFile::encodeName(e->path+'/'+path); |
389 | // QT_STAT(tpath, &stat_buf); |
390 | // bool isDir = S_ISDIR(stat_buf.st_mode); |
391 | |
392 | // The API doc is somewhat vague as to whether we should emit |
393 | // dirty() for implicitly watched files when WatchFiles has |
394 | // not been specified - we'll assume they are always interested, |
395 | // regardless. |
396 | // Don't worry about duplicates for the time |
397 | // being; this is handled in slotRescan. |
398 | e->m_pendingFileChanges.append(t: tpath); |
399 | // Avoid stat'ing the directory if only an entry inside it changed. |
400 | e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB))); |
401 | } |
402 | } |
403 | |
404 | if (!rescan_timer.isActive()) { |
405 | rescan_timer.start(msec: m_PollInterval); // singleshot |
406 | } |
407 | } |
408 | if (bytesAvailable > 0) { |
409 | // copy partial event to beginning of buffer |
410 | memmove(dest: buf, src: &buf[offsetCurrent], n: bytesAvailable); |
411 | offsetStartRead = bytesAvailable; |
412 | } |
413 | } |
414 | #endif |
415 | } |
416 | |
417 | KDirWatchPrivate::Entry::~Entry() |
418 | { |
419 | } |
420 | |
421 | /* In inotify mode, only entries which are marked dirty are scanned. |
422 | * We first need to mark all yet nonexistent, but possible created |
423 | * entries as dirty... |
424 | */ |
425 | void KDirWatchPrivate::Entry::propagate_dirty() |
426 | { |
427 | for (Entry *sub_entry : std::as_const(t&: m_entries)) { |
428 | if (!sub_entry->dirty) { |
429 | sub_entry->dirty = true; |
430 | sub_entry->propagate_dirty(); |
431 | } |
432 | } |
433 | } |
434 | |
435 | /* A KDirWatch instance is interested in getting events for |
436 | * this file/Dir entry. |
437 | */ |
438 | void KDirWatchPrivate::Entry::addClient(KDirWatch *instance, KDirWatch::WatchModes watchModes) |
439 | { |
440 | if (instance == nullptr) { |
441 | return; |
442 | } |
443 | |
444 | auto it = findInstance(other: instance); |
445 | if (it != m_clients.end()) { |
446 | Client &client = *it; |
447 | ++client.count; |
448 | client.m_watchModes = watchModes; |
449 | return; |
450 | } |
451 | |
452 | m_clients.emplace_back(args&: instance, args&: watchModes); |
453 | } |
454 | |
455 | void KDirWatchPrivate::Entry::removeClient(KDirWatch *instance) |
456 | { |
457 | auto it = findInstance(other: instance); |
458 | if (it != m_clients.end()) { |
459 | Client &client = *it; |
460 | --client.count; |
461 | if (client.count == 0) { |
462 | m_clients.erase(position: it); |
463 | } |
464 | } |
465 | } |
466 | |
467 | /* get number of clients */ |
468 | int KDirWatchPrivate::Entry::clientCount() const |
469 | { |
470 | int clients = 0; |
471 | for (const Client &client : m_clients) { |
472 | clients += client.count; |
473 | } |
474 | |
475 | return clients; |
476 | } |
477 | |
478 | QString KDirWatchPrivate::Entry::parentDirectory() const |
479 | { |
480 | return QDir::cleanPath(path: path + QLatin1String("/.." )); |
481 | } |
482 | |
483 | QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::clientsForFileOrDir(const QString &tpath, bool *isDir) const |
484 | { |
485 | QList<const Client *> ret; |
486 | QFileInfo fi(tpath); |
487 | if (fi.exists()) { |
488 | *isDir = fi.isDir(); |
489 | const KDirWatch::WatchModes flag = *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; |
490 | for (const Client &client : m_clients) { |
491 | if (client.m_watchModes & flag) { |
492 | ret.append(t: &client); |
493 | } |
494 | } |
495 | } else { |
496 | // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp" |
497 | // qCDebug(KDIRWATCH) << "ERROR: couldn't stat" << tpath; |
498 | // In this case isDir is not set, but ret is empty anyway |
499 | // so isDir won't be used. |
500 | } |
501 | return ret; |
502 | } |
503 | |
504 | // inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder. |
505 | // isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived |
506 | QList<const KDirWatchPrivate::Client *> KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const |
507 | { |
508 | QList<const Client *> ret; |
509 | const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; |
510 | for (const Client &client : m_clients) { |
511 | if (client.m_watchModes & flag) { |
512 | ret.append(t: &client); |
513 | } |
514 | } |
515 | return ret; |
516 | } |
517 | |
518 | QDebug operator<<(QDebug debug, const KDirWatch & /* watch */) |
519 | { |
520 | if (!dwp_self.hasLocalData()) { |
521 | debug << "KDirWatch not used" ; |
522 | return debug; |
523 | } |
524 | debug << dwp_self.localData(); |
525 | return debug; |
526 | } |
527 | |
528 | QDebug operator<<(QDebug debug, const KDirWatchPrivate &dwp) |
529 | { |
530 | debug << "Entries watched:" ; |
531 | if (dwp.m_mapEntries.count() == 0) { |
532 | debug << " None." ; |
533 | } else { |
534 | auto it = dwp.m_mapEntries.cbegin(); |
535 | for (; it != dwp.m_mapEntries.cend(); ++it) { |
536 | const KDirWatchPrivate::Entry &e = it.value(); |
537 | debug << " " << e; |
538 | |
539 | for (const KDirWatchPrivate::Client &c : e.m_clients) { |
540 | QByteArray pending; |
541 | if (c.watchingStopped) { |
542 | if (c.pending & KDirWatchPrivate::Deleted) { |
543 | pending += "deleted " ; |
544 | } |
545 | if (c.pending & KDirWatchPrivate::Created) { |
546 | pending += "created " ; |
547 | } |
548 | if (c.pending & KDirWatchPrivate::Changed) { |
549 | pending += "changed " ; |
550 | } |
551 | if (!pending.isEmpty()) { |
552 | pending = " (pending: " + pending + ')'; |
553 | } |
554 | pending = ", stopped" + pending; |
555 | } |
556 | debug << " by " << c.instance->objectName() << " (" << c.count << " times)" << pending; |
557 | } |
558 | if (!e.m_entries.isEmpty()) { |
559 | debug << " dependent entries:" ; |
560 | for (KDirWatchPrivate::Entry *d : e.m_entries) { |
561 | debug << " " << d << d->path << (d->m_status == KDirWatchPrivate::NonExistent ? "NonExistent" : "EXISTS this is an ERROR!" ); |
562 | if (s_verboseDebug) { |
563 | Q_ASSERT(d->m_status == KDirWatchPrivate::NonExistent); // it doesn't belong here otherwise |
564 | } |
565 | } |
566 | } |
567 | } |
568 | } |
569 | return debug; |
570 | } |
571 | |
572 | QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry) |
573 | { |
574 | debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file" ); |
575 | if (entry.m_status == KDirWatchPrivate::NonExistent) { |
576 | debug << ", non-existent" ; |
577 | } |
578 | debug << ", using " |
579 | << ((entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" |
580 | : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" |
581 | : (entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" |
582 | : "Unknown Method" ); |
583 | #if HAVE_SYS_INOTIFY_H |
584 | if (entry.m_mode == KDirWatchPrivate::INotifyMode) { |
585 | debug << " inotify_wd=" << entry.wd; |
586 | } |
587 | #endif |
588 | debug << ", has " << entry.m_clients.size() << " clients" ; |
589 | debug.space(); |
590 | if (!entry.m_entries.isEmpty()) { |
591 | debug << ", nonexistent subentries:" ; |
592 | for (KDirWatchPrivate::Entry *subEntry : std::as_const(t: entry.m_entries)) { |
593 | debug << subEntry << subEntry->path; |
594 | } |
595 | } |
596 | debug << ']'; |
597 | return debug; |
598 | } |
599 | |
600 | KDirWatchPrivate::Entry *KDirWatchPrivate::entry(const QString &_path) |
601 | { |
602 | if (_path.isEmpty()) { |
603 | return nullptr; |
604 | } |
605 | |
606 | QString path(_path); |
607 | |
608 | if (path.length() > 1 && path.endsWith(c: QLatin1Char('/'))) { |
609 | path.chop(n: 1); |
610 | } |
611 | |
612 | auto it = m_mapEntries.find(key: path); |
613 | return it != m_mapEntries.end() ? &it.value() : nullptr; |
614 | } |
615 | |
616 | // set polling frequency for a entry and adjust global freq if needed |
617 | void KDirWatchPrivate::useFreq(Entry *e, int newFreq) |
618 | { |
619 | e->freq = newFreq; |
620 | |
621 | // a reasonable frequency for the global polling timer |
622 | if (e->freq < freq) { |
623 | freq = e->freq; |
624 | if (m_statRescanTimer.isActive()) { |
625 | m_statRescanTimer.start(msec: freq); |
626 | } |
627 | qCDebug(KDIRWATCH) << "Global Poll Freq is now" << freq << "msec" ; |
628 | } |
629 | } |
630 | |
631 | #if HAVE_SYS_INOTIFY_H |
632 | // setup INotify notification, returns false if not possible |
633 | bool KDirWatchPrivate::useINotify(Entry *e) |
634 | { |
635 | e->wd = -1; |
636 | e->dirty = false; |
637 | |
638 | if (!supports_inotify) { |
639 | return false; |
640 | } |
641 | |
642 | e->m_mode = INotifyMode; |
643 | |
644 | if (e->m_status == NonExistent) { |
645 | addEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e, isDir: true); |
646 | return true; |
647 | } |
648 | |
649 | // May as well register for almost everything - it's free! |
650 | int mask = IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_MOVED_FROM | IN_MODIFY | IN_ATTRIB; |
651 | |
652 | if ((e->wd = inotify_add_watch(fd: m_inotify_fd, name: QFile::encodeName(fileName: e->path).data(), mask: mask)) != -1) { |
653 | m_inotify_wd_to_entry.insert(key: e->wd, value: e); |
654 | if (s_verboseDebug) { |
655 | qCDebug(KDIRWATCH) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd; |
656 | } |
657 | return true; |
658 | } |
659 | |
660 | if (errno == ENOSPC) { |
661 | // Inotify max_user_watches was reached (/proc/sys/fs/inotify/max_user_watches) |
662 | // See man inotify_add_watch, https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers |
663 | qCWarning(KDIRWATCH) << "inotify failed for monitoring" << e->path << "\n" |
664 | << "Because it reached its max_user_watches,\n" |
665 | << "you can increase the maximum number of file watches per user,\n" |
666 | << "by setting an appropriate fs.inotify.max_user_watches parameter in your /etc/sysctl.conf" ; |
667 | } else { |
668 | qCDebug(KDIRWATCH) << "inotify failed for monitoring" << e->path << ":" << strerror(errno) << " (errno:" << errno << ")" ; |
669 | } |
670 | return false; |
671 | } |
672 | #endif |
673 | #if HAVE_QFILESYSTEMWATCHER |
674 | bool KDirWatchPrivate::useQFSWatch(Entry *e) |
675 | { |
676 | e->m_mode = QFSWatchMode; |
677 | e->dirty = false; |
678 | |
679 | if (e->m_status == NonExistent) { |
680 | addEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e, isDir: true /*isDir*/); |
681 | return true; |
682 | } |
683 | |
684 | // qCDebug(KDIRWATCH) << "fsWatcher->addPath" << e->path; |
685 | if (!fsWatcher) { |
686 | fsWatcher = new QFileSystemWatcher(); |
687 | connect(sender: fsWatcher, signal: &QFileSystemWatcher::directoryChanged, context: this, slot: &KDirWatchPrivate::fswEventReceived); |
688 | connect(sender: fsWatcher, signal: &QFileSystemWatcher::fileChanged, context: this, slot: &KDirWatchPrivate::fswEventReceived); |
689 | } |
690 | fsWatcher->addPath(file: e->path); |
691 | return true; |
692 | } |
693 | #endif |
694 | |
695 | bool KDirWatchPrivate::useStat(Entry *e) |
696 | { |
697 | if (KFileSystemType::fileSystemType(path: e->path) == KFileSystemType::Nfs) { // TODO: or Smbfs? |
698 | useFreq(e, newFreq: m_nfsPollInterval); |
699 | } else { |
700 | useFreq(e, newFreq: m_PollInterval); |
701 | } |
702 | |
703 | if (e->m_mode != StatMode) { |
704 | e->m_mode = StatMode; |
705 | statEntries++; |
706 | |
707 | if (statEntries == 1) { |
708 | // if this was first STAT entry (=timer was stopped) |
709 | m_statRescanTimer.start(msec: freq); // then start the timer |
710 | qCDebug(KDIRWATCH) << " Started Polling Timer, freq " << freq; |
711 | } |
712 | } |
713 | |
714 | qCDebug(KDIRWATCH) << " Setup Stat (freq " << e->freq << ") for " << e->path; |
715 | |
716 | return true; |
717 | } |
718 | |
719 | /* If <instance> !=0, this KDirWatch instance wants to watch at <_path>, |
720 | * providing in <isDir> the type of the entry to be watched. |
721 | * Sometimes, entries are dependent on each other: if <sub_entry> !=0, |
722 | * this entry needs another entry to watch itself (when notExistent). |
723 | */ |
724 | void KDirWatchPrivate::addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes) |
725 | { |
726 | QString path(_path); |
727 | if (path.startsWith(s: QLatin1String(":/" ))) { |
728 | qCWarning(KDIRWATCH) << "Cannot watch QRC-like path" << path; |
729 | return; |
730 | } |
731 | if (path.isEmpty() |
732 | #ifndef Q_OS_WIN |
733 | || path == QLatin1String("/dev" ) |
734 | || (path.startsWith(s: QLatin1String("/dev/" )) && !path.startsWith(s: QLatin1String("/dev/." )) && !path.startsWith(s: QLatin1String("/dev/shm" ))) |
735 | #endif |
736 | ) { |
737 | return; // Don't even go there. |
738 | } |
739 | |
740 | if (path.length() > 1 && path.endsWith(c: QLatin1Char('/'))) { |
741 | path.chop(n: 1); |
742 | } |
743 | |
744 | auto it = m_mapEntries.find(key: path); |
745 | if (it != m_mapEntries.end()) { |
746 | Entry &entry = it.value(); |
747 | if (sub_entry) { |
748 | entry.m_entries.append(t: sub_entry); |
749 | if (s_verboseDebug) { |
750 | qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(for" << sub_entry->path << ")" ; |
751 | } |
752 | } else { |
753 | entry.addClient(instance, watchModes); |
754 | if (s_verboseDebug) { |
755 | qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(now" << entry.clientCount() << "clients)" |
756 | << QStringLiteral("[%1]" ).arg(a: instance->objectName()); |
757 | } |
758 | } |
759 | return; |
760 | } |
761 | |
762 | // we have a new path to watch |
763 | |
764 | QT_STATBUF stat_buf; |
765 | bool exists = (QT_STAT(file: QFile::encodeName(fileName: path).constData(), buf: &stat_buf) == 0); |
766 | |
767 | auto newIt = m_mapEntries.insert(key: path, value: Entry()); |
768 | // the insert does a copy, so we have to use <e> now |
769 | Entry *e = &(*newIt); |
770 | |
771 | if (exists) { |
772 | e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR; |
773 | |
774 | #ifndef Q_OS_WIN |
775 | if (e->isDir && !isDir) { |
776 | if (QT_LSTAT(file: QFile::encodeName(fileName: path).constData(), buf: &stat_buf) == 0) { |
777 | if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { |
778 | // if it's a symlink, don't follow it |
779 | e->isDir = false; |
780 | } |
781 | } |
782 | } |
783 | #endif |
784 | |
785 | if (e->isDir && !isDir) { |
786 | qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a directory. Use addDir!" ; |
787 | } else if (!e->isDir && isDir) { |
788 | qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. Use addFile!" ; |
789 | } |
790 | |
791 | if (!e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { |
792 | qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path |
793 | << "is a file. You can't use recursive or " |
794 | "watchFiles options" ; |
795 | watchModes = KDirWatch::WatchDirOnly; |
796 | } |
797 | |
798 | #ifdef Q_OS_WIN |
799 | // ctime is the 'creation time' on windows - use mtime instead |
800 | e->m_ctime = stat_buf.st_mtime; |
801 | #else |
802 | e->m_ctime = stat_buf.st_ctime; |
803 | #endif |
804 | e->m_status = Normal; |
805 | e->m_nlink = stat_buf.st_nlink; |
806 | e->m_ino = stat_buf.st_ino; |
807 | } else { |
808 | e->isDir = isDir; |
809 | e->m_ctime = invalid_ctime; |
810 | e->m_status = NonExistent; |
811 | e->m_nlink = 0; |
812 | e->m_ino = 0; |
813 | } |
814 | |
815 | e->path = path; |
816 | if (sub_entry) { |
817 | e->m_entries.append(t: sub_entry); |
818 | } else { |
819 | e->addClient(instance, watchModes); |
820 | } |
821 | |
822 | if (s_verboseDebug) { |
823 | qCDebug(KDIRWATCH).nospace() << "Added " << (e->isDir ? "Dir " : "File " ) << path << (e->m_status == NonExistent ? " NotExisting" : "" ) << " for " |
824 | << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]" ; |
825 | } |
826 | |
827 | // now setup the notification method |
828 | e->m_mode = UnknownMode; |
829 | e->msecLeft = 0; |
830 | |
831 | if (isNoisyFile(filename: QFile::encodeName(fileName: path).data())) { |
832 | return; |
833 | } |
834 | |
835 | if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { |
836 | // recursive watch for folders |
837 | QFlags<QDir::Filter> filters = QDir::NoDotAndDotDot; |
838 | |
839 | if ((watchModes & KDirWatch::WatchSubDirs) && (watchModes & KDirWatch::WatchFiles)) { |
840 | filters |= (QDir::Dirs | QDir::Files); |
841 | } else if (watchModes & KDirWatch::WatchSubDirs) { |
842 | filters |= QDir::Dirs; |
843 | } else if (watchModes & KDirWatch::WatchFiles) { |
844 | filters |= QDir::Files; |
845 | } |
846 | |
847 | #if HAVE_SYS_INOTIFY_H |
848 | if (m_preferredMethod == KDirWatch::INotify) { |
849 | // qCDebug(KDIRWATCH) << "Ignoring WatchFiles directive - this is implicit with inotify"; |
850 | // Placing a watch on individual files is redundant with inotify |
851 | // (inotify gives us WatchFiles functionality "for free") and indeed |
852 | // actively harmful, so prevent it. WatchSubDirs is necessary, though. |
853 | filters &= ~QDir::Files; |
854 | } |
855 | #endif |
856 | |
857 | QDir basedir(e->path); |
858 | const QFileInfoList contents = basedir.entryInfoList(filters); |
859 | for (const QFileInfo &fileInfo : contents) { |
860 | // treat symlinks as files--don't follow them. |
861 | bool isDir = fileInfo.isDir() && !fileInfo.isSymLink(); |
862 | |
863 | addEntry(instance, path: fileInfo.absoluteFilePath(), sub_entry: nullptr, isDir, watchModes: isDir ? watchModes : KDirWatch::WatchDirOnly); |
864 | } |
865 | } |
866 | |
867 | addWatch(entry: e); |
868 | } |
869 | |
870 | void KDirWatchPrivate::addWatch(Entry *e) |
871 | { |
872 | // If the watch is on a network filesystem use the nfsPreferredMethod as the |
873 | // default, otherwise use preferredMethod as the default, if the methods are |
874 | // the same we can skip the mountpoint check |
875 | |
876 | // This allows to configure a different method for NFS mounts, since inotify |
877 | // cannot detect changes made by other machines. However as a default inotify |
878 | // is fine, since the most common case is a NFS-mounted home, where all changes |
879 | // are made locally. #177892. |
880 | |
881 | KDirWatch::Method preferredMethod = m_preferredMethod; |
882 | if (m_nfsPreferredMethod != m_preferredMethod) { |
883 | if (KFileSystemType::fileSystemType(path: e->path) == KFileSystemType::Nfs) { |
884 | preferredMethod = m_nfsPreferredMethod; |
885 | } |
886 | } |
887 | |
888 | // Try the appropriate preferred method from the config first |
889 | bool entryAdded = false; |
890 | switch (preferredMethod) { |
891 | #if HAVE_SYS_INOTIFY_H |
892 | case KDirWatch::INotify: |
893 | entryAdded = useINotify(e); |
894 | break; |
895 | #else |
896 | case KDirWatch::INotify: |
897 | entryAdded = false; |
898 | break; |
899 | #endif |
900 | #if HAVE_QFILESYSTEMWATCHER |
901 | case KDirWatch::QFSWatch: |
902 | entryAdded = useQFSWatch(e); |
903 | break; |
904 | #else |
905 | case KDirWatch::QFSWatch: |
906 | entryAdded = false; |
907 | break; |
908 | #endif |
909 | case KDirWatch::Stat: |
910 | entryAdded = useStat(e); |
911 | break; |
912 | } |
913 | |
914 | // Failing that try in order INotify, QFSWatch, Stat |
915 | if (!entryAdded) { |
916 | #if HAVE_SYS_INOTIFY_H |
917 | if (preferredMethod != KDirWatch::INotify && useINotify(e)) { |
918 | return; |
919 | } |
920 | #endif |
921 | #if HAVE_QFILESYSTEMWATCHER |
922 | if (preferredMethod != KDirWatch::QFSWatch && useQFSWatch(e)) { |
923 | return; |
924 | } |
925 | #endif |
926 | if (preferredMethod != KDirWatch::Stat) { |
927 | useStat(e); |
928 | } |
929 | } |
930 | } |
931 | |
932 | void KDirWatchPrivate::removeWatch(Entry *e) |
933 | { |
934 | #if HAVE_SYS_INOTIFY_H |
935 | if (e->m_mode == INotifyMode) { |
936 | m_inotify_wd_to_entry.remove(key: e->wd); |
937 | (void)inotify_rm_watch(fd: m_inotify_fd, wd: e->wd); |
938 | if (s_verboseDebug) { |
939 | qCDebug(KDIRWATCH).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", " << e->wd << ") for " << e->path; |
940 | } |
941 | } |
942 | #endif |
943 | #if HAVE_QFILESYSTEMWATCHER |
944 | if (e->m_mode == QFSWatchMode && fsWatcher) { |
945 | if (s_verboseDebug) { |
946 | qCDebug(KDIRWATCH) << "fsWatcher->removePath" << e->path; |
947 | } |
948 | fsWatcher->removePath(file: e->path); |
949 | } |
950 | #endif |
951 | } |
952 | |
953 | void KDirWatchPrivate::removeEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry) |
954 | { |
955 | qCDebug(KDIRWATCH) << "path=" << _path << "sub_entry:" << sub_entry; |
956 | |
957 | Entry *e = entry(_path); |
958 | if (e) { |
959 | removeEntry(instance, e, sub_entry); |
960 | } |
961 | } |
962 | |
963 | void KDirWatchPrivate::removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry) |
964 | { |
965 | removeList.remove(value: e); |
966 | |
967 | if (sub_entry) { |
968 | e->m_entries.removeAll(t: sub_entry); |
969 | } else { |
970 | e->removeClient(instance); |
971 | } |
972 | |
973 | if (!e->m_clients.empty() || !e->m_entries.empty()) { |
974 | return; |
975 | } |
976 | |
977 | if (delayRemove) { |
978 | removeList.insert(value: e); |
979 | // now e->isValid() is false |
980 | return; |
981 | } |
982 | |
983 | if (e->m_status == Normal) { |
984 | removeWatch(e); |
985 | } else { |
986 | // Removed a NonExistent entry - we just remove it from the parent |
987 | if (e->isDir) { |
988 | removeEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e); |
989 | } else { |
990 | removeEntry(instance: nullptr, path: QFileInfo(e->path).absolutePath(), sub_entry: e); |
991 | } |
992 | } |
993 | |
994 | if (e->m_mode == StatMode) { |
995 | statEntries--; |
996 | if (statEntries == 0) { |
997 | m_statRescanTimer.stop(); // stop timer if lists are empty |
998 | qCDebug(KDIRWATCH) << " Stopped Polling Timer" ; |
999 | } |
1000 | } |
1001 | |
1002 | if (s_verboseDebug) { |
1003 | qCDebug(KDIRWATCH).nospace() << "Removed " << (e->isDir ? "Dir " : "File " ) << e->path << " for " << (sub_entry ? sub_entry->path : QString()) << " [" |
1004 | << (instance ? instance->objectName() : QString()) << "]" ; |
1005 | } |
1006 | QString p = e->path; // take a copy, QMap::remove takes a reference and deletes, since e points into the map |
1007 | #if HAVE_SYS_INOTIFY_H |
1008 | m_inotify_wd_to_entry.remove(key: e->wd); |
1009 | #endif |
1010 | m_mapEntries.remove(key: p); // <e> not valid any more |
1011 | } |
1012 | |
1013 | /* Called from KDirWatch destructor: |
1014 | * remove <instance> as client from all entries |
1015 | */ |
1016 | void KDirWatchPrivate::removeEntries(KDirWatch *instance) |
1017 | { |
1018 | int minfreq = 3600000; |
1019 | |
1020 | QStringList pathList; |
1021 | // put all entries where instance is a client in list |
1022 | for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { |
1023 | Entry &entry = it.value(); |
1024 | auto clientIt = entry.findInstance(other: instance); |
1025 | if (clientIt != entry.m_clients.end()) { |
1026 | clientIt->count = 1; // forces deletion of instance as client |
1027 | pathList.append(t: entry.path); |
1028 | } else if (entry.m_mode == StatMode && entry.freq < minfreq) { |
1029 | minfreq = entry.freq; |
1030 | } |
1031 | } |
1032 | |
1033 | for (const QString &path : std::as_const(t&: pathList)) { |
1034 | removeEntry(instance, path: path, sub_entry: nullptr); |
1035 | } |
1036 | |
1037 | if (minfreq > freq) { |
1038 | // we can decrease the global polling frequency |
1039 | freq = minfreq; |
1040 | if (m_statRescanTimer.isActive()) { |
1041 | m_statRescanTimer.start(msec: freq); |
1042 | } |
1043 | qCDebug(KDIRWATCH) << "Poll Freq now" << freq << "msec" ; |
1044 | } |
1045 | } |
1046 | |
1047 | // instance ==0: stop scanning for all instances |
1048 | bool KDirWatchPrivate::stopEntryScan(KDirWatch *instance, Entry *e) |
1049 | { |
1050 | int stillWatching = 0; |
1051 | for (Client &client : e->m_clients) { |
1052 | if (!instance || instance == client.instance) { |
1053 | client.watchingStopped = true; |
1054 | } else if (!client.watchingStopped) { |
1055 | stillWatching += client.count; |
1056 | } |
1057 | } |
1058 | |
1059 | qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all" )) << "stopped scanning" << e->path << "(now" << stillWatching |
1060 | << "watchers)" ; |
1061 | |
1062 | if (stillWatching == 0) { |
1063 | // if nobody is interested, we don't watch, and we don't report |
1064 | // changes that happened while not watching |
1065 | e->m_ctime = invalid_ctime; // invalid |
1066 | |
1067 | // Changing m_status like this would create wrong "created" events in stat mode. |
1068 | // To really "stop watching" we would need to determine 'stillWatching==0' in scanEntry... |
1069 | // e->m_status = NonExistent; |
1070 | } |
1071 | return true; |
1072 | } |
1073 | |
1074 | // instance ==0: start scanning for all instances |
1075 | bool KDirWatchPrivate::restartEntryScan(KDirWatch *instance, Entry *e, bool notify) |
1076 | { |
1077 | int wasWatching = 0; |
1078 | int newWatching = 0; |
1079 | for (Client &client : e->m_clients) { |
1080 | if (!client.watchingStopped) { |
1081 | wasWatching += client.count; |
1082 | } else if (!instance || instance == client.instance) { |
1083 | client.watchingStopped = false; |
1084 | newWatching += client.count; |
1085 | } |
1086 | } |
1087 | if (newWatching == 0) { |
1088 | return false; |
1089 | } |
1090 | |
1091 | qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all" )) << "restarted scanning" << e->path << "(now" << wasWatching + newWatching |
1092 | << "watchers)" ; |
1093 | |
1094 | // restart watching and emit pending events |
1095 | |
1096 | int ev = NoChange; |
1097 | if (wasWatching == 0) { |
1098 | if (!notify) { |
1099 | QT_STATBUF stat_buf; |
1100 | bool exists = (QT_STAT(file: QFile::encodeName(fileName: e->path).constData(), buf: &stat_buf) == 0); |
1101 | if (exists) { |
1102 | // ctime is the 'creation time' on windows, but with qMax |
1103 | // we get the latest change of any kind, on any platform. |
1104 | e->m_ctime = qMax(a: stat_buf.st_ctime, b: stat_buf.st_mtime); |
1105 | e->m_status = Normal; |
1106 | if (s_verboseDebug) { |
1107 | qCDebug(KDIRWATCH) << "Setting status to Normal for" << e << e->path; |
1108 | } |
1109 | e->m_nlink = stat_buf.st_nlink; |
1110 | e->m_ino = stat_buf.st_ino; |
1111 | |
1112 | // Same as in scanEntry: ensure no subentry in parent dir |
1113 | removeEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e); |
1114 | } else { |
1115 | e->m_ctime = invalid_ctime; |
1116 | e->m_status = NonExistent; |
1117 | e->m_nlink = 0; |
1118 | if (s_verboseDebug) { |
1119 | qCDebug(KDIRWATCH) << "Setting status to NonExistent for" << e << e->path; |
1120 | } |
1121 | } |
1122 | } |
1123 | e->msecLeft = 0; |
1124 | ev = scanEntry(e); |
1125 | } |
1126 | emitEvent(e, event: ev); |
1127 | |
1128 | return true; |
1129 | } |
1130 | |
1131 | // instance ==0: stop scanning for all instances |
1132 | void KDirWatchPrivate::stopScan(KDirWatch *instance) |
1133 | { |
1134 | for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { |
1135 | stopEntryScan(instance, e: &it.value()); |
1136 | } |
1137 | } |
1138 | |
1139 | void KDirWatchPrivate::startScan(KDirWatch *instance, bool notify, bool skippedToo) |
1140 | { |
1141 | if (!notify) { |
1142 | resetList(instance, skippedToo); |
1143 | } |
1144 | |
1145 | for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { |
1146 | restartEntryScan(instance, e: &it.value(), notify); |
1147 | } |
1148 | |
1149 | // timer should still be running when in polling mode |
1150 | } |
1151 | |
1152 | // clear all pending events, also from stopped |
1153 | void KDirWatchPrivate::resetList(KDirWatch *instance, bool skippedToo) |
1154 | { |
1155 | Q_UNUSED(instance); |
1156 | |
1157 | for (auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) { |
1158 | for (Client &client : it.value().m_clients) { |
1159 | if (!client.watchingStopped || skippedToo) { |
1160 | client.pending = NoChange; |
1161 | } |
1162 | } |
1163 | } |
1164 | } |
1165 | |
1166 | // Return event happened on <e> |
1167 | // |
1168 | int KDirWatchPrivate::scanEntry(Entry *e) |
1169 | { |
1170 | // Shouldn't happen: Ignore "unknown" notification method |
1171 | if (e->m_mode == UnknownMode) { |
1172 | return NoChange; |
1173 | } |
1174 | |
1175 | if (e->m_mode == INotifyMode) { |
1176 | // we know nothing has changed, no need to stat |
1177 | if (!e->dirty) { |
1178 | return NoChange; |
1179 | } |
1180 | e->dirty = false; |
1181 | } |
1182 | |
1183 | if (e->m_mode == StatMode) { |
1184 | // only scan if timeout on entry timer happens; |
1185 | // e.g. when using 500msec global timer, a entry |
1186 | // with freq=5000 is only watched every 10th time |
1187 | |
1188 | e->msecLeft -= freq; |
1189 | if (e->msecLeft > 0) { |
1190 | return NoChange; |
1191 | } |
1192 | e->msecLeft += e->freq; |
1193 | } |
1194 | |
1195 | QT_STATBUF stat_buf; |
1196 | const bool exists = (QT_STAT(file: QFile::encodeName(fileName: e->path).constData(), buf: &stat_buf) == 0); |
1197 | if (exists) { |
1198 | if (e->m_status == NonExistent) { |
1199 | // ctime is the 'creation time' on windows, but with qMax |
1200 | // we get the latest change of any kind, on any platform. |
1201 | e->m_ctime = qMax(a: stat_buf.st_ctime, b: stat_buf.st_mtime); |
1202 | e->m_status = Normal; |
1203 | e->m_ino = stat_buf.st_ino; |
1204 | if (s_verboseDebug) { |
1205 | qCDebug(KDIRWATCH) << "Setting status to Normal for just created" << e << e->path; |
1206 | } |
1207 | // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo) |
1208 | removeEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e); |
1209 | |
1210 | return Created; |
1211 | } |
1212 | |
1213 | #if 1 // for debugging the if() below |
1214 | if (s_verboseDebug) { |
1215 | struct tm *tmp = localtime(timer: &e->m_ctime); |
1216 | char outstr[200]; |
1217 | strftime(s: outstr, maxsize: sizeof(outstr), format: "%H:%M:%S" , tp: tmp); |
1218 | qCDebug(KDIRWATCH) << e->path << "e->m_ctime=" << e->m_ctime << outstr << "stat_buf.st_ctime=" << stat_buf.st_ctime |
1219 | << "stat_buf.st_mtime=" << stat_buf.st_mtime << "e->m_nlink=" << e->m_nlink << "stat_buf.st_nlink=" << stat_buf.st_nlink |
1220 | << "e->m_ino=" << e->m_ino << "stat_buf.st_ino=" << stat_buf.st_ino; |
1221 | } |
1222 | #endif |
1223 | |
1224 | if ((e->m_ctime != invalid_ctime) |
1225 | && (qMax(a: stat_buf.st_ctime, b: stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino |
1226 | || int(stat_buf.st_nlink) != int(e->m_nlink) |
1227 | #ifdef Q_OS_WIN |
1228 | // on Windows, we trust QFSW to get it right, the ctime comparisons above |
1229 | // fail for example when adding files to directories on Windows |
1230 | // which doesn't change the mtime of the directory |
1231 | || e->m_mode == QFSWatchMode |
1232 | #endif |
1233 | )) { |
1234 | e->m_ctime = qMax(a: stat_buf.st_ctime, b: stat_buf.st_mtime); |
1235 | e->m_nlink = stat_buf.st_nlink; |
1236 | if (e->m_ino != stat_buf.st_ino) { |
1237 | // The file got deleted and recreated. We need to watch it again. |
1238 | removeWatch(e); |
1239 | addWatch(e); |
1240 | e->m_ino = stat_buf.st_ino; |
1241 | return (Deleted | Created); |
1242 | } else { |
1243 | return Changed; |
1244 | } |
1245 | } |
1246 | |
1247 | return NoChange; |
1248 | } |
1249 | |
1250 | // dir/file doesn't exist |
1251 | |
1252 | e->m_nlink = 0; |
1253 | e->m_ino = 0; |
1254 | e->m_status = NonExistent; |
1255 | |
1256 | if (e->m_ctime == invalid_ctime) { |
1257 | return NoChange; |
1258 | } |
1259 | |
1260 | e->m_ctime = invalid_ctime; |
1261 | return Deleted; |
1262 | } |
1263 | |
1264 | /* Notify all interested KDirWatch instances about a given event on an entry |
1265 | * and stored pending events. When watching is stopped, the event is |
1266 | * added to the pending events. |
1267 | */ |
1268 | void KDirWatchPrivate::emitEvent(Entry *e, int event, const QString &fileName) |
1269 | { |
1270 | QString path(e->path); |
1271 | if (!fileName.isEmpty()) { |
1272 | if (!QDir::isRelativePath(path: fileName)) { |
1273 | path = fileName; |
1274 | } else { |
1275 | #ifdef Q_OS_UNIX |
1276 | path += QLatin1Char('/') + fileName; |
1277 | #elif defined(Q_OS_WIN) |
1278 | // current drive is passed instead of / |
1279 | path += QStringView(QDir::currentPath()).left(2) + QLatin1Char('/') + fileName; |
1280 | #endif |
1281 | } |
1282 | } |
1283 | |
1284 | if (s_verboseDebug) { |
1285 | qCDebug(KDIRWATCH) << event << path << e->m_clients.size() << "clients" ; |
1286 | } |
1287 | |
1288 | for (Client &c : e->m_clients) { |
1289 | if (c.instance == nullptr || c.count == 0) { |
1290 | continue; |
1291 | } |
1292 | |
1293 | if (c.watchingStopped) { |
1294 | // Do not add event to a list of pending events, the docs say restartDirScan won't emit! |
1295 | continue; |
1296 | } |
1297 | // not stopped |
1298 | if (event == NoChange || event == Changed) { |
1299 | event |= c.pending; |
1300 | } |
1301 | c.pending = NoChange; |
1302 | if (event == NoChange) { |
1303 | continue; |
1304 | } |
1305 | |
1306 | // Emit the signals delayed, to avoid unexpected re-entrance from the slots (#220153) |
1307 | |
1308 | if (event & Deleted) { |
1309 | QMetaObject::invokeMethod( |
1310 | object: c.instance, |
1311 | function: [c, path]() { |
1312 | c.instance->setDeleted(path); |
1313 | }, |
1314 | type: Qt::QueuedConnection); |
1315 | } |
1316 | |
1317 | if (event & Created) { |
1318 | QMetaObject::invokeMethod( |
1319 | object: c.instance, |
1320 | function: [c, path]() { |
1321 | c.instance->setCreated(path); |
1322 | }, |
1323 | type: Qt::QueuedConnection); |
1324 | // possible emit Change event after creation |
1325 | } |
1326 | |
1327 | if (event & Changed) { |
1328 | QMetaObject::invokeMethod( |
1329 | object: c.instance, |
1330 | function: [c, path]() { |
1331 | c.instance->setDirty(path); |
1332 | }, |
1333 | type: Qt::QueuedConnection); |
1334 | } |
1335 | } |
1336 | } |
1337 | |
1338 | // Remove entries which were marked to be removed |
1339 | void KDirWatchPrivate::slotRemoveDelayed() |
1340 | { |
1341 | delayRemove = false; |
1342 | // Removing an entry could also take care of removing its parent |
1343 | // (e.g. in inotify mode), which would remove other entries in removeList, |
1344 | // so don't use Q_FOREACH or iterators here... |
1345 | while (!removeList.isEmpty()) { |
1346 | Entry *entry = *removeList.begin(); |
1347 | removeEntry(instance: nullptr, e: entry, sub_entry: nullptr); // this will remove entry from removeList |
1348 | } |
1349 | } |
1350 | |
1351 | /* Scan all entries to be watched for changes. This is done regularly |
1352 | * when polling. inotify uses a single-shot timer to call this slot delayed. |
1353 | */ |
1354 | void KDirWatchPrivate::slotRescan() |
1355 | { |
1356 | if (s_verboseDebug) { |
1357 | qCDebug(KDIRWATCH); |
1358 | } |
1359 | |
1360 | EntryMap::Iterator it; |
1361 | |
1362 | // People can do very long things in the slot connected to dirty(), |
1363 | // like showing a message box. We don't want to keep polling during |
1364 | // that time, otherwise the value of 'delayRemove' will be reset. |
1365 | // ### TODO: now the emitEvent delays emission, this can be cleaned up |
1366 | bool timerRunning = m_statRescanTimer.isActive(); |
1367 | if (timerRunning) { |
1368 | m_statRescanTimer.stop(); |
1369 | } |
1370 | |
1371 | // We delay deletions of entries this way. |
1372 | // removeDir(), when called in slotDirty(), can cause a crash otherwise |
1373 | // ### TODO: now the emitEvent delays emission, this can be cleaned up |
1374 | delayRemove = true; |
1375 | |
1376 | if (rescan_all) { |
1377 | // mark all as dirty |
1378 | it = m_mapEntries.begin(); |
1379 | for (; it != m_mapEntries.end(); ++it) { |
1380 | (*it).dirty = true; |
1381 | } |
1382 | rescan_all = false; |
1383 | } else { |
1384 | // propagate dirty flag to dependent entries (e.g. file watches) |
1385 | it = m_mapEntries.begin(); |
1386 | for (; it != m_mapEntries.end(); ++it) { |
1387 | if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) { |
1388 | (*it).propagate_dirty(); |
1389 | } |
1390 | } |
1391 | } |
1392 | |
1393 | #if HAVE_SYS_INOTIFY_H |
1394 | QList<Entry *> cList; |
1395 | #endif |
1396 | |
1397 | it = m_mapEntries.begin(); |
1398 | for (; it != m_mapEntries.end(); ++it) { |
1399 | // we don't check invalid entries (i.e. remove delayed) |
1400 | Entry *entry = &(*it); |
1401 | if (!entry->isValid()) { |
1402 | continue; |
1403 | } |
1404 | |
1405 | const int ev = scanEntry(e: entry); |
1406 | if (s_verboseDebug) { |
1407 | qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; |
1408 | } |
1409 | |
1410 | switch (entry->m_mode) { |
1411 | #if HAVE_SYS_INOTIFY_H |
1412 | case INotifyMode: |
1413 | if (ev == Deleted) { |
1414 | if (s_verboseDebug) { |
1415 | qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was deleted" ; |
1416 | } |
1417 | addEntry(instance: nullptr, path: entry->parentDirectory(), sub_entry: entry, isDir: true); |
1418 | } else if (ev == Created) { |
1419 | if (s_verboseDebug) { |
1420 | qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd; |
1421 | } |
1422 | if (entry->wd < 0) { |
1423 | cList.append(t: entry); |
1424 | addWatch(e: entry); |
1425 | } |
1426 | } |
1427 | break; |
1428 | #endif |
1429 | case QFSWatchMode: |
1430 | if (ev == Created) { |
1431 | addWatch(e: entry); |
1432 | } |
1433 | break; |
1434 | default: |
1435 | // dunno about StatMode... |
1436 | break; |
1437 | } |
1438 | |
1439 | #if HAVE_SYS_INOTIFY_H |
1440 | if (entry->isDir) { |
1441 | // Report and clear the list of files that have changed in this directory. |
1442 | // Remove duplicates by changing to set and back again: |
1443 | // we don't really care about preserving the order of the |
1444 | // original changes. |
1445 | QStringList pendingFileChanges = entry->m_pendingFileChanges; |
1446 | pendingFileChanges.removeDuplicates(); |
1447 | for (const QString &changedFilename : std::as_const(t&: pendingFileChanges)) { |
1448 | if (s_verboseDebug) { |
1449 | qCDebug(KDIRWATCH) << "processing pending file change for" << changedFilename; |
1450 | } |
1451 | emitEvent(e: entry, event: Changed, fileName: changedFilename); |
1452 | } |
1453 | entry->m_pendingFileChanges.clear(); |
1454 | } |
1455 | #endif |
1456 | |
1457 | if (ev != NoChange) { |
1458 | emitEvent(e: entry, event: ev); |
1459 | } |
1460 | } |
1461 | |
1462 | if (timerRunning) { |
1463 | m_statRescanTimer.start(msec: freq); |
1464 | } |
1465 | |
1466 | #if HAVE_SYS_INOTIFY_H |
1467 | // Remove watch of parent of new created directories |
1468 | for (Entry *e : std::as_const(t&: cList)) { |
1469 | removeEntry(instance: nullptr, path: e->parentDirectory(), sub_entry: e); |
1470 | } |
1471 | #endif |
1472 | |
1473 | QTimer::singleShot(interval: 0, receiver: this, slot: &KDirWatchPrivate::slotRemoveDelayed); |
1474 | } |
1475 | |
1476 | bool KDirWatchPrivate::isNoisyFile(const char *filename) |
1477 | { |
1478 | // $HOME/.X.err grows with debug output, so don't notify change |
1479 | if (*filename == '.') { |
1480 | if (strncmp(s1: filename, s2: ".X.err" , n: 6) == 0) { |
1481 | return true; |
1482 | } |
1483 | if (strncmp(s1: filename, s2: ".xsession-errors" , n: 16) == 0) { |
1484 | return true; |
1485 | } |
1486 | // fontconfig updates the cache on every KDE app start |
1487 | // as well as during kio_thumbnail worker execution |
1488 | // TODO:; check which fontconfig version this file was deprecated and the check can be removed |
1489 | if (strncmp(s1: filename, s2: ".fonts.cache" , n: 12) == 0) { |
1490 | return true; |
1491 | } |
1492 | } |
1493 | |
1494 | return false; |
1495 | } |
1496 | |
1497 | void KDirWatchPrivate::ref(KDirWatch *watch) |
1498 | { |
1499 | m_referencesObjects.push_back(t: watch); |
1500 | } |
1501 | |
1502 | void KDirWatchPrivate::unref(KDirWatch *watch) |
1503 | { |
1504 | m_referencesObjects.removeOne(t: watch); |
1505 | if (m_referencesObjects.isEmpty()) { |
1506 | destroyPrivate(); |
1507 | } |
1508 | } |
1509 | |
1510 | #if HAVE_SYS_INOTIFY_H |
1511 | QString KDirWatchPrivate::inotifyEventName(const inotify_event *event) const |
1512 | { |
1513 | if (event->mask & IN_OPEN) |
1514 | return QStringLiteral("OPEN" ); |
1515 | else if (event->mask & IN_CLOSE_NOWRITE) |
1516 | return QStringLiteral("CLOSE_NOWRITE" ); |
1517 | else if (event->mask & IN_CLOSE_WRITE) |
1518 | return QStringLiteral("CLOSE_WRITE" ); |
1519 | else if (event->mask & IN_MOVED_TO) |
1520 | return QStringLiteral("MOVED_TO" ); |
1521 | else if (event->mask & IN_MOVED_FROM) |
1522 | return QStringLiteral("MOVED_FROM" ); |
1523 | else if (event->mask & IN_MOVE) |
1524 | return QStringLiteral("MOVE" ); |
1525 | else if (event->mask & IN_CREATE) |
1526 | return QStringLiteral("CREATE" ); |
1527 | else if (event->mask & IN_DELETE) |
1528 | return QStringLiteral("DELETE" ); |
1529 | else if (event->mask & IN_DELETE_SELF) |
1530 | return QStringLiteral("DELETE_SELF" ); |
1531 | else if (event->mask & IN_MOVE_SELF) |
1532 | return QStringLiteral("MOVE_SELF" ); |
1533 | else if (event->mask & IN_ATTRIB) |
1534 | return QStringLiteral("ATTRIB" ); |
1535 | else if (event->mask & IN_MODIFY) |
1536 | return QStringLiteral("MODIFY" ); |
1537 | if (event->mask & IN_ACCESS) |
1538 | return QStringLiteral("ACCESS" ); |
1539 | if (event->mask & IN_IGNORED) |
1540 | return QStringLiteral("IGNORED" ); |
1541 | if (event->mask & IN_UNMOUNT) |
1542 | return QStringLiteral("IN_UNMOUNT" ); |
1543 | else |
1544 | return QStringLiteral("UNKWOWN" ); |
1545 | } |
1546 | #endif |
1547 | |
1548 | #if HAVE_QFILESYSTEMWATCHER |
1549 | // Slot for QFileSystemWatcher |
1550 | void KDirWatchPrivate::fswEventReceived(const QString &path) |
1551 | { |
1552 | if (s_verboseDebug) { |
1553 | qCDebug(KDIRWATCH) << path; |
1554 | } |
1555 | |
1556 | auto it = m_mapEntries.find(key: path); |
1557 | if (it != m_mapEntries.end()) { |
1558 | Entry *entry = &it.value(); |
1559 | entry->dirty = true; |
1560 | const int ev = scanEntry(e: entry); |
1561 | if (s_verboseDebug) { |
1562 | qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; |
1563 | } |
1564 | if (ev != NoChange) { |
1565 | emitEvent(e: entry, event: ev); |
1566 | } |
1567 | if (ev == Deleted) { |
1568 | if (entry->isDir) { |
1569 | addEntry(instance: nullptr, path: entry->parentDirectory(), sub_entry: entry, isDir: true); |
1570 | } else { |
1571 | addEntry(instance: nullptr, path: QFileInfo(entry->path).absolutePath(), sub_entry: entry, isDir: true); |
1572 | } |
1573 | } else if (ev == Created) { |
1574 | // We were waiting for it to appear; now watch it |
1575 | addWatch(e: entry); |
1576 | } else if (entry->isDir) { |
1577 | // Check if any file or dir was created under this directory, that we were waiting for |
1578 | for (Entry *sub_entry : std::as_const(t&: entry->m_entries)) { |
1579 | fswEventReceived(path: sub_entry->path); // recurse, to call scanEntry and see if something changed |
1580 | } |
1581 | } else { |
1582 | /* Even though QFileSystemWatcher only reported the file as modified, it is possible that the file |
1583 | * was in fact just deleted and then immediately recreated. If the file was deleted, QFileSystemWatcher |
1584 | * will delete the watch, and will ignore the file, even after it is recreated. Since it is impossible |
1585 | * to reliably detect this case, always re-request the watch on a dirty signal, to avoid losing the |
1586 | * underlying OS monitor. |
1587 | */ |
1588 | fsWatcher->addPath(file: entry->path); |
1589 | } |
1590 | } |
1591 | } |
1592 | #else |
1593 | void KDirWatchPrivate::fswEventReceived(const QString &path) |
1594 | { |
1595 | Q_UNUSED(path); |
1596 | qCWarning(KCOREADDONS_DEBUG) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported" ; |
1597 | } |
1598 | #endif // HAVE_QFILESYSTEMWATCHER |
1599 | |
1600 | // |
1601 | // Class KDirWatch |
1602 | // |
1603 | |
1604 | Q_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf) |
1605 | KDirWatch *KDirWatch::self() |
1606 | { |
1607 | return s_pKDirWatchSelf(); |
1608 | } |
1609 | |
1610 | // <steve> is this used anywhere? |
1611 | // <dfaure> yes, see kio/src/core/kcoredirlister_p.h:328 |
1612 | bool KDirWatch::exists() |
1613 | { |
1614 | return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData(); |
1615 | } |
1616 | |
1617 | KDirWatch::KDirWatch(QObject *parent) |
1618 | : QObject(parent) |
1619 | , d(createPrivate()) |
1620 | { |
1621 | d->ref(watch: this); |
1622 | static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1); |
1623 | const int counter = nameCounter.fetchAndAddRelaxed(valueToAdd: 1); // returns the old value |
1624 | setObjectName(QStringLiteral("KDirWatch-%1" ).arg(a: counter)); |
1625 | } |
1626 | |
1627 | KDirWatch::~KDirWatch() |
1628 | { |
1629 | if (d) { |
1630 | d->removeEntries(instance: this); |
1631 | d->unref(watch: this); |
1632 | } |
1633 | } |
1634 | |
1635 | void KDirWatch::addDir(const QString &_path, WatchModes watchModes) |
1636 | { |
1637 | if (KNetworkMounts::self()->isOptionEnabledForPath(path: _path, option: KNetworkMounts::KDirWatchDontAddWatches)) { |
1638 | return; |
1639 | } |
1640 | |
1641 | if (d) { |
1642 | d->addEntry(instance: this, _path, sub_entry: nullptr, isDir: true, watchModes); |
1643 | } |
1644 | } |
1645 | |
1646 | void KDirWatch::addFile(const QString &_path) |
1647 | { |
1648 | if (KNetworkMounts::self()->isOptionEnabledForPath(path: _path, option: KNetworkMounts::KDirWatchDontAddWatches)) { |
1649 | return; |
1650 | } |
1651 | |
1652 | if (!d) { |
1653 | return; |
1654 | } |
1655 | |
1656 | d->addEntry(instance: this, _path, sub_entry: nullptr, isDir: false); |
1657 | } |
1658 | |
1659 | QDateTime KDirWatch::ctime(const QString &_path) const |
1660 | { |
1661 | KDirWatchPrivate::Entry *e = d->entry(_path); |
1662 | |
1663 | if (!e) { |
1664 | return QDateTime(); |
1665 | } |
1666 | |
1667 | return QDateTime::fromSecsSinceEpoch(secs: e->m_ctime); |
1668 | } |
1669 | |
1670 | void KDirWatch::removeDir(const QString &_path) |
1671 | { |
1672 | if (d) { |
1673 | d->removeEntry(instance: this, _path, sub_entry: nullptr); |
1674 | } |
1675 | } |
1676 | |
1677 | void KDirWatch::removeFile(const QString &_path) |
1678 | { |
1679 | if (d) { |
1680 | d->removeEntry(instance: this, _path, sub_entry: nullptr); |
1681 | } |
1682 | } |
1683 | |
1684 | bool KDirWatch::stopDirScan(const QString &_path) |
1685 | { |
1686 | if (d) { |
1687 | KDirWatchPrivate::Entry *e = d->entry(_path); |
1688 | if (e && e->isDir) { |
1689 | return d->stopEntryScan(instance: this, e); |
1690 | } |
1691 | } |
1692 | return false; |
1693 | } |
1694 | |
1695 | bool KDirWatch::restartDirScan(const QString &_path) |
1696 | { |
1697 | if (d) { |
1698 | KDirWatchPrivate::Entry *e = d->entry(_path); |
1699 | if (e && e->isDir) |
1700 | // restart without notifying pending events |
1701 | { |
1702 | return d->restartEntryScan(instance: this, e, notify: false); |
1703 | } |
1704 | } |
1705 | return false; |
1706 | } |
1707 | |
1708 | void KDirWatch::stopScan() |
1709 | { |
1710 | if (d) { |
1711 | d->stopScan(instance: this); |
1712 | d->_isStopped = true; |
1713 | } |
1714 | } |
1715 | |
1716 | bool KDirWatch::isStopped() |
1717 | { |
1718 | return d->_isStopped; |
1719 | } |
1720 | |
1721 | void KDirWatch::startScan(bool notify, bool skippedToo) |
1722 | { |
1723 | if (d) { |
1724 | d->_isStopped = false; |
1725 | d->startScan(instance: this, notify, skippedToo); |
1726 | } |
1727 | } |
1728 | |
1729 | bool KDirWatch::contains(const QString &_path) const |
1730 | { |
1731 | KDirWatchPrivate::Entry *e = d->entry(_path); |
1732 | if (!e) { |
1733 | return false; |
1734 | } |
1735 | |
1736 | for (const KDirWatchPrivate::Client &client : e->m_clients) { |
1737 | if (client.instance == this) { |
1738 | return true; |
1739 | } |
1740 | } |
1741 | |
1742 | return false; |
1743 | } |
1744 | |
1745 | void KDirWatch::setCreated(const QString &_file) |
1746 | { |
1747 | qCDebug(KDIRWATCH) << objectName() << "emitting created" << _file; |
1748 | Q_EMIT created(path: _file); |
1749 | } |
1750 | |
1751 | void KDirWatch::setDirty(const QString &_file) |
1752 | { |
1753 | Q_EMIT dirty(path: _file); |
1754 | } |
1755 | |
1756 | void KDirWatch::setDeleted(const QString &_file) |
1757 | { |
1758 | qCDebug(KDIRWATCH) << objectName() << "emitting deleted" << _file; |
1759 | Q_EMIT deleted(path: _file); |
1760 | } |
1761 | |
1762 | KDirWatch::Method KDirWatch::internalMethod() const |
1763 | { |
1764 | // This reproduces the logic in KDirWatchPrivate::addWatch |
1765 | switch (d->m_preferredMethod) { |
1766 | case KDirWatch::INotify: |
1767 | #if HAVE_SYS_INOTIFY_H |
1768 | if (d->supports_inotify) { |
1769 | return KDirWatch::INotify; |
1770 | } |
1771 | #endif |
1772 | break; |
1773 | case KDirWatch::QFSWatch: |
1774 | #if HAVE_QFILESYSTEMWATCHER |
1775 | return KDirWatch::QFSWatch; |
1776 | #else |
1777 | break; |
1778 | #endif |
1779 | case KDirWatch::Stat: |
1780 | return KDirWatch::Stat; |
1781 | } |
1782 | |
1783 | #if HAVE_SYS_INOTIFY_H |
1784 | if (d->supports_inotify) { |
1785 | return KDirWatch::INotify; |
1786 | } |
1787 | #endif |
1788 | #if HAVE_QFILESYSTEMWATCHER |
1789 | return KDirWatch::QFSWatch; |
1790 | #else |
1791 | return KDirWatch::Stat; |
1792 | #endif |
1793 | } |
1794 | |
1795 | bool KDirWatch::event(QEvent *event) |
1796 | { |
1797 | if (Q_LIKELY(event->type() != QEvent::ThreadChange)) { |
1798 | return QObject::event(event); |
1799 | } |
1800 | |
1801 | qCCritical(KDIRWATCH) << "KDirwatch is moving its thread. This is not supported at this time; your watch will not watch anything anymore!" |
1802 | << "Create and use watches on the correct thread" |
1803 | << "Watch:" << this; |
1804 | |
1805 | // We are still in the old thread when the event runs, so this is safe. |
1806 | Q_ASSERT(thread() == d->thread()); |
1807 | d->removeEntries(instance: this); |
1808 | d->unref(watch: this); |
1809 | d = nullptr; |
1810 | |
1811 | // Schedule the creation of the new private in the new thread. |
1812 | QMetaObject::invokeMethod( |
1813 | object: this, |
1814 | function: [this] { |
1815 | d = createPrivate(); |
1816 | }, |
1817 | type: Qt::QueuedConnection); |
1818 | |
1819 | // NOTE: to actually support moving watches across threads we'd have to make Entry copyable and schedule a complete |
1820 | // re-installation of watches on the new thread after createPrivate. |
1821 | |
1822 | return QObject::event(event); |
1823 | } |
1824 | |
1825 | #include "moc_kdirwatch.cpp" |
1826 | #include "moc_kdirwatch_p.cpp" |
1827 | |
1828 | // sven |
1829 | |