diff --git a/autotests/unit/file/kinotifytest.cpp b/autotests/unit/file/kinotifytest.cpp index 21f2948c..30066e64 100644 --- a/autotests/unit/file/kinotifytest.cpp +++ b/autotests/unit/file/kinotifytest.cpp @@ -1,405 +1,442 @@ /* Copyright (C) 2010 by Sebastian Trueg Copyright (C) 2014 by Vishesh Handa This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "kinotify.h" #include #include #include #include #include #include #include #include #include class KInotifyTest : public QObject { Q_OBJECT private Q_SLOTS: void testDeleteFile(); void testDeleteFolder(); void testCreateFolder(); void testRenameFile(); void testMoveFile(); void testRenameFolder(); void testMoveFolder(); + void testMoveFromUnwatchedFolder(); void testMoveRootFolder(); void testFileClosedAfterWrite(); }; namespace { void touchFile(const QString& path) { QFile file(path); file.open(QIODevice::WriteOnly); QTextStream s(&file); s << KRandom::randomString(10); } void mkdir(const QString& path) { QDir().mkpath(path); QVERIFY(QDir(path).exists()); } } void KInotifyTest::testDeleteFile() { // create some test files QTemporaryDir dir; const QString f1(QStringLiteral("%1/randomJunk1").arg(dir.path())); touchFile(f1); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(deleted(QString,bool))); // test removing a file QFile::remove(f1); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.takeFirst().at(0).toString(), f1); } void KInotifyTest::testDeleteFolder() { // create some test files QTemporaryDir dir; const QString d1(QStringLiteral("%1/randomJunk4").arg(dir.path())); mkdir(d1); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(deleted(QString,bool))); // test removing a folder QDir().rmdir(d1); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toString(), d1); QCOMPARE(spy.first().at(1).toBool(), true); // make sure we do not watch the removed folder anymore QVERIFY(!kn.watchingPath(d1)); } void KInotifyTest::testCreateFolder() { QTemporaryDir dir; // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy createdSpy(&kn, SIGNAL(created(QString,bool))); // create the subdir const QString d1(QStringLiteral("%1/randomJunk1").arg(dir.path())); mkdir(d1); QVERIFY(createdSpy.wait()); QCOMPARE(createdSpy.count(), 1); QCOMPARE(createdSpy.takeFirst().at(0).toString(), d1); QVERIFY(kn.watchingPath(d1)); // lets go one level deeper const QString d2 = QStringLiteral("%1/subdir1").arg(d1); mkdir(d2); QVERIFY(createdSpy.wait()); QCOMPARE(createdSpy.count(), 1); QCOMPARE(createdSpy.takeFirst().at(0).toString(), d2); QVERIFY(kn.watchingPath(d2)); // although we are in the folder test lets try creating a file const QString f1 = QStringLiteral("%1/somefile1").arg(d2); touchFile(f1); QVERIFY(createdSpy.wait()); QCOMPARE(createdSpy.count(), 1); QCOMPARE(createdSpy.takeFirst().at(0).toString(), f1); } void KInotifyTest::testRenameFile() { // create some test files QTemporaryDir dir; const QString f1(QStringLiteral("%1/randomJunk1").arg(dir.path())); touchFile(f1); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(moved(QString,QString))); // actually move the file const QString f2(QStringLiteral("%1/randomJunk2").arg(dir.path())); QFile::rename(f1, f2); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), f1); QCOMPARE(args.at(1).toString(), f2); // test a subsequent rename const QString f3(QStringLiteral("%1/randomJunk3").arg(dir.path())); QFile::rename(f2, f3); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), f2); QCOMPARE(args.at(1).toString(), f3); } void KInotifyTest::testMoveFile() { // create some test files QTemporaryDir dir1; QTemporaryDir dir2; const QString src(QStringLiteral("%1/randomJunk1").arg(dir1.path())); const QString dest(QStringLiteral("%1/randomJunk2").arg(dir2.path())); touchFile(src); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir1.path(), KInotify::EventAll); kn.addWatch(dir2.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(moved(QString,QString))); // actually move the file QFile::rename(src, dest); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), src); QCOMPARE(args.at(1).toString(), dest); // test a subsequent move (back to the original folder) const QString dest2(QStringLiteral("%1/randomJunk3").arg(dir1.path())); QFile::rename(dest, dest2); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), dest); QCOMPARE(args.at(1).toString(), dest2); } void KInotifyTest::testRenameFolder() { // create some test files QTemporaryDir dir; const QString f1(QStringLiteral("%1/randomJunk1").arg(dir.path())); mkdir(f1); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(moved(QString,QString))); // actually move the file const QString f2(QStringLiteral("%1/randomJunk2").arg(dir.path())); QFile::rename(f1, f2); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), f1); QCOMPARE(args.at(1).toString(), f2); // check the path cache QVERIFY(!kn.watchingPath(f1)); QVERIFY(kn.watchingPath(f2)); // test a subsequent rename const QString f3(QStringLiteral("%1/randomJunk3").arg(dir.path())); QFile::rename(f2, f3); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), f2); QCOMPARE(args.at(1).toString(), f3); // check the path cache QVERIFY(!kn.watchingPath(f1)); QVERIFY(!kn.watchingPath(f2)); QVERIFY(kn.watchingPath(f3)); // KInotify claims it has updated its data structures, lets see if that is true // by creating a file in the new folder // listen to the desired signal const QString f4(QStringLiteral("%1/somefile").arg(f3)); QSignalSpy createdSpy(&kn, SIGNAL(created(QString,bool))); // test creating a file touchFile(f4); QVERIFY(createdSpy.wait()); QCOMPARE(createdSpy.count(), 1); QCOMPARE(createdSpy.takeFirst().at(0).toString(), f4); } void KInotifyTest::testMoveFolder() { // create some test files QTemporaryDir dir1; QTemporaryDir dir2; const QString src(QStringLiteral("%1/randomJunk1").arg(dir1.path())); const QString dest(QStringLiteral("%1/randomJunk2").arg(dir2.path())); mkdir(src); // start the inotify watcher KInotify kn(nullptr /*no config*/); kn.addWatch(dir1.path(), KInotify::EventAll); kn.addWatch(dir2.path(), KInotify::EventAll); // listen to the desired signal QSignalSpy spy(&kn, SIGNAL(moved(QString,QString))); // actually move the file QFile::rename(src, dest); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), src); QCOMPARE(args.at(1).toString(), dest); // check the path cache QVERIFY(!kn.watchingPath(src)); QVERIFY(kn.watchingPath(dest)); // test a subsequent move const QString dest2(QStringLiteral("%1/randomJunk3").arg(dir1.path())); QFile::rename(dest, dest2); // check the desired signal QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), dest); QCOMPARE(args.at(1).toString(), dest2); // check the path cache QVERIFY(!kn.watchingPath(src)); QVERIFY(!kn.watchingPath(dest)); QVERIFY(kn.watchingPath(dest2)); // KInotify claims it has updated its data structures, lets see if that is true // by creating a file in the new folder // listen to the desired signal const QString f4(QStringLiteral("%1/somefile").arg(dest2)); QSignalSpy createdSpy(&kn, SIGNAL(created(QString,bool))); // test creating a file touchFile(f4); QVERIFY(createdSpy.wait()); QCOMPARE(createdSpy.count(), 1); QCOMPARE(createdSpy.takeFirst().at(0).toString(), f4); } +void KInotifyTest::testMoveFromUnwatchedFolder() +{ + // create some test folders + QTemporaryDir dir; + const QString src(QStringLiteral("%1/randomJunk1").arg(dir.path())); + const QString dest(QStringLiteral("%1/randomJunk2").arg(dir.path())); + mkdir(src); + mkdir(dest); + + // Start watching only for destination + KInotify kn(nullptr); + kn.addWatch(dest, KInotify::EventAll); + QSignalSpy spy(&kn, &KInotify::created); + + // Create stuff inside src + mkdir(QStringLiteral("%1/sub").arg(src)); + touchFile(QStringLiteral("%1/sub/file1").arg(src)); + mkdir(QStringLiteral("%1/sub/sub1").arg(src)); + touchFile(QStringLiteral("%1/sub/sub1/file2").arg(src)); + + // Now move + QFile::rename(QStringLiteral("%1/sub").arg(src), + QStringLiteral("%1/sub").arg(dest)); + + QVERIFY(spy.wait()); + QCOMPARE(spy.count(), 4); + + // Checking if watches are installed + QSignalSpy spy1(&kn, &KInotify::deleted); + QDir dstdir(QStringLiteral("%1/sub").arg(dest)); + dstdir.removeRecursively(); + + QVERIFY(spy1.wait()); + QCOMPARE(spy1.count(), 4); +} + void KInotifyTest::testMoveRootFolder() { // create some test folders QTemporaryDir dir; const QString src(QStringLiteral("%1/randomJunk1").arg(dir.path())); const QString dest(QStringLiteral("%1/randomJunk2").arg(dir.path())); mkdir(src); // start watching the new subfolder only KInotify kn(nullptr /*no config*/); kn.addWatch(src, KInotify::EventAll); // listen for the moved signal QSignalSpy spy(&kn, SIGNAL(moved(QString,QString))); // actually move the file QFile::rename(src, dest); // check the desired signal QEXPECT_FAIL("", "KInotify cannot handle moving of top-level folders.", Abort); QVERIFY(spy.wait(500)); QCOMPARE(spy.count(), 1); QList args = spy.takeFirst(); QCOMPARE(args.at(0).toString(), src); QCOMPARE(args.at(1).toString(), dest); // check the path cache QVERIFY(!kn.watchingPath(src)); QVERIFY(kn.watchingPath(dest)); } void KInotifyTest::testFileClosedAfterWrite() { QTemporaryDir dir; touchFile(dir.path() + QLatin1String("/file")); KInotify kn(nullptr /*no config*/); kn.addWatch(dir.path(), KInotify::EventAll); QSignalSpy spy(&kn, SIGNAL(closedWrite(QString))); touchFile(dir.path() + QLatin1String("/file")); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).first().toString(), QString(dir.path() + QLatin1String("/file"))); } QTEST_GUILESS_MAIN(KInotifyTest) #include "kinotifytest.moc" diff --git a/src/file/kinotify.cpp b/src/file/kinotify.cpp index b4b19370..2ccc68e5 100644 --- a/src/file/kinotify.cpp +++ b/src/file/kinotify.cpp @@ -1,473 +1,493 @@ /* This file is part of the KDE libraries Copyright (C) 2007-2010 Sebastian Trueg Copyright (C) 2012-2014 Vishesh Handa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kinotify.h" #include "optimizedbytearray.h" #include "fileindexerconfig.h" #include "filtereddiriterator.h" #include "baloodebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QByteArray stripTrailingSlash(const QByteArray& path) { QByteArray p(path); if (p.endsWith('/')) p.truncate(p.length() - 1); return p; } QByteArray concatPath(const QByteArray& p1, const QByteArray& p2) { QByteArray p(p1); if (p.isEmpty() || (!p2.isEmpty() && p[p.length() - 1] != '/')) p.append('/'); p.append(p2); return p; } } class KInotify::Private { public: Private(KInotify* parent) : userLimitReachedSignaled(false) , config(nullptr) , m_dirIter(nullptr) , m_inotifyFd(-1) , m_notifier(nullptr) , q(parent) { } ~Private() { close(); delete m_dirIter; } QHash > cookies; QTimer cookieExpireTimer; // This variable is set to true if the watch limit is reached, and reset when it is raised bool userLimitReachedSignaled; // url <-> wd mappings // Read the documentation fo OptimizedByteArray to understand why have a cache QHash watchPathHash; QHash pathWatchHash; QSet pathCache; Baloo::FileIndexerConfig* config; QStringList m_paths; Baloo::FilteredDirIterator* m_dirIter; // FIXME: only stored from the last addWatch call WatchEvents mode; WatchFlags flags; int inotify() { if (m_inotifyFd < 0) { open(); } return m_inotifyFd; } void close() { delete m_notifier; m_notifier = nullptr; ::close(m_inotifyFd); m_inotifyFd = -1; } bool addWatch(const QString& path) { WatchEvents newMode = mode; WatchFlags newFlags = flags; // we always need the unmount event to maintain our path hash const int mask = newMode | newFlags | EventUnmount | FlagExclUnlink; const QByteArray encpath = QFile::encodeName(path); int wd = inotify_add_watch(inotify(), encpath.data(), mask); if (wd > 0) { // qCDebug(BALOO) << "Successfully added watch for" << path << watchPathHash.count(); OptimizedByteArray normalized(stripTrailingSlash(encpath), pathCache); watchPathHash.insert(wd, normalized); pathWatchHash.insert(normalized, wd); return true; } else { qCDebug(BALOO) << "Failed to create watch for" << path << strerror(errno); //If we could not create the watch because we have hit the limit, try raising it. if (errno == ENOSPC) { //If we can't, fall back to signalling qCDebug(BALOO) << "User limit reached. Count: " << watchPathHash.count(); userLimitReachedSignaled = true; Q_EMIT q->watchUserLimitReached(path); } return false; } } void removeWatch(int wd) { pathWatchHash.remove(watchPathHash.take(wd)); inotify_rm_watch(inotify(), wd); } /** * Add one watch and call oneself asynchronously */ bool _k_addWatches() { bool addedWatchSuccessfully = false; //Do nothing if the inotify user limit has been signaled. if (userLimitReachedSignaled) { return false; } // It is much faster to add watches in batches instead of adding each one // asynchronously. Try out the inotify test to compare results. for (int i = 0; i < 1000; i++) { if (!m_dirIter || m_dirIter->next().isEmpty()) { if (!m_paths.isEmpty()) { delete m_dirIter; m_dirIter = new Baloo::FilteredDirIterator(config, m_paths.takeFirst(), Baloo::FilteredDirIterator::DirsOnly); } else { delete m_dirIter; m_dirIter = nullptr; break; } } else { QString path = m_dirIter->filePath(); addedWatchSuccessfully = addWatch(path); } } // asynchronously add the next batch if (m_dirIter) { QMetaObject::invokeMethod(q, "_k_addWatches", Qt::QueuedConnection); } else { Q_EMIT q->installedWatches(); } return addedWatchSuccessfully; } private: void open() { m_inotifyFd = inotify_init(); delete m_notifier; if (m_inotifyFd > 0) { fcntl(m_inotifyFd, F_SETFD, FD_CLOEXEC); m_notifier = new QSocketNotifier(m_inotifyFd, QSocketNotifier::Read); connect(m_notifier, &QSocketNotifier::activated, q, &KInotify::slotEvent); } else { Q_ASSERT_X(0, "kinotify", "Failed to initialize inotify"); } } int m_inotifyFd; QSocketNotifier* m_notifier; KInotify* q; }; KInotify::KInotify(Baloo::FileIndexerConfig* config, QObject* parent) : QObject(parent) , d(new Private(this)) { d->config = config; // 1 second is more than enough time for the EventMoveTo event to occur // after the EventMoveFrom event has occurred d->cookieExpireTimer.setInterval(1000); d->cookieExpireTimer.setSingleShot(true); connect(&d->cookieExpireTimer, &QTimer::timeout, this, &KInotify::slotClearCookies); } KInotify::~KInotify() { delete d; } bool KInotify::available() const { if (d->inotify() > 0) { // trueg: Copied from KDirWatch. struct utsname uts; int major, minor, patch = 0; if (uname(&uts) < 0) { return false; // *shrug* } else if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) { //Kernels > 3.0 can in principle have two-number versions. if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) return false; // *shrug* } else if (major * 1000000 + minor * 1000 + patch < 2006014) { // <2.6.14 qCDebug(BALOO) << "Can't use INotify, Linux kernel too old"; return false; } return true; } else { return false; } } bool KInotify::watchingPath(const QString& path) const { const QByteArray p(stripTrailingSlash(QFile::encodeName(path))); return d->pathWatchHash.contains(OptimizedByteArray(p, d->pathCache)); } void KInotify::resetUserLimit() { d->userLimitReachedSignaled = false; } bool KInotify::addWatch(const QString& path, WatchEvents mode, WatchFlags flags) { // qCDebug(BALOO) << path; d->mode = mode; d->flags = flags; // If the inotify user limit has been signaled, // just queue this folder for watching. if (d->m_dirIter || d->userLimitReachedSignaled) { d->m_paths << path; return false; } d->m_dirIter = new Baloo::FilteredDirIterator(d->config, path, Baloo::FilteredDirIterator::DirsOnly); return d->_k_addWatches(); } bool KInotify::removeWatch(const QString& path) { // Stop all of the iterators which contain path QMutableListIterator iter(d->m_paths); while (iter.hasNext()) { if (iter.next().startsWith(path)) { iter.remove(); } } if (d->m_dirIter) { if (d->m_dirIter->filePath().startsWith(path)) { delete d->m_dirIter; d->m_dirIter = nullptr; } } // Remove all the watches QByteArray encodedPath(QFile::encodeName(path)); QHash::iterator it = d->watchPathHash.begin(); while (it != d->watchPathHash.end()) { if (it.value().toByteArray().startsWith(encodedPath)) { inotify_rm_watch(d->inotify(), it.key()); d->pathWatchHash.remove(it.value()); it = d->watchPathHash.erase(it); } else { ++it; } } return true; } +void KInotify::handleDirCreated(const QString& path) +{ + Baloo::FilteredDirIterator it(d->config, path); + // First entry is the directory itself (if not excluded) + if (!it.next().isEmpty()) { + d->addWatch(it.filePath()); + } + while (!it.next().isEmpty()) { + Q_EMIT created(it.filePath(), it.fileInfo().isDir()); + if (it.fileInfo().isDir()) { + d->addWatch(it.filePath()); + } + } +} void KInotify::slotEvent(int socket) { int avail; if (ioctl(socket, FIONREAD, &avail) == EINVAL) { qCDebug(BALOO) << "Did not receive an entire inotify event."; return; } char* buffer = (char*)malloc(avail); const int len = read(socket, buffer, avail); Q_ASSERT(len == avail); int i = 0; while (i < len) { const struct inotify_event* event = (struct inotify_event*)&buffer[i]; QByteArray path; // Overflow happens sometimes if we process the events too slowly if (event->wd < 0 && (event->mask & EventQueueOverflow)) { qWarning() << "Inotify - too many event - Overflowed"; free(buffer); return; } // the event name only contains an interesting value if we get an event for a file/folder inside // a watched folder. Otherwise we should ignore it if (event->mask & (EventDeleteSelf | EventMoveSelf)) { path = d->watchPathHash.value(event->wd).toByteArray(); } else { // we cannot use event->len here since it contains the size of the buffer and not the length of the string const QByteArray eventName = QByteArray::fromRawData(event->name, qstrnlen(event->name, event->len)); const QByteArray hashedPath = d->watchPathHash.value(event->wd).toByteArray(); path = concatPath(hashedPath, eventName); } Q_ASSERT(!path.isEmpty() || event->mask & EventIgnored); Q_ASSERT(path != "/" || event->mask & EventIgnored || event->mask & EventUnmount); // now signal the event if (event->mask & EventAccess) { // qCDebug(BALOO) << path << "EventAccess"; Q_EMIT accessed(QFile::decodeName(path)); } if (event->mask & EventAttributeChange) { // qCDebug(BALOO) << path << "EventAttributeChange"; Q_EMIT attributeChanged(QFile::decodeName(path)); } if (event->mask & EventCloseWrite) { // qCDebug(BALOO) << path << "EventCloseWrite"; Q_EMIT closedWrite(QFile::decodeName(path)); } if (event->mask & EventCloseRead) { // qCDebug(BALOO) << path << "EventCloseRead"; Q_EMIT closedRead(QFile::decodeName(path)); } if (event->mask & EventCreate) { // qCDebug(BALOO) << path << "EventCreate"; + const QString fname = QFile::decodeName(path); + Q_EMIT created(fname, event->mask & IN_ISDIR); if (event->mask & IN_ISDIR) { - // FIXME: store the mode and flags somewhere - addWatch(QString::fromUtf8(path), d->mode, d->flags); + // Files/directories inside the new directory may be created before the watch + // is installed. Ensure created events for all children are issued at least once + handleDirCreated(fname); } - Q_EMIT created(QFile::decodeName(path), event->mask & IN_ISDIR); } if (event->mask & EventDeleteSelf) { // qCDebug(BALOO) << path << "EventDeleteSelf"; d->removeWatch(event->wd); Q_EMIT deleted(QFile::decodeName(path), true); } if (event->mask & EventDelete) { // qCDebug(BALOO) << path << "EventDelete"; // we watch all folders recursively. Thus, folder removing is reported in DeleteSelf. if (!(event->mask & IN_ISDIR)) Q_EMIT deleted(QFile::decodeName(path), false); } if (event->mask & EventModify) { // qCDebug(BALOO) << path << "EventModify"; Q_EMIT modified(QFile::decodeName(path)); } if (event->mask & EventMoveSelf) { // qCDebug(BALOO) << path << "EventMoveSelf"; qWarning() << "EventMoveSelf: THIS CASE IS NOT HANDLED PROPERLY!"; } if (event->mask & EventMoveFrom) { // qCDebug(BALOO) << path << "EventMoveFrom"; d->cookies[event->cookie] = qMakePair(path, WatchFlags(event->mask)); d->cookieExpireTimer.start(); } if (event->mask & EventMoveTo) { // check if we have a cookie for this one if (d->cookies.contains(event->cookie)) { const QByteArray oldPath = d->cookies.take(event->cookie).first; // update the path cache if (event->mask & IN_ISDIR) { OptimizedByteArray optimOldPath(oldPath, d->pathCache); QHash::iterator it = d->pathWatchHash.find(optimOldPath); if (it != d->pathWatchHash.end()) { // qCDebug(BALOO) << oldPath << path; const int wd = it.value(); OptimizedByteArray optimPath(path, d->pathCache); d->watchPathHash[wd] = optimPath; d->pathWatchHash.erase(it); d->pathWatchHash.insert(optimPath, wd); } } // qCDebug(BALOO) << oldPath << "EventMoveTo" << path; Q_EMIT moved(QFile::decodeName(oldPath), QFile::decodeName(path)); } else { // qCDebug(BALOO) << "No cookie for move information of" << path << "simulating new file event"; - Q_EMIT created(QString::fromUtf8(path), event->mask & IN_ISDIR); + const QString fname = QFile::decodeName(path); + Q_EMIT created(fname, event->mask & IN_ISDIR); + if (event->mask & IN_ISDIR) { + handleDirCreated(fname); + } } } if (event->mask & EventOpen) { // qCDebug(BALOO) << path << "EventOpen"; Q_EMIT opened(QFile::decodeName(path)); } if (event->mask & EventUnmount) { // qCDebug(BALOO) << path << "EventUnmount. removing from path hash"; if (event->mask & IN_ISDIR) { d->removeWatch(event->wd); } // This is present because a unmount event is sent by inotify after unmounting, by // which time the watches have already been removed. if (path != "/") { Q_EMIT unmounted(QFile::decodeName(path)); } } if (event->mask & EventQueueOverflow) { // This should not happen since we grab all events as soon as they arrive qCDebug(BALOO) << path << "EventQueueOverflow"; // Q_EMIT queueOverflow(); } if (event->mask & EventIgnored) { // qCDebug(BALOO) << path << "EventIgnored"; } i += sizeof(struct inotify_event) + event->len; } if (len < 0) { qCDebug(BALOO) << "Failed to read event."; } free(buffer); } void KInotify::slotClearCookies() { QHashIterator > it(d->cookies); while (it.hasNext()) { it.next(); removeWatch(QString::fromUtf8(it.value().first)); Q_EMIT deleted(QFile::decodeName(it.value().first), it.value().second & IN_ISDIR); } d->cookies.clear(); } #include "moc_kinotify.cpp" diff --git a/src/file/kinotify.h b/src/file/kinotify.h index 3b405217..85bce785 100644 --- a/src/file/kinotify.h +++ b/src/file/kinotify.h @@ -1,197 +1,205 @@ /* This file is part of the KDE libraries Copyright (C) 2007-2010 Sebastian Trueg This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KINOTIFY_H_ #define KINOTIFY_H_ #include namespace Baloo { class FileIndexerConfig; } /** * A simple wrapper around inotify which only allows * to add folders recursively. * * Warning: moving of top-level folders is not supported and * results in undefined behaviour. */ class KInotify : public QObject { Q_OBJECT public: explicit KInotify(Baloo::FileIndexerConfig* config, QObject* parent = nullptr); ~KInotify() override; /** * Inotify events that can occur. Use with addWatch * to define the events that should be watched. * * These flags correspond to the native Linux inotify flags. */ enum WatchEvent { EventAccess = 0x00000001, /**< File was accessed (read, compare inotify's IN_ACCESS) */ EventAttributeChange = 0x00000004, /**< Metadata changed (permissions, timestamps, extended attributes, etc., compare inotify's IN_ATTRIB) */ EventCloseWrite = 0x00000008, /**< File opened for writing was closed (compare inotify's IN_CLOSE_WRITE) */ EventCloseRead = 0x00000010, /**< File not opened for writing was closed (compare inotify's IN_CLOSE_NOWRITE) */ EventCreate = 0x00000100, /** File/directory created in watched directory (compare inotify's IN_CREATE) */ EventDelete = 0x00000200, /**< File/directory deleted from watched directory (compare inotify's IN_DELETE) */ EventDeleteSelf = 0x00000400, /**< Watched file/directory was itself deleted (compare inotify's IN_DELETE_SELF) */ EventModify = 0x00000002, /**< File was modified (compare inotify's IN_MODIFY) */ EventMoveSelf = 0x00000800, /**< Watched file/directory was itself moved (compare inotify's IN_MOVE_SELF) */ EventMoveFrom = 0x00000040, /**< File moved out of watched directory (compare inotify's IN_MOVED_FROM) */ EventMoveTo = 0x00000080, /**< File moved into watched directory (compare inotify's IN_MOVED_TO) */ EventOpen = 0x00000020, /**< File was opened (compare inotify's IN_OPEN) */ EventUnmount = 0x00002000, /**< Backing fs was unmounted (compare inotify's IN_UNMOUNT) */ EventQueueOverflow = 0x00004000, /**< Event queued overflowed (compare inotify's IN_Q_OVERFLOW) */ EventIgnored = 0x00008000, /**< File was ignored (compare inotify's IN_IGNORED) */ EventMove = (EventMoveFrom | EventMoveTo), EventAll = (EventAccess | EventAttributeChange | EventCloseWrite | EventCloseRead | EventCreate | EventDelete | EventDeleteSelf | EventModify | EventMoveSelf | EventMoveFrom | EventMoveTo | EventOpen) }; Q_DECLARE_FLAGS(WatchEvents, WatchEvent) /** * Watch flags * * These flags correspond to the native Linux inotify flags. */ enum WatchFlag { FlagOnlyDir = 0x01000000, /**< Only watch the path if it is a directory (IN_ONLYDIR) */ FlagDoNotFollow = 0x02000000, /**< Don't follow a sym link (IN_DONT_FOLLOW) */ FlagOneShot = 0x80000000, /**< Only send event once (IN_ONESHOT) */ FlagExclUnlink = 0x04000000 /**< Do not generate events for unlinked files (IN_EXCL_UNLINK) */ }; Q_DECLARE_FLAGS(WatchFlags, WatchFlag) /** * \return \p true if inotify is available and usable. */ bool available() const; bool watchingPath(const QString& path) const; /** * Call this when the inotify limit has been increased. */ void resetUserLimit(); public Q_SLOTS: bool addWatch(const QString& path, WatchEvents modes, WatchFlags flags = WatchFlags()); bool removeWatch(const QString& path); Q_SIGNALS: /** * Emitted if a file is accessed (KInotify::EventAccess) */ void accessed(const QString& file); /** * Emitted if file attributes are changed (KInotify::EventAttributeChange) */ void attributeChanged(const QString& file); /** * Emitted if FIXME (KInotify::EventCloseWrite) */ void closedWrite(const QString& file); /** * Emitted if FIXME (KInotify::EventCloseRead) */ void closedRead(const QString& file); /** * Emitted if a new file has been created in one of the watched * folders (KInotify::EventCreate) */ void created(const QString& file, bool isDir); /** * Emitted if a watched file or folder has been deleted. * This includes files in watched foldes (KInotify::EventDelete and KInotify::EventDeleteSelf) */ void deleted(const QString& file, bool isDir); /** * Emitted if a watched file is modified (KInotify::EventModify) */ void modified(const QString& file); /** * Emitted if a file or folder has been moved or renamed. * * \warning The moved signal will only be emitted if both the source and target folder * are being watched. */ void moved(const QString& oldName, const QString& newName); /** * Emitted if a file is opened (KInotify::EventOpen) */ void opened(const QString& file); /** * Emitted if a watched path has been unmounted (KInotify::EventUnmount) */ void unmounted(const QString& file); /** * Emitted if during updating the internal watch structures (recursive watches) * the inotify user watch limit was reached. * * This means that not all requested paths can be watched until the user watch * limit is increased. * * The argument is the path being added when the limit was reached. * * This signal will only be emitted once until resetUserLimit is called. */ void watchUserLimitReached(const QString& path); /** * This is emitted once watches have been installed in all the directories * indicated by addWatch */ void installedWatches(); private Q_SLOTS: void slotEvent(int); void slotClearCookies(); private: class Private; Private* const d; + /** + * Recursively iterates over all files/folders inside @param path + * (that are not excluded by the config); + * emits created() signal for each entry (excluding @param path) + * and installs watches for all subdirectories (including @param path) + */ + void handleDirCreated(const QString& path); + Q_PRIVATE_SLOT(d, bool _k_addWatches()) }; #endif