diff options
author | Christian Kamm <mail@ckamm.de> | 2018-10-12 12:03:10 +0300 |
---|---|---|
committer | ckamm <mail@ckamm.de> | 2018-10-12 13:06:48 +0300 |
commit | 55cf407337a5db93511f1cb061165c53fa6dba6e (patch) | |
tree | 9cc826d82cf598c7923e2ba16a938d2b10dbade8 /src | |
parent | d0815239807540c423c52282e57a5ed8ebfd41e2 (diff) |
Folderwatcher: On linux, fix paths after dir renames #6808
If a folder was renamed A -> B, the folder watcher for the inode
would be unaware and still report changes for A/foo. Now directory
renames in the watched folders are tracked and paths are updated
accordingly.
Diffstat (limited to 'src')
-rw-r--r-- | src/gui/folderwatcher_linux.cpp | 64 | ||||
-rw-r--r-- | src/gui/folderwatcher_linux.h | 33 |
2 files changed, 81 insertions, 16 deletions
diff --git a/src/gui/folderwatcher_linux.cpp b/src/gui/folderwatcher_linux.cpp index 5f6d580b1..d2fdb7d8d 100644 --- a/src/gui/folderwatcher_linux.cpp +++ b/src/gui/folderwatcher_linux.cpp @@ -31,6 +31,11 @@ FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path , _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)); @@ -91,6 +96,17 @@ void FolderWatcherPrivate::inotifyRegisterPath(const QString &path) } } +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; @@ -126,6 +142,11 @@ void FolderWatcherPrivate::slotAddFolderRecursive(const QString &path) } } +void FolderWatcherPrivate::wipePotentialRenames() +{ + _potentialRenames.clear(); +} + void FolderWatcherPrivate::slotReceivedNotification(int fd) { int len; @@ -154,33 +175,44 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) } } while (false); - // 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 == NULL) { 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; } } diff --git a/src/gui/folderwatcher_linux.h b/src/gui/folderwatcher_linux.h index 24d337777..e9361eb42 100644 --- a/src/gui/folderwatcher_linux.h +++ b/src/gui/folderwatcher_linux.h @@ -23,6 +23,8 @@ #include "folderwatcher.h" +class QTimer; + namespace OCC { /** @@ -44,10 +46,22 @@ protected slots: 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; @@ -55,6 +69,25 @@ private: 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; }; } |