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) {
352 addEntry(client->instance, tpath,
nullptr, isDir, isDir ? client->m_watchModes :
KDirWatch::WatchDirOnly);
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);
400 e->dirty = (wasDirty || (
path.
isEmpty() && (
event->mask & IN_ATTRIB)));
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 "
824 << (sub_entry ? sub_entry->
path :
QString()) <<
" [" << (instance ? instance->objectName() :
QString()) <<
"]";
828 e->m_mode = UnknownMode;
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();
863 addEntry(instance, fileInfo.absoluteFilePath(),
nullptr, isDir, isDir ? watchModes :
KDirWatch::WatchDirOnly);
870void KDirWatchPrivate::addWatch(Entry *e)
881 KDirWatch::Method preferredMethod = m_preferredMethod;
882 if (m_nfsPreferredMethod != m_preferredMethod) {
884 preferredMethod = m_nfsPreferredMethod;
889 bool entryAdded =
false;
890 switch (preferredMethod) {
891#if HAVE_SYS_INOTIFY_H
892 case KDirWatch::INotify:
893 entryAdded = useINotify(e);
896 case KDirWatch::INotify:
900#if HAVE_QFILESYSTEMWATCHER
901 case KDirWatch::QFSWatch:
902 entryAdded = useQFSWatch(e);
905 case KDirWatch::QFSWatch:
909 case KDirWatch::Stat:
910 entryAdded = useStat(e);
916#if HAVE_SYS_INOTIFY_H
917 if (preferredMethod != KDirWatch::INotify && useINotify(e)) {
921#if HAVE_QFILESYSTEMWATCHER
922 if (preferredMethod != KDirWatch::QFSWatch && useQFSWatch(e)) {
926 if (preferredMethod != KDirWatch::Stat) {
932void KDirWatchPrivate::removeWatch(Entry *e)
934#if HAVE_SYS_INOTIFY_H
935 if (e->m_mode == INotifyMode) {
936 m_inotify_wd_to_entry.remove(e->wd);
937 (void)inotify_rm_watch(m_inotify_fd, e->wd);
938 if (s_verboseDebug) {
939 qCDebug(KDIRWATCH).nospace() <<
"Cancelled INotify (fd " << m_inotify_fd <<
", " << e->wd <<
") for " << e->path;
943#if HAVE_QFILESYSTEMWATCHER
944 if (e->m_mode == QFSWatchMode && fsWatcher) {
945 if (s_verboseDebug) {
946 qCDebug(KDIRWATCH) <<
"fsWatcher->removePath" << e->path;
948 fsWatcher->removePath(e->path);
953void KDirWatchPrivate::removeEntry(
KDirWatch *instance,
const QString &_path, Entry *sub_entry)
955 qCDebug(KDIRWATCH) <<
"path=" << _path <<
"sub_entry:" << sub_entry;
957 Entry *e = entry(_path);
959 removeEntry(instance, e, sub_entry);
963void KDirWatchPrivate::removeEntry(
KDirWatch *instance, Entry *e, Entry *sub_entry)
965 removeList.remove(e);
968 e->m_entries.removeAll(sub_entry);
970 e->removeClient(instance);
973 if (!e->m_clients.empty() || !e->m_entries.empty()) {
978 removeList.insert(e);
983 if (e->m_status == Normal) {
988 removeEntry(
nullptr, e->parentDirectory(), e);
994 if (e->m_mode == StatMode) {
996 if (statEntries == 0) {
997 m_statRescanTimer.stop();
998 qCDebug(KDIRWATCH) <<
" Stopped Polling Timer";
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()) <<
"]";
1007#if HAVE_SYS_INOTIFY_H
1008 m_inotify_wd_to_entry.
remove(e->wd);
1016void KDirWatchPrivate::removeEntries(
KDirWatch *instance)
1018 int minfreq = 3600000;
1022 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1023 Entry &entry = it.
value();
1024 auto clientIt = entry.findInstance(instance);
1025 if (clientIt != entry.m_clients.end()) {
1026 clientIt->count = 1;
1027 pathList.
append(entry.path);
1028 }
else if (entry.m_mode == StatMode && entry.freq < minfreq) {
1029 minfreq = entry.freq;
1033 for (
const QString &path :
std::as_const(pathList)) {
1034 removeEntry(instance, path,
nullptr);
1037 if (minfreq > freq) {
1040 if (m_statRescanTimer.isActive()) {
1041 m_statRescanTimer.start(freq);
1043 qCDebug(KDIRWATCH) <<
"Poll Freq now" << freq <<
"msec";
1048bool KDirWatchPrivate::stopEntryScan(
KDirWatch *instance, Entry *e)
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;
1059 qCDebug(KDIRWATCH) << (instance ? instance->
objectName() : QStringLiteral(
"all")) <<
"stopped scanning" << e->path <<
"(now" << stillWatching
1062 if (stillWatching == 0) {
1065 e->m_ctime = invalid_ctime;
1075bool KDirWatchPrivate::restartEntryScan(
KDirWatch *instance, Entry *e,
bool notify)
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;
1087 if (newWatching == 0) {
1091 qCDebug(KDIRWATCH) << (instance ? instance->
objectName() : QStringLiteral(
"all")) <<
"restarted scanning" << e->path <<
"(now" << wasWatching + newWatching
1097 if (wasWatching == 0) {
1099 QT_STATBUF stat_buf;
1104 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1106 if (s_verboseDebug) {
1107 qCDebug(KDIRWATCH) <<
"Setting status to Normal for" << e << e->path;
1109 e->m_nlink = stat_buf.st_nlink;
1110 e->m_ino = stat_buf.st_ino;
1113 removeEntry(
nullptr, e->parentDirectory(), e);
1115 e->m_ctime = invalid_ctime;
1116 e->m_status = NonExistent;
1118 if (s_verboseDebug) {
1119 qCDebug(KDIRWATCH) <<
"Setting status to NonExistent for" << e << e->path;
1132void KDirWatchPrivate::stopScan(
KDirWatch *instance)
1134 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1135 stopEntryScan(instance, &it.value());
1139void KDirWatchPrivate::startScan(
KDirWatch *instance,
bool notify,
bool skippedToo)
1142 resetList(instance, skippedToo);
1145 for (
auto it = m_mapEntries.begin(); it != m_mapEntries.end(); ++it) {
1146 restartEntryScan(instance, &it.value(), notify);
1153void KDirWatchPrivate::resetList(
KDirWatch *instance,
bool skippedToo)
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;
1168int KDirWatchPrivate::scanEntry(Entry *e)
1171 if (e->m_mode == UnknownMode) {
1175 if (e->m_mode == INotifyMode) {
1183 if (e->m_mode == StatMode) {
1188 e->msecLeft -= freq;
1189 if (e->msecLeft > 0) {
1192 e->msecLeft += e->freq;
1195 QT_STATBUF stat_buf;
1198 if (e->m_status == NonExistent) {
1201 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
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;
1208 removeEntry(
nullptr, e->parentDirectory(), e);
1214 if (s_verboseDebug) {
1215 struct tm *tmp = localtime(&e->m_ctime);
1217 strftime(outstr,
sizeof(outstr),
"%H:%M:%S", 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;
1224 if ((e->m_ctime != invalid_ctime)
1225 && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino
1226 ||
int(stat_buf.st_nlink) !=
int(e->m_nlink)
1231 || e->m_mode == QFSWatchMode
1234 e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime);
1235 e->m_nlink = stat_buf.st_nlink;
1236 if (e->m_ino != stat_buf.st_ino) {
1240 e->m_ino = stat_buf.st_ino;
1241 return (Deleted | Created);
1254 e->m_status = NonExistent;
1256 if (e->m_ctime == invalid_ctime) {
1260 e->m_ctime = invalid_ctime;
1268void KDirWatchPrivate::emitEvent(Entry *e,
int event,
const QString &fileName)
1277#elif defined(Q_OS_WIN)
1284 if (s_verboseDebug) {
1285 qCDebug(KDIRWATCH) <<
event <<
path << e->m_clients.
size() <<
"clients";
1288 for (Client &c : e->m_clients) {
1289 if (c.instance ==
nullptr || c.count == 0) {
1293 if (c.watchingStopped) {
1298 if (event == NoChange || event == Changed) {
1301 c.pending = NoChange;
1302 if (event == NoChange) {
1308 if (event & Deleted) {
1312 c.instance->setDeleted(path);
1317 if (event & Created) {
1321 c.instance->setCreated(path);
1327 if (event & Changed) {
1331 c.instance->setDirty(path);
1339void KDirWatchPrivate::slotRemoveDelayed()
1341 delayRemove =
false;
1345 while (!removeList.isEmpty()) {
1346 Entry *entry = *removeList.begin();
1347 removeEntry(
nullptr, entry,
nullptr);
1354void KDirWatchPrivate::slotRescan()
1356 if (s_verboseDebug) {
1360 EntryMap::Iterator it;
1366 bool timerRunning = m_statRescanTimer.isActive();
1368 m_statRescanTimer.stop();
1378 it = m_mapEntries.begin();
1379 for (; it != m_mapEntries.end(); ++it) {
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();
1393#if HAVE_SYS_INOTIFY_H
1397 it = m_mapEntries.
begin();
1398 for (; it != m_mapEntries.end(); ++it) {
1400 Entry *entry = &(*it);
1401 if (!entry->isValid()) {
1405 const int ev = scanEntry(entry);
1406 if (s_verboseDebug) {
1407 qCDebug(KDIRWATCH) <<
"scanEntry for" << entry->path <<
"says" << ev;
1410 switch (entry->m_mode) {
1411#if HAVE_SYS_INOTIFY_H
1413 if (ev == Deleted) {
1414 if (s_verboseDebug) {
1415 qCDebug(KDIRWATCH) <<
"scanEntry says" << entry->path <<
"was deleted";
1417 addEntry(
nullptr, entry->parentDirectory(), entry,
true);
1418 }
else if (ev == Created) {
1419 if (s_verboseDebug) {
1420 qCDebug(KDIRWATCH) <<
"scanEntry says" << entry->path <<
"was created. wd=" << entry->wd;
1422 if (entry->wd < 0) {
1430 if (ev == Created) {
1439#if HAVE_SYS_INOTIFY_H
1445 QStringList pendingFileChanges = entry->m_pendingFileChanges;
1447 for (
const QString &changedFilename :
std::as_const(pendingFileChanges)) {
1448 if (s_verboseDebug) {
1449 qCDebug(KDIRWATCH) <<
"processing pending file change for" << changedFilename;
1451 emitEvent(entry, Changed, changedFilename);
1453 entry->m_pendingFileChanges.clear();
1457 if (ev != NoChange) {
1458 emitEvent(entry, ev);
1463 m_statRescanTimer.start(freq);
1466#if HAVE_SYS_INOTIFY_H
1468 for (Entry *e :
std::as_const(cList)) {
1469 removeEntry(
nullptr, e->parentDirectory(), e);
1476bool KDirWatchPrivate::isNoisyFile(
const char *filename)
1479 if (*filename ==
'.') {
1480 if (strncmp(filename,
".X.err", 6) == 0) {
1483 if (strncmp(filename,
".xsession-errors", 16) == 0) {
1489 if (strncmp(filename,
".fonts.cache", 12) == 0) {
1497void KDirWatchPrivate::ref(
KDirWatch *watch)
1499 m_referencesObjects.push_back(watch);
1502void KDirWatchPrivate::unref(
KDirWatch *watch)
1504 m_referencesObjects.removeOne(watch);
1505 if (m_referencesObjects.isEmpty()) {
1510#if HAVE_SYS_INOTIFY_H
1511QString KDirWatchPrivate::inotifyEventName(
const inotify_event *event)
const
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");
1544 return QStringLiteral(
"UNKWOWN");
1548#if HAVE_QFILESYSTEMWATCHER
1550void KDirWatchPrivate::fswEventReceived(
const QString &path)
1552 if (s_verboseDebug) {
1553 qCDebug(KDIRWATCH) <<
path;
1556 auto it = m_mapEntries.find(path);
1557 if (it != m_mapEntries.end()) {
1558 Entry *entry = &it.value();
1559 entry->dirty =
true;
1560 const int ev = scanEntry(entry);
1561 if (s_verboseDebug) {
1562 qCDebug(KDIRWATCH) <<
"scanEntry for" << entry->path <<
"says" << ev;
1564 if (ev != NoChange) {
1565 emitEvent(entry, ev);
1567 if (ev == Deleted) {
1569 addEntry(
nullptr, entry->parentDirectory(), entry,
true);
1573 }
else if (ev == Created) {
1576 }
else if (entry->isDir) {
1578 for (Entry *sub_entry :
std::as_const(entry->m_entries)) {
1579 fswEventReceived(sub_entry->path);
1588 fsWatcher->addPath(entry->path);
1593void KDirWatchPrivate::fswEventReceived(
const QString &path)
1596 qCWarning(KCOREADDONS_DEBUG) <<
"QFileSystemWatcher event received but QFileSystemWatcher is not supported";
1604Q_GLOBAL_STATIC(
KDirWatch, s_pKDirWatchSelf)
1607 return s_pKDirWatchSelf();
1614 return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData();
1619 , d(createPrivate())
1622 static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
1623 const int counter = nameCounter.fetchAndAddRelaxed(1);
1630 d->removeEntries(
this);
1642 d->addEntry(
this, _path,
nullptr,
true, watchModes);
1656 d->addEntry(
this, _path,
nullptr,
false);
1661 KDirWatchPrivate::Entry *e = d->entry(_path);
1673 d->removeEntry(
this, _path,
nullptr);
1680 d->removeEntry(
this, _path,
nullptr);
1687 KDirWatchPrivate::Entry *e = d->entry(_path);
1688 if (e && e->isDir) {
1689 return d->stopEntryScan(
this, e);
1698 KDirWatchPrivate::Entry *e = d->entry(_path);
1702 return d->restartEntryScan(
this, e,
false);
1712 d->_isStopped =
true;
1718 return d->_isStopped;
1724 d->_isStopped =
false;
1725 d->startScan(
this, notify, skippedToo);
1731 KDirWatchPrivate::Entry *e = d->entry(_path);
1736 for (
const KDirWatchPrivate::Client &client : e->m_clients) {
1737 if (client.instance ==
this) {
1747 qCDebug(KDIRWATCH) <<
objectName() <<
"emitting created" << _file;
1758 qCDebug(KDIRWATCH) <<
objectName() <<
"emitting deleted" << _file;
1765 switch (d->m_preferredMethod) {
1766 case KDirWatch::INotify:
1767#if HAVE_SYS_INOTIFY_H
1768 if (d->supports_inotify) {
1769 return KDirWatch::INotify;
1773 case KDirWatch::QFSWatch:
1774#if HAVE_QFILESYSTEMWATCHER
1775 return KDirWatch::QFSWatch;
1779 case KDirWatch::Stat:
1780 return KDirWatch::Stat;
1783#if HAVE_SYS_INOTIFY_H
1784 if (d->supports_inotify) {
1785 return KDirWatch::INotify;
1788#if HAVE_QFILESYSTEMWATCHER
1789 return KDirWatch::QFSWatch;
1791 return KDirWatch::Stat;
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;
1806 Q_ASSERT(
thread() == d->thread());
1807 d->removeEntries(
this);
1815 d = createPrivate();
1825#include "moc_kdirwatch.cpp"
1826#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.
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
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
T value(qsizetype i) const const
virtual bool event(QEvent *e)
void setObjectName(QAnyStringView name)
QThread * thread() const const
void activated(QSocketDescriptor socket, QSocketNotifier::Type type)
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
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)