diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2018-11-13 14:22:59 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2018-11-14 10:50:55 +0000 |
commit | 80d26c15080c92012f0a51f7675d3c70ad56a8bd (patch) | |
tree | dc913a0550686c0f8424525990e9fcc325b9f2bc /tools | |
parent | 1613e32042a8e710c3105b926422827ccaad1c5c (diff) |
qmlpreview: Use a better file system watcher
We need to keep watching files even if they are removed and re-added as
editors frequently do when manipulating text files. QFileSystemWatcher
cannot do this by itself, and our simplistic timeout-based attempt
didn't work work properly either.
Fixes: QTBUG-71768
Change-Id: I21e914138179ad8adf07f0196fec8ddcda2cbfca
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmlpreview/qmlpreview.pro | 6 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.cpp | 28 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.h | 7 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp | 150 | ||||
-rw-r--r-- | tools/qmlpreview/qmlpreviewfilesystemwatcher.h | 70 |
5 files changed, 231 insertions, 30 deletions
diff --git a/tools/qmlpreview/qmlpreview.pro b/tools/qmlpreview/qmlpreview.pro index 7d443b5f6c..d42f2cbe07 100644 --- a/tools/qmlpreview/qmlpreview.pro +++ b/tools/qmlpreview/qmlpreview.pro @@ -3,10 +3,12 @@ CONFIG += no_import_scan SOURCES += \ main.cpp \ - qmlpreviewapplication.cpp + qmlpreviewapplication.cpp \ + qmlpreviewfilesystemwatcher.cpp HEADERS += \ - qmlpreviewapplication.h + qmlpreviewapplication.h \ + qmlpreviewfilesystemwatcher.h QMAKE_TARGET_DESCRIPTION = QML Preview diff --git a/tools/qmlpreview/qmlpreviewapplication.cpp b/tools/qmlpreview/qmlpreviewapplication.cpp index 031381a948..02f10831ec 100644 --- a/tools/qmlpreview/qmlpreviewapplication.cpp +++ b/tools/qmlpreview/qmlpreviewapplication.cpp @@ -63,9 +63,9 @@ QmlPreviewApplication::QmlPreviewApplication(int &argc, char **argv) : connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::request, this, &QmlPreviewApplication::serveRequest); - connect(&m_watcher, &QFileSystemWatcher::fileChanged, + connect(&m_watcher, &QmlPreviewFileSystemWatcher::fileChanged, this, &QmlPreviewApplication::sendFile); - connect(&m_watcher, &QFileSystemWatcher::directoryChanged, + connect(&m_watcher, &QmlPreviewFileSystemWatcher::directoryChanged, this, &QmlPreviewApplication::sendDirectory); } @@ -212,17 +212,12 @@ void QmlPreviewApplication::serveRequest(const QString &path) if (info.isDir()) { m_qmlPreviewClient->sendDirectory(path, QDir(path).entryList()); - m_watcher.addPath(path); + m_watcher.addDirectory(path); } else { QFile file(path); if (file.open(QIODevice::ReadOnly)) { m_qmlPreviewClient->sendFile(path, file.readAll()); - m_watcher.addPath(path); - - // Also watch the directory, because editors will rather replace a file than change it. - // Therefore when the file changes, we can't read it, but when the file is re-added we can - // see that from the directory changing. - m_watcher.addPath(info.absolutePath()); + m_watcher.addFile(path); } else { logStatus(QString("Could not open file %1 for reading: %2").arg(path) .arg(file.errorString())); @@ -236,13 +231,10 @@ bool QmlPreviewApplication::sendFile(const QString &path) QFile file(path); if (file.open(QIODevice::ReadOnly)) { m_qmlPreviewClient->sendFile(path, file.readAll()); - m_pendingFiles.removeAll(path); // Defer the Load, because files tend to change multiple times in a row. m_loadTimer.start(); return true; } - if (!m_pendingFiles.contains(path)) - m_pendingFiles.append(path); logStatus(QString("Could not open file %1 for reading: %2").arg(path).arg(file.errorString())); return false; } @@ -250,17 +242,5 @@ bool QmlPreviewApplication::sendFile(const QString &path) void QmlPreviewApplication::sendDirectory(const QString &path) { m_qmlPreviewClient->sendDirectory(path, QDir(path).entryList()); - for (auto it = m_pendingFiles.begin(); it != m_pendingFiles.end();) { - const QString filePath = *it; - QFile file(filePath); - if (file.open(QIODevice::ReadOnly)) { - logStatus(QString("Sending replaced file %1.").arg(filePath)); - m_qmlPreviewClient->sendFile(filePath, file.readAll()); - m_watcher.addPath(filePath); - it = m_pendingFiles.erase(it); - } else { - ++it; - } - } m_loadTimer.start(); } diff --git a/tools/qmlpreview/qmlpreviewapplication.h b/tools/qmlpreview/qmlpreviewapplication.h index eb363b0eb6..7da4a9ab5c 100644 --- a/tools/qmlpreview/qmlpreviewapplication.h +++ b/tools/qmlpreview/qmlpreviewapplication.h @@ -29,13 +29,14 @@ #ifndef QMLPREVIEWAPPLICATION_H #define QMLPREVIEWAPPLICATION_H +#include "qmlpreviewfilesystemwatcher.h" + #include <private/qqmlpreviewclient_p.h> #include <private/qqmldebugconnection_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qprocess.h> #include <QtCore/qtimer.h> -#include <QtCore/qfilesystemwatcher.h> #include <QtNetwork/qabstractsocket.h> @@ -71,13 +72,11 @@ private: QScopedPointer<QQmlDebugConnection> m_connection; QScopedPointer<QQmlPreviewClient> m_qmlPreviewClient; - QFileSystemWatcher m_watcher; + QmlPreviewFileSystemWatcher m_watcher; QTimer m_loadTimer; QTimer m_connectTimer; uint m_connectionAttempts; - - QStringList m_pendingFiles; }; #endif // QMLPREVIEWAPPLICATION_H diff --git a/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp b/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp new file mode 100644 index 0000000000..78a6ccead3 --- /dev/null +++ b/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlpreviewfilesystemwatcher.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtCore/qfilesystemwatcher.h> + +QmlPreviewFileSystemWatcher::QmlPreviewFileSystemWatcher(QObject *parent) : + QObject(parent), m_watcher(new QFileSystemWatcher(this)) +{ + connect(m_watcher, &QFileSystemWatcher::fileChanged, + this, &QmlPreviewFileSystemWatcher::fileChanged); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, + this, &QmlPreviewFileSystemWatcher::onDirectoryChanged); +} + +bool QmlPreviewFileSystemWatcher::watchesFile(const QString &file) const +{ + return m_files.contains(file); +} + +void QmlPreviewFileSystemWatcher::addFile(const QString &file) +{ + if (watchesFile(file)) { + qWarning() << "FileSystemWatcher: File" << file << "is already being watched."; + return; + } + + QStringList toAdd(file); + m_files.insert(file); + + const QString directory = QFileInfo(file).path(); + const int dirCount = ++m_directoryCount[directory]; + Q_ASSERT(dirCount > 0); + + if (dirCount == 1) + toAdd.append(directory); + + m_watcher->addPaths(toAdd); +} + +void QmlPreviewFileSystemWatcher::removeFile(const QString &file) +{ + WatchEntrySetIterator it = m_files.find(file); + if (it == m_files.end()) { + qWarning() << "FileSystemWatcher: File" << file << "is not watched."; + return; + } + + QStringList toRemove(file); + m_files.erase(it); + m_watcher->removePath(file); + + const QString directory = QFileInfo(file).path(); + const int dirCount = --m_directoryCount[directory]; + Q_ASSERT(dirCount >= 0); + + if (!dirCount) + toRemove.append(directory); + + m_watcher->removePaths(toRemove); +} + +bool QmlPreviewFileSystemWatcher::watchesDirectory(const QString &directory) const +{ + return m_directories.contains(directory); +} + +void QmlPreviewFileSystemWatcher::addDirectory(const QString &directory) +{ + if (watchesDirectory(directory)) { + qWarning() << "FileSystemWatcher: Directory" << directory << "is already being watched."; + return; + } + + m_directories.insert(directory); + const int count = ++m_directoryCount[directory]; + Q_ASSERT(count > 0); + + if (count == 1) + m_watcher->addPath(directory); +} + +void QmlPreviewFileSystemWatcher::removeDirectory(const QString &directory) +{ + WatchEntrySetIterator it = m_directories.find(directory); + if (it == m_directories.end()) { + qWarning() << "FileSystemWatcher: Directory" << directory << "is not watched."; + return; + } + + m_directories.erase(it); + + const int count = --m_directoryCount[directory]; + Q_ASSERT(count >= 0); + + if (!count) + m_watcher->removePath(directory); +} + +void QmlPreviewFileSystemWatcher::onDirectoryChanged(const QString &path) +{ + if (m_directories.contains(path)) + emit directoryChanged(path); + + QStringList toReadd; + const QDir dir(path); + for (const QFileInfo &entry : dir.entryInfoList(QDir::Files)) { + const QString file = entry.filePath(); + if (m_files.contains(file)) + toReadd.append(file); + } + + if (!toReadd.isEmpty()) { + const QStringList remove = m_watcher->addPaths(toReadd); + for (const QString &rejected : remove) + toReadd.removeOne(rejected); + + // If we've successfully added the file, that means it was deleted and replaced. + for (const QString &reAdded : qAsConst(toReadd)) + emit fileChanged(reAdded); + } +} diff --git a/tools/qmlpreview/qmlpreviewfilesystemwatcher.h b/tools/qmlpreview/qmlpreviewfilesystemwatcher.h new file mode 100644 index 0000000000..a7ead641d8 --- /dev/null +++ b/tools/qmlpreview/qmlpreviewfilesystemwatcher.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLPREVIEWFILESYSTEMWATCHER_H +#define QMLPREVIEWFILESYSTEMWATCHER_H + +#include <QtCore/qfilesystemwatcher.h> +#include <QtCore/qobject.h> +#include <QtCore/qset.h> + +class QmlPreviewFileSystemWatcher : public QObject +{ + Q_OBJECT + +public: + explicit QmlPreviewFileSystemWatcher(QObject *parent = nullptr); + + void addFile(const QString &file); + void removeFile(const QString &file); + bool watchesFile(const QString &file) const; + + void addDirectory(const QString &file); + void removeDirectory(const QString &file); + bool watchesDirectory(const QString &file) const; + +signals: + void fileChanged(const QString &path); + void directoryChanged(const QString &path); + +private: + using WatchEntrySet = QSet<QString>; + using WatchEntrySetIterator = WatchEntrySet::iterator; + + void onDirectoryChanged(const QString &path); + + WatchEntrySet m_files; + WatchEntrySet m_directories; + + // Directories watched either explicitly or implicitly through files contained in them. + QHash<QString, int> m_directoryCount; + + QFileSystemWatcher *m_watcher = nullptr; +}; + +#endif // QMLPREVIEWFILESYSTEMWATCHER_H |