diff options
-rw-r--r-- | src/gui/itemmodels/qfileinfogatherer.cpp | 51 | ||||
-rw-r--r-- | src/gui/itemmodels/qfileinfogatherer_p.h | 6 | ||||
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel.cpp | 73 | ||||
-rw-r--r-- | src/gui/itemmodels/qfilesystemmodel_p.h | 4 |
4 files changed, 95 insertions, 39 deletions
diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp index e0f2d3c41a..94981b482e 100644 --- a/src/gui/itemmodels/qfileinfogatherer.cpp +++ b/src/gui/itemmodels/qfileinfogatherer.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qfileinfogatherer_p.h" +#include <qcoreapplication.h> #include <qdebug.h> #include <qdiriterator.h> #include <private/qfileinfo_p.h> @@ -57,11 +58,40 @@ QFileInfoGatherer::QFileInfoGatherer(QObject *parent) */ QFileInfoGatherer::~QFileInfoGatherer() { - abort.storeRelaxed(true); + requestAbort(); + wait(); +} + +bool QFileInfoGatherer::event(QEvent *event) +{ + if (event->type() == QEvent::DeferredDelete && isRunning()) { + // We have been asked to shut down later but were blocked, + // so the owning QFileSystemModel proceeded with its shut-down + // and deferred the destruction of the gatherer. + // If we are still blocked now, then we have three bad options: + // terminate, wait forever (preventing the process from shutting down), + // or accept a memory leak. + requestAbort(); + if (!wait(5000)) { + // If the application is shutting down, then we terminate. + // Otherwise assume that sooner or later the thread will finish, + // and we delete it then. + if (QCoreApplication::closingDown()) + terminate(); + else + connect(this, &QThread::finished, this, [this]{ delete this; }); + return true; + } + } + + return QThread::event(event); +} + +void QFileInfoGatherer::requestAbort() +{ + requestInterruption(); QMutexLocker locker(&mutex); condition.wakeAll(); - locker.unlock(); - wait(); } void QFileInfoGatherer::setResolveSymlinks(bool enable) @@ -280,10 +310,13 @@ void QFileInfoGatherer::list(const QString &directoryPath) void QFileInfoGatherer::run() { forever { + // Disallow termination while we are holding a mutex or can be + // woken up cleanly. + setTerminationEnabled(false); QMutexLocker locker(&mutex); - while (!abort.loadRelaxed() && path.isEmpty()) + while (!isInterruptionRequested() && path.isEmpty()) condition.wait(&mutex); - if (abort.loadRelaxed()) + if (isInterruptionRequested()) return; const QString thisPath = std::as_const(path).front(); path.pop_front(); @@ -291,6 +324,10 @@ void QFileInfoGatherer::run() files.pop_front(); locker.unlock(); + // Some of the system APIs we call when gathering file infomration + // might hang (e.g. waiting for network), so we explicitly allow + // termination now. + setTerminationEnabled(true); getFileInfos(thisPath, thisList); } } @@ -369,7 +406,7 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil QStringList allFiles; if (files.isEmpty()) { QDirIterator dirIt(path, QDir::AllEntries | QDir::System | QDir::Hidden); - while (!abort.loadRelaxed() && dirIt.hasNext()) { + while (!isInterruptionRequested() && dirIt.hasNext()) { fileInfo = dirIt.nextFileInfo(); fileInfo.stat(); allFiles.append(fileInfo.fileName()); @@ -380,7 +417,7 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil emit newListOfFiles(path, allFiles); QStringList::const_iterator filesIt = filesToCheck.constBegin(); - while (!abort.loadRelaxed() && filesIt != filesToCheck.constEnd()) { + while (!isInterruptionRequested() && filesIt != filesToCheck.constEnd()) { fileInfo.setFile(path + QDir::separator() + *filesIt); ++filesIt; fileInfo.stat(); diff --git a/src/gui/itemmodels/qfileinfogatherer_p.h b/src/gui/itemmodels/qfileinfogatherer_p.h index dc81cdaee1..3d5f59c22e 100644 --- a/src/gui/itemmodels/qfileinfogatherer_p.h +++ b/src/gui/itemmodels/qfileinfogatherer_p.h @@ -149,6 +149,8 @@ public: QAbstractFileIconProvider *iconProvider() const; bool resolveSymlinks() const; + void requestAbort(); + public Q_SLOTS: void list(const QString &directoryPath); void fetchExtendedInformation(const QString &path, const QStringList &files); @@ -160,6 +162,9 @@ private Q_SLOTS: void driveAdded(); void driveRemoved(); +protected: + bool event(QEvent *event) override; + private: void run() override; // called by run(): @@ -176,7 +181,6 @@ private: QStack<QString> path; QStack<QStringList> files; // end protected by mutex - QAtomicInt abort; #if QT_CONFIG(filesystemwatcher) QFileSystemWatcher *m_watcher = nullptr; diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp index f8810366df..2000afc5b2 100644 --- a/src/gui/itemmodels/qfilesystemmodel.cpp +++ b/src/gui/itemmodels/qfilesystemmodel.cpp @@ -438,7 +438,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this); node = p->addNode(parent, element,info); #if QT_CONFIG(filesystemwatcher) - node->populate(fileInfoGatherer.getInfo(info)); + node->populate(fileInfoGatherer->getInfo(info)); #endif } else { node = parent->children.value(element); @@ -479,7 +479,7 @@ void QFileSystemModel::timerEvent(QTimerEvent *event) for (int i = 0; i < d->toFetch.size(); ++i) { const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node; if (!node->hasInformation()) { - d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir, + d->fileInfoGatherer->fetchExtendedInformation(d->toFetch.at(i).dir, QStringList(d->toFetch.at(i).file)); } else { // qDebug("yah!, you saved a little gerbil soul"); @@ -650,7 +650,7 @@ void QFileSystemModel::fetchMore(const QModelIndex &parent) return; indexNode->populatedChildren = true; #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.list(filePath(parent)); + d->fileInfoGatherer->list(filePath(parent)); #endif } @@ -693,7 +693,7 @@ QVariant QFileSystemModel::myComputer(int role) const return QFileSystemModelPrivate::myComputer(); #if QT_CONFIG(filesystemwatcher) case Qt::DecorationRole: - return d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Computer); + return d->fileInfoGatherer->iconProvider()->icon(QAbstractFileIconProvider::Computer); #endif } return QVariant(); @@ -734,9 +734,9 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const #if QT_CONFIG(filesystemwatcher) if (icon.isNull()) { if (d->node(index)->isDir()) - icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Folder); + icon = d->fileInfoGatherer->iconProvider()->icon(QAbstractFileIconProvider::Folder); else - icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::File); + icon = d->fileInfoGatherer->iconProvider()->icon(QAbstractFileIconProvider::File); } #endif // filesystemwatcher return icon; @@ -816,7 +816,7 @@ QString QFileSystemModelPrivate::name(const QModelIndex &index) const QFileSystemNode *dirNode = node(index); if ( #if QT_CONFIG(filesystemwatcher) - fileInfoGatherer.resolveSymlinks() && + fileInfoGatherer->resolveSymlinks() && #endif !resolvedSymLinks.isEmpty() && dirNode->isSymLink(/* ignoreNtfsSymLinks = */ true)) { QString fullPath = QDir::fromNativeSeparators(filePath(index)); @@ -901,7 +901,7 @@ bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, in nodeToRename->fileName = newName; nodeToRename->parent = parentNode; #if QT_CONFIG(filesystemwatcher) - nodeToRename->populate(d->fileInfoGatherer.getInfo(QFileInfo(parentPath, newName))); + nodeToRename->populate(d->fileInfoGatherer->getInfo(QFileInfo(parentPath, newName))); #endif nodeToRename->isVisible = true; parentNode->children[newName] = nodeToRename.release(); @@ -1326,7 +1326,7 @@ void QFileSystemModel::setOptions(Options options) #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); if (changed.testFlag(DontWatchForChanges)) - d->fileInfoGatherer.setWatching(!options.testFlag(DontWatchForChanges)); + d->fileInfoGatherer->setWatching(!options.testFlag(DontWatchForChanges)); #endif if (changed.testFlag(DontUseCustomDirectoryIcons)) { @@ -1347,7 +1347,7 @@ QFileSystemModel::Options QFileSystemModel::options() const result.setFlag(DontResolveSymlinks, !resolveSymlinks()); #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - result.setFlag(DontWatchForChanges, !d->fileInfoGatherer.isWatching()); + result.setFlag(DontWatchForChanges, !d->fileInfoGatherer->isWatching()); #else result.setFlag(DontWatchForChanges); #endif @@ -1369,7 +1369,7 @@ QString QFileSystemModel::filePath(const QModelIndex &index) const QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index); if (dirNode->isSymLink() #if QT_CONFIG(filesystemwatcher) - && d->fileInfoGatherer.resolveSymlinks() + && d->fileInfoGatherer->resolveSymlinks() #endif && d->resolvedSymLinks.contains(fullPath) && dirNode->isDir()) { @@ -1431,7 +1431,7 @@ QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &na Q_ASSERT(parentNode->children.contains(name)); QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name]; #if QT_CONFIG(filesystemwatcher) - node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); + node->populate(d->fileInfoGatherer->getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name))); #endif d->addVisibleFiles(parentNode, QStringList(name)); return d->index(node); @@ -1499,7 +1499,7 @@ QModelIndex QFileSystemModel::setRootPath(const QString &newPath) if (!rootPath().isEmpty() && rootPath() != "."_L1) { //This remove the watcher for the old rootPath #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.removePath(rootPath()); + d->fileInfoGatherer->removePath(rootPath()); #endif //This line "marks" the node as dirty, so the next fetchMore //call on the path will ask the gatherer to install a watcher again @@ -1555,7 +1555,7 @@ void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider) { Q_D(QFileSystemModel); #if QT_CONFIG(filesystemwatcher) - d->fileInfoGatherer.setIconProvider(provider); + d->fileInfoGatherer->setIconProvider(provider); #endif d->root.updateIcon(provider, QString()); } @@ -1567,7 +1567,7 @@ QAbstractFileIconProvider *QFileSystemModel::iconProvider() const { #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - return d->fileInfoGatherer.iconProvider(); + return d->fileInfoGatherer->iconProvider(); #else return 0; #endif @@ -1623,7 +1623,7 @@ void QFileSystemModel::setResolveSymlinks(bool enable) { #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); - d->fileInfoGatherer.setResolveSymlinks(enable); + d->fileInfoGatherer->setResolveSymlinks(enable); #else Q_UNUSED(enable); #endif @@ -1633,7 +1633,7 @@ bool QFileSystemModel::resolveSymlinks() const { #if QT_CONFIG(filesystemwatcher) Q_D(const QFileSystemModel); - return d->fileInfoGatherer.resolveSymlinks(); + return d->fileInfoGatherer->resolveSymlinks(); #else return false; #endif @@ -1738,7 +1738,7 @@ bool QFileSystemModel::event(QEvent *event) #if QT_CONFIG(filesystemwatcher) Q_D(QFileSystemModel); if (event->type() == QEvent::LanguageChange) { - d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString()); + d->root.retranslateStrings(d->fileInfoGatherer->iconProvider(), QString()); return true; } #endif @@ -1752,7 +1752,7 @@ bool QFileSystemModel::rmdir(const QModelIndex &aindex) #if QT_CONFIG(filesystemwatcher) if (success) { QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func()); - d->fileInfoGatherer.removePath(path); + d->fileInfoGatherer->removePath(path); } #endif return success; @@ -1924,7 +1924,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path, for (const auto &update : updates) { QString fileName = update.first; Q_ASSERT(!fileName.isEmpty()); - QExtendedInformation info = fileInfoGatherer.getInfo(update.second); + QExtendedInformation info = fileInfoGatherer->getInfo(update.second); bool previouslyHere = parentNode->children.contains(fileName); if (!previouslyHere) { addNode(parentNode, fileName, info.fileInfo()); @@ -2055,22 +2055,37 @@ QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index) return false; }; - const QStringList &watchedFiles = fileInfoGatherer.watchedFiles(); + const QStringList &watchedFiles = fileInfoGatherer->watchedFiles(); std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(), std::back_inserter(result), filter); - const QStringList &watchedDirectories = fileInfoGatherer.watchedDirectories(); + const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories(); std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(), std::back_inserter(result), filter); - fileInfoGatherer.unwatchPaths(result); + fileInfoGatherer->unwatchPaths(result); return result; } #endif // filesystemwatcher && Q_OS_WIN -QFileSystemModelPrivate::QFileSystemModelPrivate() = default; +QFileSystemModelPrivate::QFileSystemModelPrivate() + : fileInfoGatherer(new QFileInfoGatherer) +{ +} -QFileSystemModelPrivate::~QFileSystemModelPrivate() = default; +QFileSystemModelPrivate::~QFileSystemModelPrivate() +{ + fileInfoGatherer->requestInterruption(); + if (!fileInfoGatherer->wait(1000)) { + // If the thread hangs, perhaps because the network was disconnected + // while the gatherer was stat'ing a remote file, then don't block + // shutting down the model (which might block a file dialog and the + // main thread). Schedule the gatherer for later deletion; it's + // destructor will wait for the thread to finish. + auto *rawGatherer = fileInfoGatherer.release(); + rawGatherer->deleteLater(); + } +} /*! \internal @@ -2083,13 +2098,13 @@ void QFileSystemModelPrivate::init() qRegisterMetaType<QList<std::pair<QString, QFileInfo>>>(); #if QT_CONFIG(filesystemwatcher) - q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)), + q->connect(fileInfoGatherer.get(), SIGNAL(newListOfFiles(QString,QStringList)), q, SLOT(_q_directoryChanged(QString,QStringList))); - q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QList<std::pair<QString,QFileInfo>>)), q, + q->connect(fileInfoGatherer.get(), SIGNAL(updates(QString,QList<std::pair<QString,QFileInfo>>)), q, SLOT(_q_fileSystemChanged(QString,QList<std::pair<QString,QFileInfo>>))); - q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)), + q->connect(fileInfoGatherer.get(), SIGNAL(nameResolved(QString,QString)), q, SLOT(_q_resolvedName(QString,QString))); - q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)), + q->connect(fileInfoGatherer.get(), SIGNAL(directoryLoaded(QString)), q, SIGNAL(directoryLoaded(QString))); #endif // filesystemwatcher q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection); diff --git a/src/gui/itemmodels/qfilesystemmodel_p.h b/src/gui/itemmodels/qfilesystemmodel_p.h index b46be018a9..784c135357 100644 --- a/src/gui/itemmodels/qfilesystemmodel_p.h +++ b/src/gui/itemmodels/qfilesystemmodel_p.h @@ -259,9 +259,9 @@ public: #if QT_CONFIG(filesystemwatcher) # ifdef Q_OS_WIN QStringList unwatchPathsAt(const QModelIndex &); - void watchPaths(const QStringList &paths) { fileInfoGatherer.watchPaths(paths); } + void watchPaths(const QStringList &paths) { fileInfoGatherer->watchPaths(paths); } # endif // Q_OS_WIN - QFileInfoGatherer fileInfoGatherer; + std::unique_ptr<QFileInfoGatherer> fileInfoGatherer; #endif // filesystemwatcher QTimer delayedSortTimer; QHash<const QFileSystemNode*, bool> bypassFilters; |