diff options
author | Marcus Tillmanns <marcus.tillmanns@qt.io> | 2022-10-05 15:42:14 +0200 |
---|---|---|
committer | Marcus Tillmanns <marcus.tillmanns@qt.io> | 2022-10-11 08:53:02 +0000 |
commit | ae58d373b02a4a1e3391aa7f6df17d103db1a060 (patch) | |
tree | 8c1d7502c723849dcea408990716fed5ffe16d8b | |
parent | e5e90ad9310f6f662e4eae8dbf2c5b7f8a279168 (diff) |
Add FSEngine FilePath Cache
To speed up file dialogs we introduce a 1 minute cache for
the FilePathInfo.
A new version of "IDevice::iterateDirectories" allows implementations to
provide the FilePathInfo directly.
DockerDevice implements fetching the filePathInfo during
iterateDirectories which greatly improves the speed again.
Change-Id: I24ac16adb2478cbf16a22012e72fcb8910dcdac5
Reviewed-by: hjk <hjk@qt.io>
-rw-r--r-- | src/libs/utils/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/libs/utils/filepath.cpp | 78 | ||||
-rw-r--r-- | src/libs/utils/filepath.h | 25 | ||||
-rw-r--r-- | src/libs/utils/filepathinfo.h | 56 | ||||
-rw-r--r-- | src/libs/utils/fileutils.cpp | 165 | ||||
-rw-r--r-- | src/libs/utils/fileutils.h | 13 | ||||
-rw-r--r-- | src/libs/utils/fsengine/fileiconprovider.cpp | 60 | ||||
-rw-r--r-- | src/libs/utils/fsengine/filepathinfocache.h | 68 | ||||
-rw-r--r-- | src/libs/utils/fsengine/fsengine_impl.cpp | 52 | ||||
-rw-r--r-- | src/plugins/docker/dockerdevice.cpp | 115 | ||||
-rw-r--r-- | src/plugins/docker/dockerdevice.h | 10 | ||||
-rw-r--r-- | src/plugins/projectexplorer/devicesupport/devicemanager.cpp | 18 | ||||
-rw-r--r-- | src/plugins/projectexplorer/devicesupport/idevice.cpp | 33 | ||||
-rw-r--r-- | src/plugins/projectexplorer/devicesupport/idevice.h | 8 |
14 files changed, 579 insertions, 124 deletions
diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 2cd8223d83e..1d7099bc3a9 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -48,6 +48,7 @@ add_qtc_library(Utils fileinprojectfinder.cpp fileinprojectfinder.h filenamevalidatinglineedit.cpp filenamevalidatinglineedit.h filepath.cpp filepath.h + filepathinfo.h filesearch.cpp filesearch.h filesystemmodel.cpp filesystemmodel.h filesystemwatcher.cpp filesystemwatcher.h @@ -264,6 +265,7 @@ extend_qtc_library(Utils fsengine/fixedlistfsengine.h fsengine/fsenginehandler.cpp fsengine/fsenginehandler.h + fsengine/filepathinfocache.h ) if (WIN32) diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 3aa7e4a11d6..80035ac504f 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -513,8 +513,7 @@ FilePaths FilePath::dirEntries(QDir::Filters filters) const // either of the specified \a nameFilters. // An empty \nameFilters list matches every name. -void FilePath::iterateDirectory(const std::function<bool(const FilePath &item)> &callBack, - const FileFilter &filter) const +void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const { if (needsDevice()) { QTC_ASSERT(s_deviceHooks.iterateDirectory, return); @@ -529,8 +528,25 @@ void FilePath::iterateDirectory(const std::function<bool(const FilePath &item)> } } +void FilePath::iterateDirectory(const IterateDirWithInfoCallback &callBack, + const FileFilter &filter) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.iterateDirectoryWithInfo, return); + s_deviceHooks.iterateDirectoryWithInfo(*this, callBack, filter); + return; + } + + QDirIterator it(path(), filter.nameFilters, filter.fileFilters, filter.iteratorFlags); + while (it.hasNext()) { + const FilePath path = FilePath::fromString(it.next()); + if (!callBack(path, path.filePathInfo())) + return; + } +} + void FilePath::iterateDirectories(const FilePaths &dirs, - const std::function<bool(const FilePath &)> &callBack, + const IterateDirCallback &callBack, const FileFilter &filter) { for (const FilePath &dir : dirs) @@ -601,6 +617,38 @@ bool FilePath::writeFileContents(const QByteArray &data, qint64 offset) const return res == data.size(); } +FilePathInfo FilePath::filePathInfo() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.filePathInfo, return {}); + return s_deviceHooks.filePathInfo(*this); + } + + FilePathInfo result; + + QFileInfo fi(path()); + result.fileSize = fi.size(); + result.lastModified = fi.lastModified(); + result.fileFlags = (FilePathInfo::FileFlag) fi.permissions().toInt(); + + if (fi.isDir()) + result.fileFlags |= FilePathInfo::DirectoryType; + if (fi.isFile()) + result.fileFlags |= FilePathInfo::FileType; + if (fi.exists()) + result.fileFlags |= FilePathInfo::ExistsFlag; + if (fi.isSymbolicLink()) + result.fileFlags |= FilePathInfo::LinkType; + if (fi.isBundle()) + result.fileFlags |= FilePathInfo::BundleType; + if (fi.isHidden()) + result.fileFlags |= FilePathInfo::HiddenFlag; + if (fi.isRoot()) + result.fileFlags |= FilePathInfo::RootFlag; + + return result; +} + void FilePath::asyncWriteFileContents(const Continuation<bool> &cont, const QByteArray &data, qint64 offset) const @@ -1834,17 +1882,18 @@ FileFilter::FileFilter(const QStringList &nameFilters, { } -QStringList FileFilter::asFindArguments() const +QStringList FileFilter::asFindArguments(const QString &path) const { QStringList arguments; const QDir::Filters filters = fileFilters; - if (filters & QDir::NoSymLinks) - arguments.prepend("-H"); + + if (iteratorFlags.testFlag(QDirIterator::FollowSymlinks)) + arguments << "-L"; else - arguments.prepend("-L"); + arguments << "-H"; - arguments.append({"-mindepth", "1"}); + arguments << path; if (!iteratorFlags.testFlag(QDirIterator::Subdirectories)) arguments.append({"-maxdepth", "1"}); @@ -1854,14 +1903,23 @@ QStringList FileFilter::asFindArguments() const if (!(filters & QDir::Hidden)) filterOptions << "!" << "-name" << ".*"; + QStringList typesToList; + QStringList filterFilesAndDirs; - if (filters & QDir::Dirs) + if (filters.testFlag(QDir::Dirs)) filterFilesAndDirs << "-type" << "d"; - if (filters & QDir::Files) { + if (filters.testFlag(QDir::Files)) { if (!filterFilesAndDirs.isEmpty()) filterFilesAndDirs << "-o"; filterFilesAndDirs << "-type" << "f"; } + + if (!filters.testFlag(QDir::NoSymLinks)) { + if (!filterFilesAndDirs.isEmpty()) + filterFilesAndDirs << "-o"; + filterFilesAndDirs << "-type" << "l"; + } + if (!filterFilesAndDirs.isEmpty()) filterOptions << "(" << filterFilesAndDirs << ")"; diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index d4b1f714b24..f03d89d4d8b 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -5,6 +5,7 @@ #include "utils_global.h" +#include "filepathinfo.h" #include "osspecificaspects.h" #include <QDir> @@ -36,7 +37,7 @@ public: const QDir::Filters fileFilters = QDir::NoFilter, const QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags); - QStringList asFindArguments() const; + QStringList asFindArguments(const QString &path) const; const QStringList nameFilters; const QDir::Filters fileFilters = QDir::NoFilter; @@ -119,6 +120,7 @@ public: FilePaths dirEntries(QDir::Filters filters) const; std::optional<QByteArray> fileContents(qint64 maxSize = -1, qint64 offset = 0) const; bool writeFileContents(const QByteArray &data, qint64 offset = 0) const; + FilePathInfo filePathInfo() const; bool operator==(const FilePath &other) const; bool operator!=(const FilePath &other) const; @@ -151,10 +153,17 @@ public: [[nodiscard]] Environment deviceEnvironment() const; [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; - void iterateDirectory(const std::function<bool(const FilePath &item)> &callBack, + + using IterateDirCallback = std::function<bool(const FilePath &item)>; + using IterateDirWithInfoCallback + = std::function<bool(const FilePath &item, const FilePathInfo &info)>; + + void iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const; + void iterateDirectory(const IterateDirWithInfoCallback &callBack, const FileFilter &filter) const; + static void iterateDirectories(const FilePaths &dirs, - const std::function<bool(const FilePath &item)> &callBack, + const IterateDirCallback &callBack, const FileFilter &filter); enum PathAmending { AppendToPath, PrependToPath }; @@ -258,8 +267,13 @@ public: std::function<FilePath(const FilePath &)> symLinkTarget; std::function<QString(const FilePath &)> mapToDevicePath; std::function<void(const FilePath &, - const std::function<bool(const FilePath &)> &, // Abort on 'false' return. - const FileFilter &)> iterateDirectory; + const FilePath::IterateDirCallback &, // Abort on 'false' return. + const FileFilter &)> + iterateDirectory; + std::function<void(const FilePath &, + const FilePath::IterateDirWithInfoCallback &, // Abort on 'false' return. + const FileFilter &)> + iterateDirectoryWithInfo; std::function<std::optional<QByteArray>(const FilePath &, qint64, qint64)> fileContents; std::function<bool(const FilePath &, const QByteArray &, qint64)> writeFileContents; std::function<QDateTime(const FilePath &)> lastModified; @@ -271,6 +285,7 @@ public: std::function<qint64(const FilePath &)> bytesAvailable; std::function<QString(const FilePath &)> deviceDisplayName; std::function<bool(const FilePath &, const FilePath &)> isSameDevice; + std::function<FilePathInfo(const FilePath &)> filePathInfo; template <class ...Args> using Continuation = std::function<void(Args...)>; diff --git a/src/libs/utils/filepathinfo.h b/src/libs/utils/filepathinfo.h new file mode 100644 index 00000000000..7f05e678343 --- /dev/null +++ b/src/libs/utils/filepathinfo.h @@ -0,0 +1,56 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <QDateTime> + +namespace Utils { + +struct FilePathInfo +{ + // Copy of QAbstractFileEngine::FileFlags so we don't need to include private headers. + enum FileFlag { + //perms (overlaps the QFile::Permission) + ReadOwnerPerm = 0x4000, // 0x100 + WriteOwnerPerm = 0x2000, // 0x80 + ExeOwnerPerm = 0x1000, // 0x40 + ReadUserPerm = 0x0400, + WriteUserPerm = 0x0200, + ExeUserPerm = 0x0100, + ReadGroupPerm = 0x0040, // 0x20 + WriteGroupPerm = 0x0020, // 0x10 + ExeGroupPerm = 0x0010, // 0x8 + ReadOtherPerm = 0x0004, // 0x4 + WriteOtherPerm = 0x0002, // 0x2 + ExeOtherPerm = 0x0001, // 0x1 + + //types + LinkType = 0x10000, // 0xa000 + FileType = 0x20000, // 0x8000 + DirectoryType = 0x40000, // 0x4000 + BundleType = 0x80000, + + //flags + HiddenFlag = 0x0100000, + LocalDiskFlag = 0x0200000, // 0x6000 + ExistsFlag = 0x0400000, + RootFlag = 0x0800000, + Refresh = 0x1000000, + + //masks + PermsMask = 0x0000FFFF, + TypesMask = 0x000F0000, + FlagsMask = 0x0FF00000, + FileInfoAll = FlagsMask | PermsMask | TypesMask + }; + + Q_DECLARE_FLAGS(FileFlags, FileFlag) + + + qint64 fileSize = 0; + FileFlags fileFlags; + QDateTime lastModified; +}; + +} // namespace Utils diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index b4b952166fd..cb893ff4a46 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -620,34 +620,139 @@ void FileUtils::iterateLsOutput(const FilePath &base, } } -// returns whether 'find' could be used. -static bool iterateWithFind(const FilePath &filePath, - const FileFilter &filter, - const std::function<RunResult(const CommandLine &)> &runInShell, - const std::function<bool(const FilePath &)> &callBack) +FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString) +{ + bool ok = false; + uint mode = hexString.toUInt(&ok, 16); + + QTC_ASSERT(ok, return {}); + + FilePathInfo::FileFlags result; + + if (mode & 0x100) // S_IRUSR + result |= FilePathInfo::ReadOwnerPerm; + if (mode & 0x80) // S_IWUSR + result |= FilePathInfo::WriteOwnerPerm; + if (mode & 0x40) // S_IXUSR + result |= FilePathInfo::ExeOwnerPerm; + if (mode & 0x20) // S_IRGRP + result |= FilePathInfo::ReadGroupPerm; + if (mode & 0x10) // S_IWGRP + result |= FilePathInfo::WriteGroupPerm; + if (mode & 0x8) // S_IXGRP + result |= FilePathInfo::ExeGroupPerm; + if (mode & 0x4) // S_IROTH + result |= FilePathInfo::ReadOtherPerm; + if (mode & 0x2) // S_IWOTH + result |= FilePathInfo::WriteOtherPerm; + if (mode & 0x1) // S_IXOTH + result |= FilePathInfo::ExeOtherPerm; + if (mode & 0xa000) // S_IFLNK + result |= FilePathInfo::LinkType; + if (mode & 0x8000) // S_IFREG + result |= FilePathInfo::FileType; + if (mode & 0x4000) // S_IFDIR + result |= FilePathInfo::DirectoryType; + if (mode & 0x6000) // S_IFBLK + result |= FilePathInfo::LocalDiskFlag; + + if (result != 0) // There is no Exist flag, but if anything was set before, it must exist. + result |= FilePathInfo::ExistsFlag; + + return result; +} + +FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) +{ + const QStringList parts = infos.split(' ', Qt::SkipEmptyParts); + if (parts.size() != 3) + return {}; + + FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]); + + const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC); + qint64 size = parts[2].toLongLong(); + return {size, flags, dt}; +} + +bool iterateWithFind(const FilePath &filePath, + const FileFilter &filter, + const std::function<RunResult(const CommandLine &)> &runInShell, + const std::function<bool(const QString &)> callBack, + const QString &extraArguments) { QTC_CHECK(filePath.isAbsolutePath()); - QStringList arguments{filePath.path()}; - arguments << filter.asFindArguments(); + const QStringList arguments = filter.asFindArguments(filePath.path()); - const RunResult result = runInShell({"find", arguments}); - if (!result.stdErr.isEmpty()) { - // missing find, unknown option e.g. "find: unknown predicate `-L'\n" - // qDebug() << "find error: " << result.stdErr; - return false; - } + CommandLine cmdLine{"find", arguments}; + if (!extraArguments.isEmpty()) + cmdLine.addArgs(extraArguments, CommandLine::Raw); + const RunResult result = runInShell(cmdLine); const QString out = QString::fromUtf8(result.stdOut); - const QStringList entries = out.split("\n", Qt::SkipEmptyParts); + if (result.exitCode != 0) { + // Find returns non-zero exit code for any error it encounters, even if it finds some files. + + if (!out.startsWith('"' + filePath.path())) { + if (!filePath.exists()) // File does not exist, so no files to find. + return true; + + // If the output does not start with the path we are searching in, find has failed. + // Possibly due to unknown options. + return false; + } + } + + QStringList entries = out.split("\n", Qt::SkipEmptyParts); + // Remove the first line, it is always the directory we are searching in. + // as long as we do not specify "mindepth > 0" + entries.pop_front(); for (const QString &entry : entries) { - const FilePath fp = FilePath::fromString(entry); - // Call back returning 'false' indicates a request to abort iteration. - if (!callBack(fp.onDevice(filePath))) + if (!callBack(entry)) break; } + return true; } +// returns whether 'find' could be used. +static bool iterateWithFind(const FilePath &filePath, + const FileFilter &filter, + const std::function<RunResult(const CommandLine &)> &runInShell, + const FilePath::IterateDirCallback &callBack) +{ + const auto toFilePath = [&filePath, &callBack](const QString &entry){ + return callBack(filePath.withNewPath(entry)); + }; + + return iterateWithFind(filePath, filter, runInShell, toFilePath, {}); +} + +// returns whether 'find' could be used. +static bool iterateWithFind(const FilePath &filePath, + const FileFilter &filter, + const std::function<RunResult(const CommandLine &)> &runInShell, + const FilePath::IterateDirWithInfoCallback &callBack) +{ + // TODO: Using stat -L will always return the link target, not the link itself. + // We may wan't to add the information that it is a link at some point. + const QString infoArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)"); + + const auto toFilePathAndInfo = [&filePath, &callBack](const QString &entry) { + const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); + const QString infos = entry.mid(fileName.length() + 3); + + const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); + if (!fi.fileFlags) + return true; + + const FilePath fp = filePath.withNewPath(fileName); + return callBack(fp, fi); + }; + + return iterateWithFind(filePath, filter, runInShell, toFilePathAndInfo, infoArgs); +} + static void findUsingLs(const QString ¤t, const FileFilter &filter, const std::function<RunResult(const CommandLine &)> &runInShell, @@ -670,7 +775,7 @@ void FileUtils::iterateUnixDirectory(const FilePath &filePath, const FileFilter &filter, bool *useFind, const std::function<RunResult (const CommandLine &)> &runInShell, - const std::function<bool(const FilePath &)> &callBack) + const FilePath::IterateDirCallback &callBack) { QTC_ASSERT(callBack, return); @@ -688,6 +793,30 @@ void FileUtils::iterateUnixDirectory(const FilePath &filePath, FileUtils::iterateLsOutput(filePath, entries, filter, callBack); } +void FileUtils::iterateUnixDirectory(const FilePath &filePath, + const FileFilter &filter, + bool *useFind, + const std::function<RunResult(const CommandLine &)> &runInShell, + const FilePath::IterateDirWithInfoCallback &callBack) +{ + QTC_ASSERT(callBack, return); + + // We try to use 'find' first, because that can filter better directly. + // Unfortunately, it's not installed on all devices by default. + if (useFind && *useFind) { + if (iterateWithFind(filePath, filter, runInShell, callBack)) + return; + *useFind = false; // remember the failure for the next time and use the 'ls' fallback below. + } + + // if we do not have find - use ls as fallback + QStringList entries; + findUsingLs(filePath.path(), filter, runInShell, &entries); + FileUtils::iterateLsOutput(filePath, entries, filter, [&callBack](const FilePath & filePath){ + return callBack(filePath, filePath.filePathInfo()); + }); +} + /*! Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain the target directory, which will be created. Example usage: diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 5a1c5e1f075..fe3a388bb0b 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -35,7 +35,7 @@ class CommandLine; struct QTCREATOR_UTILS_EXPORT RunResult { - int exitCode = 0; + int exitCode = -1; QByteArray stdOut; QByteArray stdErr; }; @@ -97,10 +97,19 @@ public: const FileFilter &filter, bool *useFind, const std::function<RunResult(const CommandLine &)> &runInShell, - const std::function<bool(const FilePath &)> &callBack); + const FilePath::IterateDirCallback &callBack); + + static void iterateUnixDirectory( + const FilePath &base, + const FileFilter &filter, + bool *useFind, + const std::function<RunResult(const CommandLine &)> &runInShell, + const FilePath::IterateDirWithInfoCallback &callBack); static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput); + static FilePathInfo filePathInfoFromTriple(const QString &infos); + #ifdef QT_WIDGETS_LIB static void setDialogParentGetter(const std::function<QWidget *()> &getter); diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp index b397cc9447b..12ca91e2a2b 100644 --- a/src/libs/utils/fsengine/fileiconprovider.cpp +++ b/src/libs/utils/fsengine/fileiconprovider.cpp @@ -121,27 +121,22 @@ QIcon FileIconProviderImplementation::icon(IconType type) const return QFileIconProvider::icon(type); } -QIcon FileIconProviderImplementation::icon(const QFileInfo &fi) const -{ - return icon(FilePath::fromString(fi.filePath())); -} - QString FileIconProviderImplementation::type(const QFileInfo &fi) const { const FilePath fPath = FilePath::fromString(fi.filePath()); if (fPath.needsDevice()) { - if (fPath.isDir()) { + if (fi.isDir()) { #ifdef Q_OS_WIN return QGuiApplication::translate("QAbstractFileIconProvider", "File Folder", "Match Windows Explorer"); #else return QGuiApplication::translate("QAbstractFileIconProvider", "Folder", "All other platforms"); #endif } - if (fPath.isExecutableFile()) { + if (fi.isExecutable()) { return "Program"; } - return QFileIconProvider::type(fi); + return "File"; } return QFileIconProvider::type(fi); } @@ -169,6 +164,55 @@ static const QIcon &dirIcon() return icon; } +QIcon FileIconProviderImplementation::icon(const QFileInfo &fi) const +{ + qCDebug(fileIconProvider) << "FileIconProvider::icon" << fi.absoluteFilePath(); + + const FilePath filePath = FilePath::fromString(fi.filePath()); + + if (filePath.isEmpty()) + return unknownFileIcon(); + + // Check if its one of the virtual devices directories + if (filePath.path().startsWith( + FilePath::specialPath(FilePath::SpecialPathComponent::RootPath))) { + // If the filepath does not need a device, it is a virtual device directory + if (!filePath.needsDevice()) + return dirIcon(); + } + + bool isDir = fi.isDir(); + + // Check for cached overlay icons by file suffix. + const QString filename = !isDir ? fi.fileName() : QString(); + if (!filename.isEmpty()) { + const std::optional<QIcon> icon = getIcon(m_filenameCache, filename); + if (icon) + return *icon; + } + + const QString suffix = !isDir ? fi.suffix() : QString(); + if (!suffix.isEmpty()) { + const std::optional<QIcon> icon = getIcon(m_suffixCache, suffix); + if (icon) + return *icon; + } + + if (filePath.needsDevice()) + return isDir ? dirIcon() : unknownFileIcon(); + + // Get icon from OS (and cache it based on suffix!) + QIcon icon; + if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost()) + icon = QFileIconProvider::icon(filePath.toFileInfo()); + else // File icons are unknown on linux systems. + icon = isDir ? QFileIconProvider::icon(filePath.toFileInfo()) : unknownFileIcon(); + + if (!isDir && !suffix.isEmpty()) + m_suffixCache.insert(suffix, icon); + return icon; +} + QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const { qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath(); diff --git a/src/libs/utils/fsengine/filepathinfocache.h b/src/libs/utils/fsengine/filepathinfocache.h new file mode 100644 index 00000000000..e0b7094cbfe --- /dev/null +++ b/src/libs/utils/fsengine/filepathinfocache.h @@ -0,0 +1,68 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../filepath.h" + +#include <QCache> +#include <QMutex> +#include <QMutexLocker> + +namespace Utils::Internal { + +class FilePathInfoCache +{ +public: + struct CachedData + { + FilePathInfo filePathInfo; + QDateTime timeout; + }; + + using RetrievalFunction = CachedData (*)(const FilePath &); + +public: + FilePathInfoCache() + : m_cache(50000) + {} + + CachedData cached(const FilePath &filePath, const RetrievalFunction &retrievalFunction) + { + QMutexLocker lk(&m_mutex); + CachedData *data = m_cache.object(filePath); + + // If the cache entry is too old, don't use it ... + if (data && data->timeout < QDateTime::currentDateTime()) + data = nullptr; + + // If no data was found, retrieve it and store it in the cache ... + if (!data) { + data = new CachedData; + *data = retrievalFunction(filePath); + m_cache.insert(filePath, data); + } + + // Return a copy of the data, so it cannot be deleted by the cache + return *data; + } + + void cache(const FilePath &path, CachedData *data) + { + QMutexLocker lk(&m_mutex); + m_cache.insert(path, data); + } + + void cache(const QList<QPair<FilePath, CachedData>> &fileDataList) + { + QMutexLocker lk(&m_mutex); + for (const auto &[path, data] : fileDataList) + m_cache.insert(path, new CachedData(data)); + } + +private: + QMutex m_mutex; + QCache<FilePath, CachedData> m_cache; +}; + +} // namespace Utils::Internal diff --git a/src/libs/utils/fsengine/fsengine_impl.cpp b/src/libs/utils/fsengine/fsengine_impl.cpp index 02ffe3a58f4..f8277195b28 100644 --- a/src/libs/utils/fsengine/fsengine_impl.cpp +++ b/src/libs/utils/fsengine/fsengine_impl.cpp @@ -4,6 +4,7 @@ #include "fsengine_impl.h" #include "diriterator.h" +#include "filepathinfocache.h" #include "../filepath.h" #include "../qtcassert.h" @@ -15,6 +16,15 @@ namespace Utils { namespace Internal { +FilePathInfoCache g_filePathInfoCache; + +FilePathInfoCache::CachedData createCacheData(const FilePath &filePath) { + FilePathInfoCache::CachedData data; + data.filePathInfo = filePath.filePathInfo(); + data.timeout = QDateTime::currentDateTime().addSecs(60); + return data; +}; + FSEngineImpl::FSEngineImpl(FilePath filePath) : m_filePath(std::move(filePath)) {} @@ -30,6 +40,10 @@ bool FSEngineImpl::open(QIODeviceBase::OpenMode openMode, std::optional<QFile::P bool FSEngineImpl::open(QIODevice::OpenMode openMode) #endif { + const FilePathInfoCache::CachedData data = g_filePathInfoCache.cached(m_filePath, + createCacheData); + bool exists = (data.filePathInfo.fileFlags & QAbstractFileEngine::ExistsFlag); + ensureStorage(); QTC_ASSERT(m_tempStorage->open(), return false); @@ -38,10 +52,10 @@ bool FSEngineImpl::open(QIODevice::OpenMode openMode) bool write = openMode & QIODevice::WriteOnly; bool append = openMode & QIODevice::Append; - if (!write && !m_filePath.exists()) + if (!write && !exists) return false; - if (openMode & QIODevice::NewOnly && m_filePath.exists()) + if (openMode & QIODevice::NewOnly && exists) return false; if (read || append) { @@ -88,7 +102,7 @@ bool FSEngineImpl::syncToDisk() qint64 FSEngineImpl::size() const { - return m_filePath.fileSize(); + return g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.fileSize; } qint64 FSEngineImpl::pos() const @@ -175,8 +189,12 @@ QStringList FSEngineImpl::entryList(QDir::Filters filters, const QStringList &fi { QStringList result; m_filePath.iterateDirectory( - [&result](const FilePath &p) { + [&result](const FilePath &p, const FilePathInfo &fi) { result.append(p.toFSPathString()); + g_filePathInfoCache + .cache(p, + new FilePathInfoCache::CachedData{fi, + QDateTime::currentDateTime().addSecs(60)}); return true; }, {filterNames, filters}); @@ -185,22 +203,8 @@ QStringList FSEngineImpl::entryList(QDir::Filters filters, const QStringList &fi QAbstractFileEngine::FileFlags FSEngineImpl::fileFlags(FileFlags type) const { - FileFlags result{0}; - - if (type & FileInfoAll && m_filePath.exists()) { - result |= QAbstractFileEngine::ExistsFlag; - - if (type & DirectoryType && m_filePath.isDir()) - result |= QAbstractFileEngine::DirectoryType; - if (type & FileType && m_filePath.isFile()) - result |= QAbstractFileEngine::FileType; - - if (type & PermsMask) { - result |= FileFlags::fromInt(m_filePath.permissions().toInt()); - } - } - - return result; + Q_UNUSED(type); + return {g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.fileFlags.toInt()}; } bool FSEngineImpl::setPermissions(uint /*perms*/) @@ -265,7 +269,7 @@ bool FSEngineImpl::setFileTime(const QDateTime &newDate, FileTime time) QDateTime FSEngineImpl::fileTime(FileTime time) const { Q_UNUSED(time) - return m_filePath.lastModified(); + return g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.lastModified; } void FSEngineImpl::setFileName(const QString &file) @@ -289,8 +293,12 @@ QAbstractFileEngine::Iterator *FSEngineImpl::beginEntryList(QDir::Filters filter { FilePaths paths{m_filePath.pathAppended(".")}; m_filePath.iterateDirectory( - [&paths](const FilePath &p) { + [&paths](const FilePath &p, const FilePathInfo &fi) { paths.append(p); + FilePathInfoCache::CachedData *data + = new FilePathInfoCache::CachedData{fi, + QDateTime::currentDateTime().addSecs(60)}; + g_filePathInfoCache.cache(p, data); return true; }, {filterNames, filters}); diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 38a30aa76f3..401e98d59c4 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -67,12 +67,11 @@ #include <QThread> #include <QToolButton> - #include <numeric> #ifdef Q_OS_UNIX -#include <unistd.h> #include <sys/types.h> +#include <unistd.h> #endif using namespace Core; @@ -92,13 +91,13 @@ public: : m_settings(settings) , m_containerId(containerId) , m_devicePath(devicePath) - { - } + {} private: void setupShellProcess(QtcProcess *shellProcess) final { - shellProcess->setCommand({m_settings->dockerBinaryPath.filePath(), {"container", "start", "-i", "-a", m_containerId}}); + shellProcess->setCommand({m_settings->dockerBinaryPath.filePath(), + {"container", "start", "-i", "-a", m_containerId}}); } CommandLine createFallbackCommand(const CommandLine &cmdLine) @@ -162,7 +161,8 @@ public: DockerDeviceData m_data; DockerSettings *m_settings; - struct TemporaryMountInfo { + struct TemporaryMountInfo + { FilePath path; FilePath containerPath; }; @@ -327,11 +327,9 @@ Tasks DockerDevice::validate() const return result; } - DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data) : d(new DockerDevicePrivate(this, settings, data)) { - setDisplayType(Tr::tr("Docker")); setOsType(OsTypeOtherUnix); setDefaultDisplayName(Tr::tr("Docker Image")); @@ -421,7 +419,11 @@ CommandLine DockerDevicePrivate::withDockerExecCmd(const CommandLine &cmd, bool void DockerDevicePrivate::stopCurrentContainer() { - if (!m_settings || m_container.isEmpty() || !DockerApi::isDockerDaemonAvailable(false).value_or(false)) + if (!m_settings) + return; + if (m_container.isEmpty()) + return; + if (!DockerApi::isDockerDaemonAvailable(false).value_or(false)) return; m_shell.reset(); @@ -589,8 +591,7 @@ void DockerDevice::fromMap(const QVariantMap &map) data.tag = map.value(DockerDeviceDataTagKey).toString(); data.imageId = map.value(DockerDeviceDataImageIdKey).toString(); data.size = map.value(DockerDeviceDataSizeKey).toString(); - data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, HostOsInfo::isLinuxHost()) - .toBool(); + data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, HostOsInfo::isLinuxHost()).toBool(); data.mounts = map.value(DockerDeviceMappedPaths).toStringList(); data.keepEntryPoint = map.value(DockerDeviceKeepEntryPoint).toBool(); d->setData(data); @@ -623,26 +624,24 @@ bool DockerDevice::canAutoDetectPorts() const PortsGatheringMethod DockerDevice::portsGatheringMethod() const { - return { - [this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { - // We might encounter the situation that protocol is given IPv6 - // but the consumer of the free port information decides to open - // an IPv4(only) port. As a result the next IPv6 scan will - // report the port again as open (in IPv6 namespace), while the - // same port in IPv4 namespace might still be blocked, and - // re-use of this port fails. - // GDBserver behaves exactly like this. - - Q_UNUSED(protocol) - - // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 - return {filePath("sed"), - "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*", - CommandLine::Raw}; - }, - - &Port::parseFromSedOutput - }; + return {[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { + // We might encounter the situation that protocol is given IPv6 + // but the consumer of the free port information decides to open + // an IPv4(only) port. As a result the next IPv6 scan will + // report the port again as open (in IPv6 namespace), while the + // same port in IPv4 namespace might still be blocked, and + // re-use of this port fails. + // GDBserver behaves exactly like this. + + Q_UNUSED(protocol) + + // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 + return {filePath("sed"), + "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*", + CommandLine::Raw}; + }, + + &Port::parseFromSedOutput}; }; DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const @@ -714,12 +713,12 @@ bool DockerDevice::handlesFile(const FilePath &filePath) const if (filePath.scheme() == u"device" && filePath.host() == id().toString()) return true; - if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME - && filePath.host() == d->dockerImageId()) + const bool isDockerScheme = filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME; + + if (isDockerScheme && filePath.host() == d->dockerImageId()) return true; - if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME - && filePath.host() == QString(d->repoAndTag())) + if (isDockerScheme && filePath.host() == QString(d->repoAndTag())) return true; return false; @@ -875,7 +874,8 @@ QFileDevice::Permissions DockerDevice::permissions(const FilePath &filePath) con return perm; } -bool DockerDevice::setPermissions(const FilePath &filePath, QFileDevice::Permissions permissions) const +bool DockerDevice::setPermissions(const FilePath &filePath, + QFileDevice::Permissions permissions) const { Q_UNUSED(permissions) QTC_ASSERT(handlesFile(filePath), return {}); @@ -894,7 +894,16 @@ bool DockerDevice::ensureReachable(const FilePath &other) const } void DockerDevice::iterateDirectory(const FilePath &filePath, - const std::function<bool(const FilePath &)> &callBack, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const +{ + QTC_ASSERT(handlesFile(filePath), return); + auto runInShell = [this](const CommandLine &cmd) { return d->runInShell(cmd); }; + FileUtils::iterateUnixDirectory(filePath, filter, &d->m_useFind, runInShell, callBack); +} + +void DockerDevice::iterateDirectory(const FilePath &filePath, + const FilePath::IterateDirWithInfoCallback &callBack, const FileFilter &filter) const { QTC_ASSERT(handlesFile(filePath), return); @@ -923,6 +932,13 @@ bool DockerDevice::writeFileContents(const FilePath &filePath, return d->runInShellSuccess(cmd, data); } +FilePathInfo DockerDevice::filePathInfo(const FilePath &filePath) const +{ + QTC_ASSERT(handlesFile(filePath), return {}); + const RunResult stat = d->runInShell({"stat", {"-L", "-c", "%f %Y %s", filePath.path()}}); + return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); +} + Environment DockerDevice::systemEnvironment() const { return d->environment(); @@ -973,8 +989,7 @@ void DockerDevicePrivate::fetchSystemEnviroment() proc.waitForFinished(); const QString remoteOutput = proc.cleanedStdOut(); - m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), - q->osType()); + m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType()); const QString remoteError = proc.cleanedStdErr(); if (!remoteError.isEmpty()) @@ -1078,6 +1093,7 @@ public: m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); using namespace Layouting; + Column { Stack { statusLabel, @@ -1085,17 +1101,16 @@ public: }, m_log, errorLabel, - Row { - showUnnamedContainers, - m_buttons - }, - }.attachTo(this); + Row{showUnnamedContainers, m_buttons}, + } + .attachTo(this); connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false); - CommandLine cmd{m_settings->dockerBinaryPath.filePath(), {"images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}"}}; + CommandLine cmd{m_settings->dockerBinaryPath.filePath(), + {"images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}"}}; m_log->append(Tr::tr("Running \"%1\"\n").arg(cmd.toUserOutput())); m_process = new QtcProcess(this); @@ -1146,7 +1161,8 @@ public: { const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows(); QTC_ASSERT(selectedRows.size() == 1, return {}); - DockerImageItem *item = m_model.itemForIndex(m_proxyModel->mapToSource(selectedRows.front())); + DockerImageItem *item = m_model.itemForIndex( + m_proxyModel->mapToSource(selectedRows.front())); QTC_ASSERT(item, return {}); auto device = DockerDevice::create(m_settings, *item); @@ -1201,10 +1217,9 @@ void DockerDeviceFactory::shutdownExistingDevices() bool DockerDevicePrivate::addTemporaryMount(const FilePath &path, const FilePath &containerPath) { - bool alreadyAdded = anyOf(m_temporaryMounts, - [containerPath](const TemporaryMountInfo &info) { - return info.containerPath == containerPath; - }); + bool alreadyAdded = anyOf(m_temporaryMounts, [containerPath](const TemporaryMountInfo &info) { + return info.containerPath == containerPath; + }); if (alreadyAdded) return false; diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 4e7840b9282..8398ee4e235 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -100,7 +100,10 @@ public: bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; void iterateDirectory(const Utils::FilePath &filePath, - const std::function<bool(const Utils::FilePath &)> &callBack, + const Utils::FilePath::IterateDirCallback &callBack, + const Utils::FileFilter &filter) const override; + void iterateDirectory(const Utils::FilePath &filePath, + const Utils::FilePath::IterateDirWithInfoCallback &callBack, const Utils::FileFilter &filter) const override; std::optional<QByteArray> fileContents(const Utils::FilePath &filePath, qint64 limit, @@ -108,6 +111,7 @@ public: bool writeFileContents(const Utils::FilePath &filePath, const QByteArray &data, qint64 offset) const override; + Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const override; QDateTime lastModified(const Utils::FilePath &filePath) const override; qint64 fileSize(const Utils::FilePath &filePath) const override; QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override; @@ -132,10 +136,6 @@ protected: QVariantMap toMap() const final; private: - void iterateWithFind(const Utils::FilePath &filePath, - const std::function<bool(const Utils::FilePath &)> &callBack, - const Utils::FileFilter &filter) const; - void aboutToBeRemoved() const final; class DockerDevicePrivate *d = nullptr; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index febcc6436eb..c5a7f4e8102 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -521,10 +521,18 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager }; deviceHooks.iterateDirectory = [](const FilePath &filePath, - const std::function<bool(const FilePath &)> &callBack, + const FilePath::IterateDirCallback &callBack, const FileFilter &filter) { auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return); + QTC_ASSERT(device, return ); + device->iterateDirectory(filePath, callBack, filter); + }; + + deviceHooks.iterateDirectoryWithInfo = [](const FilePath &filePath, + const FilePath::IterateDirWithInfoCallback &callBack, + const FileFilter &filter) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return ); device->iterateDirectory(filePath, callBack, filter); }; @@ -552,6 +560,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager return device->writeFileContents(filePath, data, offset); }; + deviceHooks.filePathInfo = [](const FilePath &filePath) -> FilePathInfo { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return {}); + return device->filePathInfo(filePath); + }; + deviceHooks.lastModified = [](const FilePath &filePath) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return QDateTime()); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 9c391de5aed..294be9f18a0 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -356,7 +356,7 @@ FilePath IDevice::symLinkTarget(const FilePath &filePath) const } void IDevice::iterateDirectory(const FilePath &filePath, - const std::function<bool(const FilePath &)> &callBack, + const FilePath::IterateDirCallback &callBack, const FileFilter &filter) const { Q_UNUSED(filePath); @@ -365,6 +365,15 @@ void IDevice::iterateDirectory(const FilePath &filePath, QTC_CHECK(false); } +void IDevice::iterateDirectory(const FilePath &filePath, + const FilePath::IterateDirWithInfoCallback &callBack, + const FileFilter &filter) const +{ + iterateDirectory(filePath, [callBack](const FilePath &path) { + return callBack(path, path.filePathInfo()); + }, filter); +} + std::optional<QByteArray> IDevice::fileContents(const FilePath &filePath, qint64 limit, qint64 offset) const @@ -393,6 +402,28 @@ bool IDevice::writeFileContents(const FilePath &filePath, const QByteArray &data return {}; } +FilePathInfo IDevice::filePathInfo(const Utils::FilePath &filePath) const +{ + bool exists = filePath.exists(); + if (!exists) + return {}; + + FilePathInfo result { + filePath.fileSize(), + {FilePathInfo::ExistsFlag}, + filePath.lastModified(), + }; + + if (filePath.isDir()) + result.fileFlags |= FilePathInfo::DirectoryType; + if (filePath.isFile()) + result.fileFlags |= FilePathInfo::FileType; + if (filePath.isRootPath()) + result.fileFlags |= FilePathInfo::RootFlag; + + return result; +} + void IDevice::asyncWriteFileContents(const Continuation<bool> &cont, const FilePath &filePath, const QByteArray &data, diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 58d0892fc65..57f2ba0a3c7 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -238,14 +238,20 @@ public: const Utils::FilePaths &dirs) const; virtual Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const; virtual void iterateDirectory(const Utils::FilePath &filePath, - const std::function<bool(const Utils::FilePath &)> &callBack, + const Utils::FilePath::IterateDirCallback &callBack, const Utils::FileFilter &filter) const; + + virtual void iterateDirectory(const Utils::FilePath &filePath, + const Utils::FilePath::IterateDirWithInfoCallback &callBack, + const Utils::FileFilter &filter) const; + virtual std::optional<QByteArray> fileContents(const Utils::FilePath &filePath, qint64 limit, qint64 offset) const; virtual bool writeFileContents(const Utils::FilePath &filePath, const QByteArray &data, qint64 offset) const; + virtual Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const; virtual QDateTime lastModified(const Utils::FilePath &filePath) const; virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const; virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const; |