33#include "kcoreaddons_debug.h"
34#include "kdirwatch_p.h"
35#include "kfilesystemtype.h"
36#include "knetworkmounts.h"
38#include <io/config-kdirwatch.h>
40#include <QCoreApplication>
43#include <QLoggingCategory>
44#include <QSocketNotifier>
46#include <QThreadStorage>
52#include <qplatformdefs.h>
59#include <sys/inotify.h>
63#define IN_DONT_FOLLOW 0x02000000
67#define IN_ONLYDIR 0x01000000
73#include <sys/utsname.h>
77Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH)
79Q_LOGGING_CATEGORY(KDIRWATCH,
"kf.coreaddons.kdirwatch", QtWarningMsg)
82static bool s_verboseDebug =
false;
85static KDirWatchPrivate *createPrivate()
87 if (!dwp_self.hasLocalData()) {
88 dwp_self.setLocalData(
new KDirWatchPrivate);
90 return dwp_self.localData();
92static void destroyPrivate()
94 dwp_self.localData()->deleteLater();
95 dwp_self.setLocalData(
nullptr);
99static KDirWatch::Method methodFromString(
const QByteArray &method)
101 if (method ==
"Stat") {
102 return KDirWatch::Stat;
103 }
else if (method ==
"QFSWatch") {
104 return KDirWatch::QFSWatch;
106#if HAVE_SYS_INOTIFY_H
108 return KDirWatch::INotify;
110 return KDirWatch::QFSWatch;
115static const char *methodToString(KDirWatch::Method method)
118 case KDirWatch::INotify:
120 case KDirWatch::Stat:
122 case KDirWatch::QFSWatch:
129static const char s_envNfsPoll[] =
"KDIRWATCH_NFSPOLLINTERVAL";
130static const char s_envPoll[] =
"KDIRWATCH_POLLINTERVAL";
131static const char s_envMethod[] =
"KDIRWATCH_METHOD";
132static const char s_envNfsMethod[] =
"KDIRWATCH_NFSMETHOD";
162KDirWatchPrivate::KDirWatchPrivate()
163 : m_statRescanTimer()
171#if HAVE_SYS_INOTIFY_H
179 s_verboseDebug =
true;
181 m_statRescanTimer.setObjectName(QStringLiteral(
"KDirWatchPrivate::timer"));
184 m_nfsPollInterval = qEnvironmentVariableIsSet(s_envNfsPoll) ? qEnvironmentVariableIntValue(s_envNfsPoll) : 5000;
185 m_PollInterval = qEnvironmentVariableIsSet(s_envPoll) ? qEnvironmentVariableIntValue(s_envPoll) : 500;
187 m_preferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envMethod) ? qgetenv(s_envMethod) :
"inotify");
189 m_nfsPreferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envNfsMethod) ? qgetenv(s_envNfsMethod) :
"Stat");
193 availableMethods <<
"Stat";
196 rescan_timer.setObjectName(QStringLiteral(
"KDirWatchPrivate::rescan_timer"));
197 rescan_timer.setSingleShot(
true);
200#if HAVE_SYS_INOTIFY_H
201 m_inotify_fd = inotify_init();
202 supports_inotify = m_inotify_fd > 0;
204 if (!supports_inotify) {
205 qCDebug(KDIRWATCH) <<
"Can't use Inotify, kernel doesn't support it:" << strerror(errno);
207 availableMethods <<
"INotify";
208 (void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC);
214#if HAVE_QFILESYSTEMWATCHER
215 availableMethods <<
"QFileSystemWatcher";
219 qCDebug(KDIRWATCH) <<
"Available methods: " << availableMethods <<
"preferred=" << methodToString(m_preferredMethod);
223KDirWatchPrivate::~KDirWatchPrivate()
225 m_statRescanTimer.stop();
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;
236 for (
auto &referenceObject : m_referencesObjects) {
237 referenceObject->d =
nullptr;
240#if HAVE_SYS_INOTIFY_H
241 if (supports_inotify) {
242 QT_CLOSE(m_inotify_fd);
245#if HAVE_QFILESYSTEMWATCHER
250void KDirWatchPrivate::inotifyEventReceived()
252#if HAVE_SYS_INOTIFY_H
253 if (!supports_inotify) {
258 int offsetStartRead = 0;
260 assert(m_inotify_fd > -1);
261 ioctl(m_inotify_fd, FIONREAD, &pending);
263 while (pending > 0) {
264 const int bytesToRead = qMin<int>(pending,
sizeof(buf) - offsetStartRead);
266 int bytesAvailable =
read(m_inotify_fd, &buf[offsetStartRead], bytesToRead);
267 pending -= bytesAvailable;
268 bytesAvailable += offsetStartRead;
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]);
275 if (
event->mask & IN_Q_OVERFLOW) {
276 qCWarning(KDIRWATCH) <<
"Inotify Event queue overflowed, check max_queued_events value";
280 const int eventSize =
sizeof(
struct inotify_event) +
event->len;
281 if (bytesAvailable < eventSize) {
285 bytesAvailable -= eventSize;
286 offsetCurrent += eventSize;
291 int len =
event->len;
292 while (len > 1 && !
event->name[len - 1]) {
300 if (!
path.
isEmpty() && isNoisyFile(cpath.data())) {
305 const bool isDir = (
event->mask & (IN_ISDIR));
307 Entry *e = m_inotify_wd_to_entry.value(
event->wd);
311 const bool wasDirty = e->dirty;
316 qCDebug(KDIRWATCH).nospace() <<
"got event " << inotifyEventName(event) <<
" for entry " << e->path
317 << (
event->mask & IN_ISDIR ?
" [directory] " :
" [file] ") << path;
319 if (
event->mask & IN_DELETE_SELF) {
320 e->m_status = NonExistent;
321 m_inotify_wd_to_entry.remove(e->wd);
323 e->m_ctime = invalid_ctime;
324 emitEvent(e, Deleted);
326 Entry *parentEntry = entry(e->parentDirectory());
328 parentEntry->dirty =
true;
331 addEntry(
nullptr, e->parentDirectory(), e,
true );
333 if (
event->mask & IN_IGNORED) {
337 if (
event->mask & (IN_CREATE | IN_MOVED_TO)) {
338 Entry *sub_entry = e->findSubEntry(tpath);
340 qCDebug(KDIRWATCH) <<
"-->got CREATE signal for" << (tpath) <<
"sub_entry=" << sub_entry;
344 sub_entry->dirty =
true;
345 rescan_timer.start(0);
346 }
else if (e->isDir && !e->m_clients.empty()) {
351 for (
const Client *client : clients) {
356 emitEvent(e, Created, tpath);
357 qCDebug(KDIRWATCH).nospace() << clients.
count() <<
" instance(s) monitoring the new " << (isDir ?
"dir " :
"file ") << tpath;
359 e->m_pendingFileChanges.
append(e->path);
360 if (!rescan_timer.isActive()) {
361 rescan_timer.start(m_PollInterval);
365 if (
event->mask & (IN_DELETE | IN_MOVED_FROM)) {
366 if ((e->isDir) && (!e->m_clients.empty())) {
372 int counter = std::count_if(e->m_clients.cbegin(), e->m_clients.cend(), [flag](
const Client &client) {
373 return client.m_watchModes & flag;
377 emitEvent(e, Deleted, tpath);
381 if (
event->mask & (IN_MODIFY | IN_ATTRIB)) {
382 if ((e->isDir) && (!e->m_clients.empty())) {
398 e->m_pendingFileChanges.append(tpath);
404 if (!rescan_timer.isActive()) {
405 rescan_timer.start(m_PollInterval);
408 if (bytesAvailable > 0) {
410 memmove(buf, &buf[offsetCurrent], bytesAvailable);
411 offsetStartRead = bytesAvailable;
417KDirWatchPrivate::Entry::~Entry()
425void KDirWatchPrivate::Entry::propagate_dirty()
427 for (Entry *sub_entry : std::as_const(m_entries)) {
428 if (!sub_entry->dirty) {
429 sub_entry->dirty =
true;
430 sub_entry->propagate_dirty();
440 if (instance ==
nullptr) {
444 auto it = findInstance(instance);
445 if (it != m_clients.end()) {
446 Client &client = *it;
448 client.m_watchModes = watchModes;
452 m_clients.emplace_back(instance, watchModes);
455void KDirWatchPrivate::Entry::removeClient(
KDirWatch *instance)
457 auto it = findInstance(instance);
458 if (it != m_clients.end()) {
459 Client &client = *it;
461 if (client.count == 0) {
468int KDirWatchPrivate::Entry::clientCount()
const
471 for (
const Client &client : m_clients) {
472 clients += client.count;
478QString KDirWatchPrivate::Entry::parentDirectory()
const
490 for (
const Client &client : m_clients) {
491 if (client.m_watchModes & flag) {
510 for (
const Client &client : m_clients) {
511 if (client.m_watchModes & flag) {
520 if (!dwp_self.hasLocalData()) {
521 debug <<
"KDirWatch not used";
524 debug << dwp_self.localData();
530 debug <<
"Entries watched:";
531 if (dwp.m_mapEntries.count() == 0) {
534 auto it = dwp.m_mapEntries.cbegin();
535 for (; it != dwp.m_mapEntries.cend(); ++it) {
536 const KDirWatchPrivate::Entry &e = it.value();
539 for (
const KDirWatchPrivate::Client &c : e.m_clients) {
541 if (c.watchingStopped) {
542 if (c.pending & KDirWatchPrivate::Deleted) {
543 pending +=
"deleted ";
545 if (c.pending & KDirWatchPrivate::Created) {
546 pending +=
"created ";
548 if (c.pending & KDirWatchPrivate::Changed) {
549 pending +=
"changed ";
552 pending =
" (pending: " + pending +
')';
554 pending =
", stopped" + pending;
556 debug <<
" by " << c.instance->objectName() <<
" (" << c.count <<
" times)" << pending;
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);
574 debug.
nospace() <<
"[ Entry for " << entry.path <<
", " << (entry.isDir ?
"dir" :
"file");
575 if (entry.m_status == KDirWatchPrivate::NonExistent) {
576 debug <<
", non-existent";
579 << ((entry.m_mode == KDirWatchPrivate::INotifyMode) ?
"INotify"
580 : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ?
"QFSWatch"
581 : (entry.m_mode == KDirWatchPrivate::StatMode) ?
"Stat"
583#if HAVE_SYS_INOTIFY_H
584 if (entry.m_mode == KDirWatchPrivate::INotifyMode) {
585 debug <<
" inotify_wd=" << entry.wd;
588 debug <<
", has " << entry.m_clients.size() <<
" clients";
590 if (!entry.m_entries.isEmpty()) {
591 debug <<
", nonexistent subentries:";
592 for (KDirWatchPrivate::Entry *subEntry : std::as_const(entry.m_entries)) {
593 debug << subEntry << subEntry->path;
600KDirWatchPrivate::Entry *KDirWatchPrivate::entry(
const QString &_path)
612 auto it = m_mapEntries.find(path);
613 return it != m_mapEntries.end() ? &it.value() :
nullptr;
617void KDirWatchPrivate::useFreq(Entry *e,
int newFreq)
622 if (e->freq < freq) {
624 if (m_statRescanTimer.isActive()) {
625 m_statRescanTimer.start(freq);
627 qCDebug(KDIRWATCH) <<
"Global Poll Freq is now" << freq <<
"msec";
631#if HAVE_SYS_INOTIFY_H
633bool KDirWatchPrivate::useINotify(Entry *e)
638 if (!supports_inotify) {
642 e->m_mode = INotifyMode;
644 if (e->m_status == NonExistent) {
645 addEntry(
nullptr, e->parentDirectory(), e,
true);
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;
653 m_inotify_wd_to_entry.insert(e->wd, e);
654 if (s_verboseDebug) {
655 qCDebug(KDIRWATCH) <<
"inotify successfully used for monitoring" << e->path <<
"wd=" << e->wd;
660 if (errno == ENOSPC) {
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";
668 qCDebug(KDIRWATCH) <<
"inotify failed for monitoring" << e->path <<
":" << strerror(errno) <<
" (errno:" << errno <<
")";
673#if HAVE_QFILESYSTEMWATCHER
674bool KDirWatchPrivate::useQFSWatch(Entry *e)
676 e->m_mode = QFSWatchMode;
679 if (e->m_status == NonExistent) {
680 addEntry(
nullptr, e->parentDirectory(), e,
true );
690 fsWatcher->addPath(e->path);
695bool KDirWatchPrivate::useStat(Entry *e)
698 useFreq(e, m_nfsPollInterval);
700 useFreq(e, m_PollInterval);
703 if (e->m_mode != StatMode) {
704 e->m_mode = StatMode;
707 if (statEntries == 1) {
709 m_statRescanTimer.start(freq);
710 qCDebug(KDIRWATCH) <<
" Started Polling Timer, freq " << freq;
714 qCDebug(KDIRWATCH) <<
" Setup Stat (freq " << e->freq <<
") for " << e->path;
728 qCWarning(KDIRWATCH) <<
"Cannot watch QRC-like path" <<
path;
744 auto it = m_mapEntries.find(path);
745 if (it != m_mapEntries.end()) {
746 Entry &entry = it.value();
748 entry.m_entries.append(sub_entry);
749 if (s_verboseDebug) {
750 qCDebug(KDIRWATCH) <<
"Added already watched Entry" <<
path <<
"(for" << sub_entry->path <<
")";
753 entry.addClient(instance, watchModes);
754 if (s_verboseDebug) {
755 qCDebug(KDIRWATCH) <<
"Added already watched Entry" <<
path <<
"(now" << entry.clientCount() <<
"clients)"
767 auto newIt = m_mapEntries.insert(path, Entry());
769 Entry *e = &(*newIt);
772 e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR;
775 if (e->isDir && !isDir) {
777 if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) {
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!";
792 qCWarning(KCOREADDONS_DEBUG) <<
"KDirWatch:" <<
path
793 <<
"is a file. You can't use recursive or "
794 "watchFiles options";
800 e->m_ctime = stat_buf.st_mtime;
802 e->m_ctime = stat_buf.st_ctime;
805 e->m_nlink = stat_buf.st_nlink;
806 e->m_ino = stat_buf.st_ino;
809 e->m_ctime = invalid_ctime;
810 e->m_status = NonExistent;
817 e->m_entries.append(sub_entry);
819 e->addClient(instance, watchModes);
822 if (s_verboseDebug) {
823 qCDebug(KDIRWATCH).nospace() <<
"Added " << (e->isDir ?
"Dir " :
"File ") << path << (e->m_status == NonExistent ?
" NotExisting" :
"") <<
" for "
847#if HAVE_SYS_INOTIFY_H
848 if (m_preferredMethod == KDirWatch::INotify) {
853 filters &=
~QDir::Files;
857 QDir basedir(e->path);
858 const QFileInfoList contents = basedir.entryInfoList(filters);
859 for (
const QFileInfo &fileInfo : contents) {
861 bool isDir = fileInfo.isDir() && !fileInfo.isSymLink();
870void KDirWatchPrivate::addWatch(Entry *e)
881 KDirWatch::Method preferredMethod = m_preferredMethod;
882 if (m_nfsPreferredMethod != m_preferredMethod) {
884 preferredMethod = m_nfsPreferredMethod;
889 bool inotifyFailed =
false;
890 bool entryAdded =
false;
891 switch (preferredMethod) {
892#if HAVE_SYS_INOTIFY_H
893 case KDirWatch::INotify:
894 entryAdded = useINotify(e);
896 inotifyFailed =
true;
900 case KDirWatch::INotify:
904#if HAVE_QFILESYSTEMWATCHER
905 case KDirWatch::QFSWatch:
906 entryAdded = useQFSWatch(e);
909 case KDirWatch::QFSWatch:
913 case KDirWatch::Stat:
914 entryAdded = useStat(e);
920#if HAVE_SYS_INOTIFY_H
921 if (preferredMethod != KDirWatch::INotify && useINotify(e)) {
925#if HAVE_QFILESYSTEMWATCHER
928 if (preferredMethod != KDirWatch::QFSWatch && !inotifyFailed && useQFSWatch(e)) {
932 if (preferredMethod != KDirWatch::Stat) {
938void KDirWatchPrivate::removeWatch(Entry *e)
940#if HAVE_SYS_INOTIFY_H
941 if (e->m_mode == INotifyMode) {
942 m_inotify_wd_to_entry.remove(e->wd);
943 (void)inotify_rm_watch(m_inotify_fd, e->wd);
944 if (s_verboseDebug) {
945 qCDebug(KDIRWATCH).nospace() <<
"Cancelled INotify (fd " << m_inotify_fd <<
", " << e->wd <<
") for " << e->path;
949#if HAVE_QFILESYSTEMWATCHER
950 if (e->m_mode == QFSWatchMode && fsWatcher) {
951 if (s_verboseDebug) {
952 qCDebug(KDIRWATCH) <<
"fsWatcher->removePath" << e->path;
954 fsWatcher->removePath(e->path);
959void KDirWatchPrivate::removeEntry(
KDirWatch *instance,
const QString &_path, Entry *sub_entry)
961 qCDebug(KDIRWATCH) <<
"path=" << _path <<
"sub_entry:" << sub_entry;
963 Entry *e = entry(_path);
965 removeEntry(instance, e, sub_entry);
969void KDirWatchPrivate::removeEntry(
KDirWatch *instance, Entry *e, Entry *sub_entry)
971 removeList.remove(e);
974 e->m_entries.removeAll(sub_entry);
976 e->removeClient(instance);
979 if (!e->m_clients.empty() || !e->m_entries.empty()) {
984 removeList.insert(e);
989 if (e->m_status == Normal) {
994 removeEntry(
nullptr, e->parentDirectory(), e);
1000 if (e->m_mode == StatMode) {
1002 if (statEntries == 0) {
1003 m_statRescanTimer.stop();
1004 qCDebug(KDIRWATCH) <<
" Stopped Polling Timer";
1008 if (s_verboseDebug) {
1009 qCDebug(KDIRWATCH).nospace() <<
"Removed " << (e->isDir ?
"Dir " :
"File ") << e->path <<
" for " << (sub_entry ? sub_entry->path :
QString()) <<
" ["
1013#if HAVE_SYS_INOTIFY_H
1014 m_inotify_wd_to_entry.
remove(e->wd);
1022void KDirWatchPrivate::removeEntries(
KDirWatch *instance)
1024 int minfreq = 3600000;
1028 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1029 Entry &entry = it.value();
1030 auto clientIt = entry.findInstance(instance);
1031 if (clientIt != entry.m_clients.end()) {
1032 clientIt->count = 1;
1033 pathList.
append(entry.path);
1034 }
else if (entry.m_mode == StatMode && entry.freq < minfreq) {
1035 minfreq = entry.freq;
1039 for (
const QString &path : std::as_const(pathList)) {
1040 removeEntry(instance, path,
nullptr);
1043 if (minfreq > freq) {
1046 if (m_statRescanTimer.isActive()) {
1047 m_statRescanTimer.start(freq);
1049 qCDebug(KDIRWATCH) <<
"Poll Freq now" << freq <<
"msec";
1054bool KDirWatchPrivate::stopEntryScan(
KDirWatch *instance, Entry *e)
1056 int stillWatching = 0;
1057 for (Client &client : e->m_clients) {
1058 if (!instance || instance == client.instance) {
1059 client.watchingStopped =
true;
1060 }
else if (!client.watchingStopped) {
1061 stillWatching += client.count;
1065 qCDebug(KDIRWATCH) << (instance ? instance->
objectName() : QStringLiteral(
"all")) <<
"stopped scanning" << e->path <<
"(now" << stillWatching
1068 if (stillWatching == 0) {
1071 e->m_ctime = invalid_ctime;
1081bool KDirWatchPrivate::restartEntryScan(
KDirWatch *instance, Entry *e,
bool notify)
1083 int wasWatching = 0;
1084 int newWatching = 0;
1085 for (Client &client : e->m_clients) {
1086 if (!client.watchingStopped) {
1087 wasWatching += client.count;
1088 }
else if (!instance || instance == client.instance) {
1089 client.watchingStopped =
false;
1090 newWatching += client.count;
1093 if (newWatching == 0) {
1097 qCDebug(KDIRWATCH) << (instance ? instance->
objectName() : QStringLiteral(
"all")) <<
"restarted scanning" << e->path <<
"(now" << wasWatching + newWatching
1103 if (wasWatching == 0) {
1105 QT_STATBUF stat_buf;
1110 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1112 if (s_verboseDebug) {
1113 qCDebug(KDIRWATCH) <<
"Setting status to Normal for" << e << e->path;
1115 e->m_nlink = stat_buf.st_nlink;
1116 e->m_ino = stat_buf.st_ino;
1119 removeEntry(
nullptr, e->parentDirectory(), e);
1121 e->m_ctime = invalid_ctime;
1122 e->m_status = NonExistent;
1124 if (s_verboseDebug) {
1125 qCDebug(KDIRWATCH) <<
"Setting status to NonExistent for" << e << e->path;
1138void KDirWatchPrivate::stopScan(
KDirWatch *instance)
1140 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1141 stopEntryScan(instance, &it.value());
1145void KDirWatchPrivate::startScan(
KDirWatch *instance,
bool notify,
bool skippedToo)
1148 resetList(instance, skippedToo);
1151 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1152 restartEntryScan(instance, &it.value(), notify);
1159void KDirWatchPrivate::resetList(
KDirWatch *instance,
bool skippedToo)
1163 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1164 for (Client &client : it.value().m_clients) {
1165 if (!client.watchingStopped || skippedToo) {
1166 client.pending = NoChange;
1174int KDirWatchPrivate::scanEntry(Entry *e)
1177 if (e->m_mode == UnknownMode) {
1181 if (e->m_mode == INotifyMode) {
1189 if (e->m_mode == StatMode) {
1194 e->msecLeft -= freq;
1195 if (e->msecLeft > 0) {
1198 e->msecLeft += e->freq;
1201 QT_STATBUF stat_buf;
1204 if (e->m_status == NonExistent) {
1207 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1209 e->m_ino = stat_buf.st_ino;
1210 if (s_verboseDebug) {
1211 qCDebug(KDIRWATCH) <<
"Setting status to Normal for just created" << e << e->path;
1214 removeEntry(
nullptr, e->parentDirectory(), e);
1220 if (s_verboseDebug) {
1221 struct tm *tmp = localtime(&e->m_ctime);
1223 strftime(outstr,
sizeof(outstr),
"%H:%M:%S", tmp);
1224 qCDebug(KDIRWATCH) << e->path <<
"e->m_ctime=" << e->m_ctime << outstr <<
"stat_buf.st_ctime=" << stat_buf.st_ctime
1225 <<
"stat_buf.st_mtime=" << stat_buf.st_mtime <<
"e->m_nlink=" << e->m_nlink <<
"stat_buf.st_nlink=" << stat_buf.st_nlink
1226 <<
"e->m_ino=" << e->m_ino <<
"stat_buf.st_ino=" << stat_buf.st_ino;
1230 if ((e->m_ctime != invalid_ctime)
1231 && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino
1232 ||
int(stat_buf.st_nlink) !=
int(e->m_nlink)
1237 || e->m_mode == QFSWatchMode
1240 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1241 e->m_nlink = stat_buf.st_nlink;
1242 if (e->m_ino != stat_buf.st_ino) {
1246 e->m_ino = stat_buf.st_ino;
1247 return (Deleted | Created);
1260 e->m_status = NonExistent;
1262 if (e->m_ctime == invalid_ctime) {
1266 e->m_ctime = invalid_ctime;
1274void KDirWatchPrivate::emitEvent(Entry *e,
int event,
const QString &fileName)
1283#elif defined(Q_OS_WIN)
1290 if (s_verboseDebug) {
1291 qCDebug(KDIRWATCH) <<
event <<
path << e->m_clients.
size() <<
"clients";
1294 for (Client &c : e->m_clients) {
1295 if (c.instance ==
nullptr || c.count == 0) {
1299 if (c.watchingStopped) {
1304 if (event == NoChange || event == Changed) {
1307 c.pending = NoChange;
1308 if (event == NoChange) {
1314 if (event & Deleted) {
1318 c.instance->setDeleted(path);
1323 if (event & Created) {
1327 c.instance->setCreated(path);
1333 if (event & Changed) {
1337 c.instance->setDirty(path);
1345void KDirWatchPrivate::slotRemoveDelayed()
1347 delayRemove =
false;
1351 while (!removeList.isEmpty()) {
1352 Entry *entry = *removeList.begin();
1353 removeEntry(
nullptr, entry,
nullptr);
1360void KDirWatchPrivate::slotRescan()
1362 if (s_verboseDebug) {
1366 EntryMap::Iterator it;
1372 bool timerRunning = m_statRescanTimer.isActive();
1374 m_statRescanTimer.stop();
1384 it = m_mapEntries.begin();
1385 for (; it != m_mapEntries.end(); ++it) {
1391 it = m_mapEntries.begin();
1392 for (; it != m_mapEntries.end(); ++it) {
1393 if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) {
1394 (*it).propagate_dirty();
1399#if HAVE_SYS_INOTIFY_H
1403 it = m_mapEntries.
begin();
1404 for (; it != m_mapEntries.end(); ++it) {
1406 Entry *entry = &(*it);
1407 if (!entry->isValid()) {
1411 const int ev = scanEntry(entry);
1412 if (s_verboseDebug) {
1413 qCDebug(KDIRWATCH) <<
"scanEntry for" << entry->path <<
"says" << ev;
1416 switch (entry->m_mode) {
1417#if HAVE_SYS_INOTIFY_H
1419 if (ev == Deleted) {
1420 if (s_verboseDebug) {
1421 qCDebug(KDIRWATCH) <<
"scanEntry says" << entry->path <<
"was deleted";
1423 addEntry(
nullptr, entry->parentDirectory(), entry,
true);
1424 }
else if (ev == Created) {
1425 if (s_verboseDebug) {
1426 qCDebug(KDIRWATCH) <<
"scanEntry says" << entry->path <<
"was created. wd=" << entry->wd;
1428 if (entry->wd < 0) {
1436 if (ev == Created) {
1445#if HAVE_SYS_INOTIFY_H
1451 QStringList pendingFileChanges = entry->m_pendingFileChanges;
1453 for (
const QString &changedFilename : std::as_const(pendingFileChanges)) {
1454 if (s_verboseDebug) {
1455 qCDebug(KDIRWATCH) <<
"processing pending file change for" << changedFilename;
1457 emitEvent(entry, Changed, changedFilename);
1459 entry->m_pendingFileChanges.clear();
1463 if (ev != NoChange) {
1464 emitEvent(entry, ev);
1469 m_statRescanTimer.start(freq);
1472#if HAVE_SYS_INOTIFY_H
1474 for (Entry *e : std::as_const(cList)) {
1475 removeEntry(
nullptr, e->parentDirectory(), e);
1482bool KDirWatchPrivate::isNoisyFile(
const char *filename)
1485 if (*filename ==
'.') {
1486 if (strncmp(filename,
".X.err", 6) == 0) {
1489 if (strncmp(filename,
".xsession-errors", 16) == 0) {
1495 if (strncmp(filename,
".fonts.cache", 12) == 0) {
1503void KDirWatchPrivate::ref(
KDirWatch *watch)
1505 m_referencesObjects.push_back(watch);
1508void KDirWatchPrivate::unref(
KDirWatch *watch)
1510 m_referencesObjects.removeOne(watch);
1511 if (m_referencesObjects.isEmpty()) {
1516#if HAVE_SYS_INOTIFY_H
1517QString KDirWatchPrivate::inotifyEventName(
const inotify_event *event)
const
1519 if (
event->mask & IN_OPEN)
1520 return QStringLiteral(
"OPEN");
1521 else if (
event->mask & IN_CLOSE_NOWRITE)
1522 return QStringLiteral(
"CLOSE_NOWRITE");
1523 else if (
event->mask & IN_CLOSE_WRITE)
1524 return QStringLiteral(
"CLOSE_WRITE");
1525 else if (
event->mask & IN_MOVED_TO)
1526 return QStringLiteral(
"MOVED_TO");
1527 else if (
event->mask & IN_MOVED_FROM)
1528 return QStringLiteral(
"MOVED_FROM");
1529 else if (
event->mask & IN_MOVE)
1530 return QStringLiteral(
"MOVE");
1531 else if (
event->mask & IN_CREATE)
1532 return QStringLiteral(
"CREATE");
1533 else if (
event->mask & IN_DELETE)
1534 return QStringLiteral(
"DELETE");
1535 else if (
event->mask & IN_DELETE_SELF)
1536 return QStringLiteral(
"DELETE_SELF");
1537 else if (
event->mask & IN_MOVE_SELF)
1538 return QStringLiteral(
"MOVE_SELF");
1539 else if (
event->mask & IN_ATTRIB)
1540 return QStringLiteral(
"ATTRIB");
1541 else if (
event->mask & IN_MODIFY)
1542 return QStringLiteral(
"MODIFY");
1543 if (
event->mask & IN_ACCESS)
1544 return QStringLiteral(
"ACCESS");
1545 if (
event->mask & IN_IGNORED)
1546 return QStringLiteral(
"IGNORED");
1547 if (
event->mask & IN_UNMOUNT)
1548 return QStringLiteral(
"IN_UNMOUNT");
1550 return QStringLiteral(
"UNKWOWN");
1554#if HAVE_QFILESYSTEMWATCHER
1556void KDirWatchPrivate::fswEventReceived(
const QString &path)
1558 if (s_verboseDebug) {
1559 qCDebug(KDIRWATCH) <<
path;
1562 auto it = m_mapEntries.find(path);
1563 if (it != m_mapEntries.end()) {
1564 Entry *entry = &it.value();
1565 entry->dirty =
true;
1566 const int ev = scanEntry(entry);
1567 if (s_verboseDebug) {
1568 qCDebug(KDIRWATCH) <<
"scanEntry for" << entry->path <<
"says" << ev;
1570 if (ev != NoChange) {
1571 emitEvent(entry, ev);
1573 if (ev == Deleted) {
1575 addEntry(
nullptr, entry->parentDirectory(), entry,
true);
1579 }
else if (ev == Created) {
1582 }
else if (entry->isDir) {
1584 for (Entry *sub_entry : std::as_const(entry->m_entries)) {
1585 fswEventReceived(sub_entry->path);
1594 fsWatcher->addPath(entry->path);
1599void KDirWatchPrivate::fswEventReceived(
const QString &path)
1602 qCWarning(KCOREADDONS_DEBUG) <<
"QFileSystemWatcher event received but QFileSystemWatcher is not supported";
1610Q_GLOBAL_STATIC(
KDirWatch, s_pKDirWatchSelf)
1613 return s_pKDirWatchSelf();
1620 return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData();
1625 , d(createPrivate())
1628 static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
1629 const int counter = nameCounter.fetchAndAddRelaxed(1);
1636 d->removeEntries(
this);
1648 d->addEntry(
this, _path,
nullptr,
true, watchModes);
1662 d->addEntry(
this, _path,
nullptr,
false);
1667 KDirWatchPrivate::Entry *e = d->entry(_path);
1679 d->removeEntry(
this, _path,
nullptr);
1686 d->removeEntry(
this, _path,
nullptr);
1693 KDirWatchPrivate::Entry *e = d->entry(_path);
1694 if (e && e->isDir) {
1695 return d->stopEntryScan(
this, e);
1704 KDirWatchPrivate::Entry *e = d->entry(_path);
1708 return d->restartEntryScan(
this, e,
false);
1718 d->_isStopped =
true;
1724 return d->_isStopped;
1730 d->_isStopped =
false;
1731 d->startScan(
this, notify, skippedToo);
1737 KDirWatchPrivate::Entry *e = d->entry(_path);
1742 for (
const KDirWatchPrivate::Client &client : e->m_clients) {
1743 if (client.instance ==
this) {
1753 qCDebug(KDIRWATCH) <<
objectName() <<
"emitting created" << _file;
1764 qCDebug(KDIRWATCH) <<
objectName() <<
"emitting deleted" << _file;
1771 switch (d->m_preferredMethod) {
1772 case KDirWatch::INotify:
1773#if HAVE_SYS_INOTIFY_H
1774 if (d->supports_inotify) {
1775 return KDirWatch::INotify;
1779 case KDirWatch::QFSWatch:
1780#if HAVE_QFILESYSTEMWATCHER
1781 return KDirWatch::QFSWatch;
1785 case KDirWatch::Stat:
1786 return KDirWatch::Stat;
1789#if HAVE_SYS_INOTIFY_H
1790 if (d->supports_inotify) {
1791 return KDirWatch::INotify;
1794#if HAVE_QFILESYSTEMWATCHER
1795 return KDirWatch::QFSWatch;
1797 return KDirWatch::Stat;
1807 qCCritical(KDIRWATCH) <<
"KDirwatch is moving its thread. This is not supported at this time; your watch will not watch anything anymore!"
1808 <<
"Create and use watches on the correct thread"
1809 <<
"Watch:" <<
this;
1812 Q_ASSERT(
thread() == d->thread());
1813 d->removeEntries(
this);
1821 d = createPrivate();
1831#include "moc_kdirwatch.cpp"
1832#include "moc_kdirwatch_p.cpp"
Class for watching directory and file changes.
Method internalMethod() const
Returns the preferred internal method to watch for changes.
void setDeleted(const QString &path)
Emits deleted().
void addFile(const QString &file)
Adds a file to be watched.
bool event(QEvent *event) override
Trivial override.
void setCreated(const QString &path)
Emits created().
void stopScan()
Stops scanning of all directories in internal list.
void removeFile(const QString &file)
Removes a file from the list of watched files.
bool contains(const QString &path) const
Check if a directory is being watched by this KDirWatch instance.
bool stopDirScan(const QString &path)
Stops scanning the specified path.
void removeDir(const QString &path)
Removes a directory from the list of scanned directories.
void deleted(const QString &path)
Emitted when a file or directory is deleted.
void startScan(bool notify=false, bool skippedToo=false)
Starts scanning of all dirs in list.
static bool exists()
Returns true if there is an instance of KDirWatch.
void addDir(const QString &path, WatchModes watchModes=WatchDirOnly)
Adds a directory to be watched.
KDirWatch(QObject *parent=nullptr)
Constructor.
@ WatchSubDirs
Watch also all the subdirs contained by the directory.
@ WatchDirOnly
Watch just the specified directory.
@ WatchFiles
Watch also all files contained by the directory.
bool isStopped()
Is scanning stopped? After creation of a KDirWatch instance, this is false.
void dirty(const QString &path)
Emitted when a watched object is changed.
void setDirty(const QString &path)
Emits dirty().
QDateTime ctime(const QString &path) const
Returns the time the directory/file was last changed.
void created(const QString &path)
Emitted when a file or directory (being watched explicitly) is created.
~KDirWatch() override
Destructor.
bool restartDirScan(const QString &path)
Restarts scanning for specified path.
static KNetworkMounts * self()
Returns (and creates if necessary) the singleton instance.
@ KDirWatchDontAddWatches
Disables dir watching completely for slow paths, avoids stat() calls on added dirs and subdirs.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
AKONADI_MIME_EXPORT const char Deleted[]
@ Nfs
NFS or other full-featured networked filesystems (autofs, subfs, cachefs, sshfs)
KCOREADDONS_EXPORT Type fileSystemType(const QString &path)
For a given path, returns the filesystem type, one of KFileSystemType::Type values.
QVariant read(const QByteArray &data, int versionOverride=0)
QString path(const QString &relativePath)
Absolute libexec path resolved in relative relation to the current shared object.
KTEXTEDITOR_EXPORT QDebug operator<<(QDebug s, const MovingCursor &cursor)
const char * constData() const const
bool isEmpty() const const
QDateTime fromSecsSinceEpoch(qint64 secs)
QString cleanPath(const QString &path)
bool isRelativePath(const QString &path)
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
QString absolutePath() const const
void directoryChanged(const QString &path)
void fileChanged(const QString &path)
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
virtual bool event(QEvent *e)
void setObjectName(QAnyStringView name)
QThread * thread() const const
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype removeDuplicates()
QStringView left(qsizetype length) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)