diff options
Diffstat (limited to 'src/gui/itemmodels/qfileinfogatherer.cpp')
-rw-r--r-- | src/gui/itemmodels/qfileinfogatherer.cpp | 193 |
1 files changed, 107 insertions, 86 deletions
diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp index bd368e945c..41fb0a0db5 100644 --- a/src/gui/itemmodels/qfileinfogatherer.cpp +++ b/src/gui/itemmodels/qfileinfogatherer.cpp @@ -1,45 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** 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-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// 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 <qdirlisting.h> +#include <private/qabstractfileiconprovider_p.h> #include <private/qfileinfo_p.h> #ifndef Q_OS_WIN # include <unistd.h> @@ -51,8 +17,10 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + #ifdef QT_BUILD_INTERNAL -static QBasicAtomicInt fetchedRoot = Q_BASIC_ATOMIC_INITIALIZER(false); +Q_CONSTINIT static QBasicAtomicInt fetchedRoot = Q_BASIC_ATOMIC_INITIALIZER(false); Q_AUTOTEST_EXPORT void qt_test_resetFetchedRoot() { fetchedRoot.storeRelaxed(false); @@ -68,9 +36,9 @@ static QString translateDriveName(const QFileInfo &drive) { QString driveName = drive.absoluteFilePath(); #ifdef Q_OS_WIN - if (driveName.startsWith(QLatin1Char('/'))) // UNC host + if (driveName.startsWith(u'/')) // UNC host return drive.fileName(); - if (driveName.endsWith(QLatin1Char('/'))) + if (driveName.endsWith(u'/')) driveName.chop(1); #endif // Q_OS_WIN return driveName; @@ -91,11 +59,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) @@ -117,7 +114,7 @@ void QFileInfoGatherer::driveRemoved() const QFileInfoList driveInfoList = QDir::drives(); for (const QFileInfo &fi : driveInfoList) drives.append(translateDriveName(fi)); - newListOfFiles(QString(), drives); + emit newListOfFiles(QString(), drives); } bool QFileInfoGatherer::resolveSymlinks() const @@ -148,21 +145,24 @@ void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStr { QMutexLocker locker(&mutex); // See if we already have this dir/file in our queue - int loc = this->path.lastIndexOf(path); - while (loc > 0) { - if (this->files.at(loc) == files) { + qsizetype loc = 0; + while ((loc = this->path.lastIndexOf(path, loc - 1)) != -1) { + if (this->files.at(loc) == files) return; - } - loc = this->path.lastIndexOf(path, loc - 1); } + +#if QT_CONFIG(thread) this->path.push(path); this->files.push(files); condition.wakeAll(); +#else // !QT_CONFIG(thread) + getFileInfos(path, files); +#endif // QT_CONFIG(thread) #if QT_CONFIG(filesystemwatcher) if (files.isEmpty() && !path.isEmpty() - && !path.startsWith(QLatin1String("//")) /*don't watch UNC path*/) { + && !path.startsWith("//"_L1) /*don't watch UNC path*/) { if (!watchedDirectories().contains(path)) watchPaths(QStringList(path)); } @@ -176,8 +176,8 @@ void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStr */ void QFileInfoGatherer::updateFile(const QString &filePath) { - QString dir = filePath.mid(0, filePath.lastIndexOf(QLatin1Char('/'))); - QString fileName = filePath.mid(dir.length() + 1); + QString dir = filePath.mid(0, filePath.lastIndexOf(u'/')); + QString fileName = filePath.mid(dir.size() + 1); fetchExtendedInformation(dir, QStringList(fileName)); } @@ -250,16 +250,23 @@ bool QFileInfoGatherer::isWatching() const return result; } +/*! \internal + + If \a v is \c false, the QFileSystemWatcher used internally will be deleted + and subsequent calls to watchPaths() will do nothing. + + If \a v is \c true, subsequent calls to watchPaths() will add those paths to + the filesystem watcher; watchPaths() will initialize a QFileSystemWatcher if + one hasn't already been initialized. +*/ void QFileInfoGatherer::setWatching(bool v) { #if QT_CONFIG(filesystemwatcher) QMutexLocker locker(&mutex); if (v != m_watching) { - if (!v) { - delete m_watcher; - m_watcher = nullptr; - } m_watching = v; + if (!m_watching) + delete std::exchange(m_watcher, nullptr); } #else Q_UNUSED(v); @@ -311,17 +318,24 @@ 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 = qAsConst(path).front(); + const QString thisPath = std::as_const(path).front(); path.pop_front(); - const QStringList thisList = qAsConst(files).front(); + const QStringList thisList = std::as_const(files).front(); 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); } } @@ -329,8 +343,12 @@ void QFileInfoGatherer::run() QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const { QExtendedInformation info(fileInfo); - info.icon = m_iconProvider->icon(fileInfo); - info.displayType = m_iconProvider->type(fileInfo); + if (m_iconProvider) { + info.icon = m_iconProvider->icon(fileInfo); + info.displayType = m_iconProvider->type(fileInfo); + } else { + info.displayType = QAbstractFileIconProviderPrivate::getFileType(fileInfo); + } #if QT_CONFIG(filesystemwatcher) // ### Not ready to listen all modifications by default static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES"); @@ -370,21 +388,23 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil #ifdef QT_BUILD_INTERNAL fetchedRoot.storeRelaxed(true); #endif - QFileInfoList infoList; + QList<std::pair<QString, QFileInfo>> updatedFiles; + auto addToUpdatedFiles = [&updatedFiles](QFileInfo &&fileInfo) { + fileInfo.stat(); + updatedFiles.emplace_back(std::pair{translateDriveName(fileInfo), fileInfo}); + }; + if (files.isEmpty()) { - infoList = QDir::drives(); + // QDir::drives() calls QFSFileEngine::drives() which creates the QFileInfoList on + // the stack and return it, so this list is not shared, so no detaching. + QFileInfoList infoList = QDir::drives(); + updatedFiles.reserve(infoList.size()); + for (auto rit = infoList.rbegin(), rend = infoList.rend(); rit != rend; ++rit) + addToUpdatedFiles(std::move(*rit)); } else { - infoList.reserve(files.count()); - for (const auto &file : files) - infoList << QFileInfo(file); - } - QList<QPair<QString, QFileInfo>> updatedFiles; - updatedFiles.reserve(infoList.count()); - for (int i = infoList.count() - 1; i >= 0; --i) { - QFileInfo driveInfo = infoList.at(i); - driveInfo.stat(); - QString driveName = translateDriveName(driveInfo); - updatedFiles.append(QPair<QString,QFileInfo>(driveName, driveInfo)); + updatedFiles.reserve(files.size()); + for (auto rit = files.crbegin(), rend = files.crend(); rit != rend; ++rit) + addToUpdatedFiles(QFileInfo(*rit)); } emit updates(path, updatedFiles); return; @@ -394,15 +414,16 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil base.start(); QFileInfo fileInfo; bool firstTime = true; - QList<QPair<QString, QFileInfo>> updatedFiles; + QList<std::pair<QString, QFileInfo>> updatedFiles; QStringList filesToCheck = files; QStringList allFiles; if (files.isEmpty()) { - QDirIterator dirIt(path, QDir::AllEntries | QDir::System | QDir::Hidden); - while (!abort.loadRelaxed() && dirIt.hasNext()) { - dirIt.next(); - fileInfo = dirIt.fileInfo(); + constexpr auto dirFilters = QDir::AllEntries | QDir::System | QDir::Hidden; + for (const auto &dirEntry : QDirListing(path, dirFilters)) { + if (isInterruptionRequested()) + break; + fileInfo = dirEntry.fileInfo(); fileInfo.stat(); allFiles.append(fileInfo.fileName()); fetch(fileInfo, base, firstTime, updatedFiles, path); @@ -412,7 +433,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(); @@ -424,12 +445,12 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil } void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime, - QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path) + QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path) { - updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo)); + updatedFiles.emplace_back(std::pair(fileInfo.fileName(), fileInfo)); QElapsedTimer current; current.start(); - if ((firstTime && updatedFiles.count() > 100) || base.msecsTo(current) > 1000) { + if ((firstTime && updatedFiles.size() > 100) || base.msecsTo(current) > 1000) { emit updates(path, updatedFiles); updatedFiles.clear(); base = current; |