, _parent(p)
, _folder(path)
{
+ _wipePotentialRenamesSoon = new QTimer(this);
+ _wipePotentialRenamesSoon->setInterval(1000);
+ _wipePotentialRenamesSoon->setSingleShot(true);
+ connect(_wipePotentialRenamesSoon, &QTimer::timeout, this, &FolderWatcherPrivate::wipePotentialRenames);
+
_fd = inotify_init();
if (_fd != -1) {
_socket.reset(new QSocketNotifier(_fd, QSocketNotifier::Read));
}
}
+void FolderWatcherPrivate::applyDirectoryRename(const FolderWatcherPrivate::Rename &rename)
+{
+ QString fromSlash = rename.from + "/";
+ qCInfo(lcFolderWatcher) << "Applying rename from" << rename.from << "to" << rename.to;
+ for (auto &watch : _watches) {
+ if (watch == rename.from || watch.startsWith(fromSlash)) {
+ watch = rename.to + watch.mid(rename.from.size());
+ }
+ }
+}
+
void FolderWatcherPrivate::slotAddFolderRecursive(const QString &path)
{
int subdirs = 0;
}
}
+void FolderWatcherPrivate::wipePotentialRenames()
+{
+ _potentialRenames.clear();
+}
+
void FolderWatcherPrivate::slotReceivedNotification(int fd)
{
int len = 0;
error = errno;
}
- // reset counter
- i = 0;
- // while there are enough events in the buffer
- while (i + sizeof(struct inotify_event) < static_cast<unsigned int>(len)) {
+ // iterate events in buffer
+ unsigned int ulen = len;
+ for (i = 0; i + sizeof(inotify_event) < ulen; i += sizeof(inotify_event) + (event ? event->len : 0)) {
// cast an inotify_event
event = (struct inotify_event *)&buffer[i];
if (!event) {
qCDebug(lcFolderWatcher) << "NULL event";
- i += sizeof(struct inotify_event);
continue;
}
// Fire event for the path that was changed.
- if (event->len > 0 && event->wd > -1) {
- QByteArray fileName(event->name);
- if (fileName.startsWith("._sync_")
- || fileName.startsWith(".csync_journal.db")
- || fileName.startsWith(".owncloudsync.log")
- || fileName.startsWith(".sync_")) {
+ if (event->len == 0 || event->wd <= -1)
+ continue;
+ QByteArray fileName(event->name);
+ if (fileName.startsWith("._sync_")
+ || fileName.startsWith(".csync_journal.db")
+ || fileName.startsWith(".owncloudsync.log")
+ || fileName.startsWith(".sync_")) {
+ continue;
+ }
+ const QString p = _watches[event->wd] + '/' + fileName;
+ _parent->changeDetected(p);
+
+ // Collect events to form complete renames where possible
+ // and apply directory renames to the cached paths.
+ if ((event->mask & (IN_MOVED_TO | IN_MOVED_FROM)) && (event->mask & IN_ISDIR) && event->cookie > 0) {
+ auto &rename = _potentialRenames[event->cookie];
+ if (event->mask & IN_MOVED_TO)
+ rename.to = p;
+ if (event->mask & IN_MOVED_FROM)
+ rename.from = p;
+ if (!rename.from.isEmpty() && !rename.to.isEmpty()) {
+ applyDirectoryRename(rename);
+ _potentialRenames.remove(event->cookie);
} else {
- const QString p = _watches[event->wd] + '/' + fileName;
- _parent->changeDetected(p);
+ _wipePotentialRenamesSoon->start();
}
}
-
- // increment counter
- i += sizeof(struct inotify_event) + event->len;
}
}
#include "folderwatcher.h"
+class QTimer;
+
namespace OCC {
/**
void slotReceivedNotification(int fd);
void slotAddFolderRecursive(const QString &path);
+ /// Remove all half-built renames. Called by timer when idle for a bit.
+ void wipePotentialRenames();
+
protected:
+ struct Rename
+ {
+ QString from;
+ QString to;
+ };
+
bool findFoldersBelow(const QDir &dir, QStringList &fullList);
void inotifyRegisterPath(const QString &path);
+ /// Adjusts the paths in _watches when directories are renamed.
+ void applyDirectoryRename(const Rename &rename);
+
private:
FolderWatcher *_parent;
QHash<int, QString> _watches;
QScopedPointer<QSocketNotifier> _socket;
int _fd;
+
+ /** Maps inotify event cookie to rename data.
+ *
+ * For moves two independent inotify events will be seen and they
+ * can be matched via the event cookie. This field stores partial
+ * information as it is received. When both sides have arrived,
+ * directory moves can be processed with applyDirectoryRename().
+ *
+ * If we don't receive both sides (if something moves away from
+ * the watched folder tree, or into it from an unwatched location)
+ * the _wipePotentialRenamesSoon will eventually discard the
+ * incomplete data.
+ *
+ * These events can even be emitted by different watches if the
+ * directory parent folder changed.
+ */
+ QHash<quint32, Rename> _potentialRenames;
+
+ QTimer *_wipePotentialRenamesSoon;
};
}
QVERIFY(waitForPathChanged(old_file));
QVERIFY(waitForPathChanged(new_file));
}
+
+ void testRenameDirectorySameBase() {
+ QString old_file(_rootPath+"/a1/b1");
+ QString new_file(_rootPath+"/a1/brename");
+ QVERIFY(QFile::exists(old_file));
+ mv(old_file, new_file);
+ QVERIFY(QFile::exists(new_file));
+
+ QVERIFY(waitForPathChanged(old_file));
+ QVERIFY(waitForPathChanged(new_file));
+
+ // Verify that further notifications end up with the correct paths
+
+ QString file(_rootPath+"/a1/brename/c1/random.bin");
+ touch(file);
+ QVERIFY(waitForPathChanged(file));
+
+ QString dir(_rootPath+"/a1/brename/newfolder");
+ mkdir(dir);
+ QVERIFY(waitForPathChanged(dir));
+ }
+
+ void testRenameDirectoryDifferentBase() {
+ QString old_file(_rootPath+"/a1/brename");
+ QString new_file(_rootPath+"/bren");
+ QVERIFY(QFile::exists(old_file));
+ mv(old_file, new_file);
+ QVERIFY(QFile::exists(new_file));
+
+ QVERIFY(waitForPathChanged(old_file));
+ QVERIFY(waitForPathChanged(new_file));
+
+ // Verify that further notifications end up with the correct paths
+
+ QString file(_rootPath+"/bren/c1/random.bin");
+ touch(file);
+ QVERIFY(waitForPathChanged(file));
+
+ QString dir(_rootPath+"/bren/newfolder2");
+ mkdir(dir);
+ QVERIFY(waitForPathChanged(dir));
+ }
};
#ifdef Q_OS_MAC