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>
This commit is contained in:
Marcus Tillmanns
2022-10-05 15:42:14 +02:00
parent e5e90ad931
commit ae58d373b0
14 changed files with 579 additions and 124 deletions

View File

@@ -48,6 +48,7 @@ add_qtc_library(Utils
fileinprojectfinder.cpp fileinprojectfinder.h fileinprojectfinder.cpp fileinprojectfinder.h
filenamevalidatinglineedit.cpp filenamevalidatinglineedit.h filenamevalidatinglineedit.cpp filenamevalidatinglineedit.h
filepath.cpp filepath.h filepath.cpp filepath.h
filepathinfo.h
filesearch.cpp filesearch.h filesearch.cpp filesearch.h
filesystemmodel.cpp filesystemmodel.h filesystemmodel.cpp filesystemmodel.h
filesystemwatcher.cpp filesystemwatcher.h filesystemwatcher.cpp filesystemwatcher.h
@@ -264,6 +265,7 @@ extend_qtc_library(Utils
fsengine/fixedlistfsengine.h fsengine/fixedlistfsengine.h
fsengine/fsenginehandler.cpp fsengine/fsenginehandler.cpp
fsengine/fsenginehandler.h fsengine/fsenginehandler.h
fsengine/filepathinfocache.h
) )
if (WIN32) if (WIN32)

View File

@@ -513,8 +513,7 @@ FilePaths FilePath::dirEntries(QDir::Filters filters) const
// either of the specified \a nameFilters. // either of the specified \a nameFilters.
// An empty \nameFilters list matches every name. // An empty \nameFilters list matches every name.
void FilePath::iterateDirectory(const std::function<bool(const FilePath &item)> &callBack, void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const
const FileFilter &filter) const
{ {
if (needsDevice()) { if (needsDevice()) {
QTC_ASSERT(s_deviceHooks.iterateDirectory, return); 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, void FilePath::iterateDirectories(const FilePaths &dirs,
const std::function<bool(const FilePath &)> &callBack, const IterateDirCallback &callBack,
const FileFilter &filter) const FileFilter &filter)
{ {
for (const FilePath &dir : dirs) for (const FilePath &dir : dirs)
@@ -601,6 +617,38 @@ bool FilePath::writeFileContents(const QByteArray &data, qint64 offset) const
return res == data.size(); 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, void FilePath::asyncWriteFileContents(const Continuation<bool> &cont,
const QByteArray &data, const QByteArray &data,
qint64 offset) const 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; QStringList arguments;
const QDir::Filters filters = fileFilters; const QDir::Filters filters = fileFilters;
if (filters & QDir::NoSymLinks)
arguments.prepend("-H");
else
arguments.prepend("-L");
arguments.append({"-mindepth", "1"}); if (iteratorFlags.testFlag(QDirIterator::FollowSymlinks))
arguments << "-L";
else
arguments << "-H";
arguments << path;
if (!iteratorFlags.testFlag(QDirIterator::Subdirectories)) if (!iteratorFlags.testFlag(QDirIterator::Subdirectories))
arguments.append({"-maxdepth", "1"}); arguments.append({"-maxdepth", "1"});
@@ -1854,14 +1903,23 @@ QStringList FileFilter::asFindArguments() const
if (!(filters & QDir::Hidden)) if (!(filters & QDir::Hidden))
filterOptions << "!" << "-name" << ".*"; filterOptions << "!" << "-name" << ".*";
QStringList typesToList;
QStringList filterFilesAndDirs; QStringList filterFilesAndDirs;
if (filters & QDir::Dirs) if (filters.testFlag(QDir::Dirs))
filterFilesAndDirs << "-type" << "d"; filterFilesAndDirs << "-type" << "d";
if (filters & QDir::Files) { if (filters.testFlag(QDir::Files)) {
if (!filterFilesAndDirs.isEmpty()) if (!filterFilesAndDirs.isEmpty())
filterFilesAndDirs << "-o"; filterFilesAndDirs << "-o";
filterFilesAndDirs << "-type" << "f"; filterFilesAndDirs << "-type" << "f";
} }
if (!filters.testFlag(QDir::NoSymLinks)) {
if (!filterFilesAndDirs.isEmpty())
filterFilesAndDirs << "-o";
filterFilesAndDirs << "-type" << "l";
}
if (!filterFilesAndDirs.isEmpty()) if (!filterFilesAndDirs.isEmpty())
filterOptions << "(" << filterFilesAndDirs << ")"; filterOptions << "(" << filterFilesAndDirs << ")";

View File

@@ -5,6 +5,7 @@
#include "utils_global.h" #include "utils_global.h"
#include "filepathinfo.h"
#include "osspecificaspects.h" #include "osspecificaspects.h"
#include <QDir> #include <QDir>
@@ -36,7 +37,7 @@ public:
const QDir::Filters fileFilters = QDir::NoFilter, const QDir::Filters fileFilters = QDir::NoFilter,
const QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags); const QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags);
QStringList asFindArguments() const; QStringList asFindArguments(const QString &path) const;
const QStringList nameFilters; const QStringList nameFilters;
const QDir::Filters fileFilters = QDir::NoFilter; const QDir::Filters fileFilters = QDir::NoFilter;
@@ -119,6 +120,7 @@ public:
FilePaths dirEntries(QDir::Filters filters) const; FilePaths dirEntries(QDir::Filters filters) const;
std::optional<QByteArray> fileContents(qint64 maxSize = -1, qint64 offset = 0) const; std::optional<QByteArray> fileContents(qint64 maxSize = -1, qint64 offset = 0) const;
bool writeFileContents(const QByteArray &data, 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;
bool operator!=(const FilePath &other) const; bool operator!=(const FilePath &other) const;
@@ -151,10 +153,17 @@ public:
[[nodiscard]] Environment deviceEnvironment() const; [[nodiscard]] Environment deviceEnvironment() const;
[[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const;
[[nodiscard]] FilePath withNewPath(const QString &newPath) 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; const FileFilter &filter) const;
static void iterateDirectories(const FilePaths &dirs, static void iterateDirectories(const FilePaths &dirs,
const std::function<bool(const FilePath &item)> &callBack, const IterateDirCallback &callBack,
const FileFilter &filter); const FileFilter &filter);
enum PathAmending { AppendToPath, PrependToPath }; enum PathAmending { AppendToPath, PrependToPath };
@@ -258,8 +267,13 @@ public:
std::function<FilePath(const FilePath &)> symLinkTarget; std::function<FilePath(const FilePath &)> symLinkTarget;
std::function<QString(const FilePath &)> mapToDevicePath; std::function<QString(const FilePath &)> mapToDevicePath;
std::function<void(const FilePath &, std::function<void(const FilePath &,
const std::function<bool(const FilePath &)> &, // Abort on 'false' return. const FilePath::IterateDirCallback &, // Abort on 'false' return.
const FileFilter &)> iterateDirectory; 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<std::optional<QByteArray>(const FilePath &, qint64, qint64)> fileContents;
std::function<bool(const FilePath &, const QByteArray &, qint64)> writeFileContents; std::function<bool(const FilePath &, const QByteArray &, qint64)> writeFileContents;
std::function<QDateTime(const FilePath &)> lastModified; std::function<QDateTime(const FilePath &)> lastModified;
@@ -271,6 +285,7 @@ public:
std::function<qint64(const FilePath &)> bytesAvailable; std::function<qint64(const FilePath &)> bytesAvailable;
std::function<QString(const FilePath &)> deviceDisplayName; std::function<QString(const FilePath &)> deviceDisplayName;
std::function<bool(const FilePath &, const FilePath &)> isSameDevice; std::function<bool(const FilePath &, const FilePath &)> isSameDevice;
std::function<FilePathInfo(const FilePath &)> filePathInfo;
template <class ...Args> using Continuation = std::function<void(Args...)>; template <class ...Args> using Continuation = std::function<void(Args...)>;

View File

@@ -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

View File

@@ -620,32 +620,137 @@ void FileUtils::iterateLsOutput(const FilePath &base,
} }
} }
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());
const QStringList arguments = filter.asFindArguments(filePath.path());
CommandLine cmdLine{"find", arguments};
if (!extraArguments.isEmpty())
cmdLine.addArgs(extraArguments, CommandLine::Raw);
const RunResult result = runInShell(cmdLine);
const QString out = QString::fromUtf8(result.stdOut);
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) {
if (!callBack(entry))
break;
}
return true;
}
// returns whether 'find' could be used. // returns whether 'find' could be used.
static bool iterateWithFind(const FilePath &filePath, static bool iterateWithFind(const FilePath &filePath,
const FileFilter &filter, const FileFilter &filter,
const std::function<RunResult(const CommandLine &)> &runInShell, const std::function<RunResult(const CommandLine &)> &runInShell,
const std::function<bool(const FilePath &)> &callBack) const FilePath::IterateDirCallback &callBack)
{ {
QTC_CHECK(filePath.isAbsolutePath()); const auto toFilePath = [&filePath, &callBack](const QString &entry){
QStringList arguments{filePath.path()}; return callBack(filePath.withNewPath(entry));
arguments << filter.asFindArguments(); };
const RunResult result = runInShell({"find", arguments}); return iterateWithFind(filePath, filter, runInShell, toFilePath, {});
if (!result.stdErr.isEmpty()) { }
// missing find, unknown option e.g. "find: unknown predicate `-L'\n"
// qDebug() << "find error: " << result.stdErr;
return false;
}
const QString out = QString::fromUtf8(result.stdOut); // returns whether 'find' could be used.
const QStringList entries = out.split("\n", Qt::SkipEmptyParts); static bool iterateWithFind(const FilePath &filePath,
for (const QString &entry : entries) { const FileFilter &filter,
const FilePath fp = FilePath::fromString(entry); const std::function<RunResult(const CommandLine &)> &runInShell,
// Call back returning 'false' indicates a request to abort iteration. const FilePath::IterateDirWithInfoCallback &callBack)
if (!callBack(fp.onDevice(filePath))) {
break; // 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.
return true; 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 &current, static void findUsingLs(const QString &current,
@@ -670,7 +775,7 @@ void FileUtils::iterateUnixDirectory(const FilePath &filePath,
const FileFilter &filter, const FileFilter &filter,
bool *useFind, bool *useFind,
const std::function<RunResult (const CommandLine &)> &runInShell, const std::function<RunResult (const CommandLine &)> &runInShell,
const std::function<bool(const FilePath &)> &callBack) const FilePath::IterateDirCallback &callBack)
{ {
QTC_ASSERT(callBack, return); QTC_ASSERT(callBack, return);
@@ -688,6 +793,30 @@ void FileUtils::iterateUnixDirectory(const FilePath &filePath,
FileUtils::iterateLsOutput(filePath, entries, filter, callBack); 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 Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain
the target directory, which will be created. Example usage: the target directory, which will be created. Example usage:

View File

@@ -35,7 +35,7 @@ class CommandLine;
struct QTCREATOR_UTILS_EXPORT RunResult struct QTCREATOR_UTILS_EXPORT RunResult
{ {
int exitCode = 0; int exitCode = -1;
QByteArray stdOut; QByteArray stdOut;
QByteArray stdErr; QByteArray stdErr;
}; };
@@ -97,10 +97,19 @@ public:
const FileFilter &filter, const FileFilter &filter,
bool *useFind, bool *useFind,
const std::function<RunResult(const CommandLine &)> &runInShell, 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 qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput);
static FilePathInfo filePathInfoFromTriple(const QString &infos);
#ifdef QT_WIDGETS_LIB #ifdef QT_WIDGETS_LIB
static void setDialogParentGetter(const std::function<QWidget *()> &getter); static void setDialogParentGetter(const std::function<QWidget *()> &getter);

View File

@@ -121,27 +121,22 @@ QIcon FileIconProviderImplementation::icon(IconType type) const
return QFileIconProvider::icon(type); return QFileIconProvider::icon(type);
} }
QIcon FileIconProviderImplementation::icon(const QFileInfo &fi) const
{
return icon(FilePath::fromString(fi.filePath()));
}
QString FileIconProviderImplementation::type(const QFileInfo &fi) const QString FileIconProviderImplementation::type(const QFileInfo &fi) const
{ {
const FilePath fPath = FilePath::fromString(fi.filePath()); const FilePath fPath = FilePath::fromString(fi.filePath());
if (fPath.needsDevice()) { if (fPath.needsDevice()) {
if (fPath.isDir()) { if (fi.isDir()) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
return QGuiApplication::translate("QAbstractFileIconProvider", "File Folder", "Match Windows Explorer"); return QGuiApplication::translate("QAbstractFileIconProvider", "File Folder", "Match Windows Explorer");
#else #else
return QGuiApplication::translate("QAbstractFileIconProvider", "Folder", "All other platforms"); return QGuiApplication::translate("QAbstractFileIconProvider", "Folder", "All other platforms");
#endif #endif
} }
if (fPath.isExecutableFile()) { if (fi.isExecutable()) {
return "Program"; return "Program";
} }
return QFileIconProvider::type(fi); return "File";
} }
return QFileIconProvider::type(fi); return QFileIconProvider::type(fi);
} }
@@ -169,6 +164,55 @@ static const QIcon &dirIcon()
return icon; 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 QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const
{ {
qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath(); qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath();

View File

@@ -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

View File

@@ -4,6 +4,7 @@
#include "fsengine_impl.h" #include "fsengine_impl.h"
#include "diriterator.h" #include "diriterator.h"
#include "filepathinfocache.h"
#include "../filepath.h" #include "../filepath.h"
#include "../qtcassert.h" #include "../qtcassert.h"
@@ -15,6 +16,15 @@ namespace Utils {
namespace Internal { 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) FSEngineImpl::FSEngineImpl(FilePath filePath)
: m_filePath(std::move(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) bool FSEngineImpl::open(QIODevice::OpenMode openMode)
#endif #endif
{ {
const FilePathInfoCache::CachedData data = g_filePathInfoCache.cached(m_filePath,
createCacheData);
bool exists = (data.filePathInfo.fileFlags & QAbstractFileEngine::ExistsFlag);
ensureStorage(); ensureStorage();
QTC_ASSERT(m_tempStorage->open(), return false); QTC_ASSERT(m_tempStorage->open(), return false);
@@ -38,10 +52,10 @@ bool FSEngineImpl::open(QIODevice::OpenMode openMode)
bool write = openMode & QIODevice::WriteOnly; bool write = openMode & QIODevice::WriteOnly;
bool append = openMode & QIODevice::Append; bool append = openMode & QIODevice::Append;
if (!write && !m_filePath.exists()) if (!write && !exists)
return false; return false;
if (openMode & QIODevice::NewOnly && m_filePath.exists()) if (openMode & QIODevice::NewOnly && exists)
return false; return false;
if (read || append) { if (read || append) {
@@ -88,7 +102,7 @@ bool FSEngineImpl::syncToDisk()
qint64 FSEngineImpl::size() const qint64 FSEngineImpl::size() const
{ {
return m_filePath.fileSize(); return g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.fileSize;
} }
qint64 FSEngineImpl::pos() const qint64 FSEngineImpl::pos() const
@@ -175,8 +189,12 @@ QStringList FSEngineImpl::entryList(QDir::Filters filters, const QStringList &fi
{ {
QStringList result; QStringList result;
m_filePath.iterateDirectory( m_filePath.iterateDirectory(
[&result](const FilePath &p) { [&result](const FilePath &p, const FilePathInfo &fi) {
result.append(p.toFSPathString()); result.append(p.toFSPathString());
g_filePathInfoCache
.cache(p,
new FilePathInfoCache::CachedData{fi,
QDateTime::currentDateTime().addSecs(60)});
return true; return true;
}, },
{filterNames, filters}); {filterNames, filters});
@@ -185,22 +203,8 @@ QStringList FSEngineImpl::entryList(QDir::Filters filters, const QStringList &fi
QAbstractFileEngine::FileFlags FSEngineImpl::fileFlags(FileFlags type) const QAbstractFileEngine::FileFlags FSEngineImpl::fileFlags(FileFlags type) const
{ {
FileFlags result{0}; Q_UNUSED(type);
return {g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.fileFlags.toInt()};
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;
} }
bool FSEngineImpl::setPermissions(uint /*perms*/) bool FSEngineImpl::setPermissions(uint /*perms*/)
@@ -265,7 +269,7 @@ bool FSEngineImpl::setFileTime(const QDateTime &newDate, FileTime time)
QDateTime FSEngineImpl::fileTime(FileTime time) const QDateTime FSEngineImpl::fileTime(FileTime time) const
{ {
Q_UNUSED(time) Q_UNUSED(time)
return m_filePath.lastModified(); return g_filePathInfoCache.cached(m_filePath, createCacheData).filePathInfo.lastModified;
} }
void FSEngineImpl::setFileName(const QString &file) void FSEngineImpl::setFileName(const QString &file)
@@ -289,8 +293,12 @@ QAbstractFileEngine::Iterator *FSEngineImpl::beginEntryList(QDir::Filters filter
{ {
FilePaths paths{m_filePath.pathAppended(".")}; FilePaths paths{m_filePath.pathAppended(".")};
m_filePath.iterateDirectory( m_filePath.iterateDirectory(
[&paths](const FilePath &p) { [&paths](const FilePath &p, const FilePathInfo &fi) {
paths.append(p); paths.append(p);
FilePathInfoCache::CachedData *data
= new FilePathInfoCache::CachedData{fi,
QDateTime::currentDateTime().addSecs(60)};
g_filePathInfoCache.cache(p, data);
return true; return true;
}, },
{filterNames, filters}); {filterNames, filters});

View File

@@ -67,12 +67,11 @@
#include <QThread> #include <QThread>
#include <QToolButton> #include <QToolButton>
#include <numeric> #include <numeric>
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
#include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
#endif #endif
using namespace Core; using namespace Core;
@@ -92,13 +91,13 @@ public:
: m_settings(settings) : m_settings(settings)
, m_containerId(containerId) , m_containerId(containerId)
, m_devicePath(devicePath) , m_devicePath(devicePath)
{ {}
}
private: private:
void setupShellProcess(QtcProcess *shellProcess) final 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) CommandLine createFallbackCommand(const CommandLine &cmdLine)
@@ -162,7 +161,8 @@ public:
DockerDeviceData m_data; DockerDeviceData m_data;
DockerSettings *m_settings; DockerSettings *m_settings;
struct TemporaryMountInfo { struct TemporaryMountInfo
{
FilePath path; FilePath path;
FilePath containerPath; FilePath containerPath;
}; };
@@ -327,11 +327,9 @@ Tasks DockerDevice::validate() const
return result; return result;
} }
DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data) DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data)
: d(new DockerDevicePrivate(this, settings, data)) : d(new DockerDevicePrivate(this, settings, data))
{ {
setDisplayType(Tr::tr("Docker")); setDisplayType(Tr::tr("Docker"));
setOsType(OsTypeOtherUnix); setOsType(OsTypeOtherUnix);
setDefaultDisplayName(Tr::tr("Docker Image")); setDefaultDisplayName(Tr::tr("Docker Image"));
@@ -421,7 +419,11 @@ CommandLine DockerDevicePrivate::withDockerExecCmd(const CommandLine &cmd, bool
void DockerDevicePrivate::stopCurrentContainer() 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; return;
m_shell.reset(); m_shell.reset();
@@ -589,8 +591,7 @@ void DockerDevice::fromMap(const QVariantMap &map)
data.tag = map.value(DockerDeviceDataTagKey).toString(); data.tag = map.value(DockerDeviceDataTagKey).toString();
data.imageId = map.value(DockerDeviceDataImageIdKey).toString(); data.imageId = map.value(DockerDeviceDataImageIdKey).toString();
data.size = map.value(DockerDeviceDataSizeKey).toString(); data.size = map.value(DockerDeviceDataSizeKey).toString();
data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, HostOsInfo::isLinuxHost()) data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, HostOsInfo::isLinuxHost()).toBool();
.toBool();
data.mounts = map.value(DockerDeviceMappedPaths).toStringList(); data.mounts = map.value(DockerDeviceMappedPaths).toStringList();
data.keepEntryPoint = map.value(DockerDeviceKeepEntryPoint).toBool(); data.keepEntryPoint = map.value(DockerDeviceKeepEntryPoint).toBool();
d->setData(data); d->setData(data);
@@ -623,26 +624,24 @@ bool DockerDevice::canAutoDetectPorts() const
PortsGatheringMethod DockerDevice::portsGatheringMethod() const PortsGatheringMethod DockerDevice::portsGatheringMethod() const
{ {
return { return {[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine { // We might encounter the situation that protocol is given IPv6
// We might encounter the situation that protocol is given IPv6 // but the consumer of the free port information decides to open
// but the consumer of the free port information decides to open // an IPv4(only) port. As a result the next IPv6 scan will
// an IPv4(only) port. As a result the next IPv6 scan will // report the port again as open (in IPv6 namespace), while the
// report the port again as open (in IPv6 namespace), while the // same port in IPv4 namespace might still be blocked, and
// same port in IPv4 namespace might still be blocked, and // re-use of this port fails.
// re-use of this port fails. // GDBserver behaves exactly like this.
// GDBserver behaves exactly like this.
Q_UNUSED(protocol) Q_UNUSED(protocol)
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
return {filePath("sed"), return {filePath("sed"),
"-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*", "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
CommandLine::Raw}; CommandLine::Raw};
}, },
&Port::parseFromSedOutput &Port::parseFromSedOutput};
};
}; };
DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const 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()) if (filePath.scheme() == u"device" && filePath.host() == id().toString())
return true; return true;
if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME const bool isDockerScheme = filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME;
&& filePath.host() == d->dockerImageId())
if (isDockerScheme && filePath.host() == d->dockerImageId())
return true; return true;
if (filePath.scheme() == Constants::DOCKER_DEVICE_SCHEME if (isDockerScheme && filePath.host() == QString(d->repoAndTag()))
&& filePath.host() == QString(d->repoAndTag()))
return true; return true;
return false; return false;
@@ -875,7 +874,8 @@ QFileDevice::Permissions DockerDevice::permissions(const FilePath &filePath) con
return perm; 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) Q_UNUSED(permissions)
QTC_ASSERT(handlesFile(filePath), return {}); QTC_ASSERT(handlesFile(filePath), return {});
@@ -894,7 +894,16 @@ bool DockerDevice::ensureReachable(const FilePath &other) const
} }
void DockerDevice::iterateDirectory(const FilePath &filePath, 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 const FileFilter &filter) const
{ {
QTC_ASSERT(handlesFile(filePath), return); QTC_ASSERT(handlesFile(filePath), return);
@@ -923,6 +932,13 @@ bool DockerDevice::writeFileContents(const FilePath &filePath,
return d->runInShellSuccess(cmd, data); 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 Environment DockerDevice::systemEnvironment() const
{ {
return d->environment(); return d->environment();
@@ -973,8 +989,7 @@ void DockerDevicePrivate::fetchSystemEnviroment()
proc.waitForFinished(); proc.waitForFinished();
const QString remoteOutput = proc.cleanedStdOut(); const QString remoteOutput = proc.cleanedStdOut();
m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
q->osType());
const QString remoteError = proc.cleanedStdErr(); const QString remoteError = proc.cleanedStdErr();
if (!remoteError.isEmpty()) if (!remoteError.isEmpty())
@@ -1078,6 +1093,7 @@ public:
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
using namespace Layouting; using namespace Layouting;
Column { Column {
Stack { Stack {
statusLabel, statusLabel,
@@ -1085,17 +1101,16 @@ public:
}, },
m_log, m_log,
errorLabel, errorLabel,
Row { Row{showUnnamedContainers, m_buttons},
showUnnamedContainers, }
m_buttons .attachTo(this);
},
}.attachTo(this);
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(false); 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_log->append(Tr::tr("Running \"%1\"\n").arg(cmd.toUserOutput()));
m_process = new QtcProcess(this); m_process = new QtcProcess(this);
@@ -1146,7 +1161,8 @@ public:
{ {
const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows(); const QModelIndexList selectedRows = m_view->selectionModel()->selectedRows();
QTC_ASSERT(selectedRows.size() == 1, return {}); 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 {}); QTC_ASSERT(item, return {});
auto device = DockerDevice::create(m_settings, *item); auto device = DockerDevice::create(m_settings, *item);
@@ -1201,10 +1217,9 @@ void DockerDeviceFactory::shutdownExistingDevices()
bool DockerDevicePrivate::addTemporaryMount(const FilePath &path, const FilePath &containerPath) bool DockerDevicePrivate::addTemporaryMount(const FilePath &path, const FilePath &containerPath)
{ {
bool alreadyAdded = anyOf(m_temporaryMounts, bool alreadyAdded = anyOf(m_temporaryMounts, [containerPath](const TemporaryMountInfo &info) {
[containerPath](const TemporaryMountInfo &info) { return info.containerPath == containerPath;
return info.containerPath == containerPath; });
});
if (alreadyAdded) if (alreadyAdded)
return false; return false;

View File

@@ -100,7 +100,10 @@ public:
bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override;
Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override;
void iterateDirectory(const Utils::FilePath &filePath, 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; const Utils::FileFilter &filter) const override;
std::optional<QByteArray> fileContents(const Utils::FilePath &filePath, std::optional<QByteArray> fileContents(const Utils::FilePath &filePath,
qint64 limit, qint64 limit,
@@ -108,6 +111,7 @@ public:
bool writeFileContents(const Utils::FilePath &filePath, bool writeFileContents(const Utils::FilePath &filePath,
const QByteArray &data, const QByteArray &data,
qint64 offset) const override; qint64 offset) const override;
Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const override;
QDateTime lastModified(const Utils::FilePath &filePath) const override; QDateTime lastModified(const Utils::FilePath &filePath) const override;
qint64 fileSize(const Utils::FilePath &filePath) const override; qint64 fileSize(const Utils::FilePath &filePath) const override;
QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override; QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override;
@@ -132,10 +136,6 @@ protected:
QVariantMap toMap() const final; QVariantMap toMap() const final;
private: private:
void iterateWithFind(const Utils::FilePath &filePath,
const std::function<bool(const Utils::FilePath &)> &callBack,
const Utils::FileFilter &filter) const;
void aboutToBeRemoved() const final; void aboutToBeRemoved() const final;
class DockerDevicePrivate *d = nullptr; class DockerDevicePrivate *d = nullptr;

View File

@@ -521,10 +521,18 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
}; };
deviceHooks.iterateDirectory = [](const FilePath &filePath, deviceHooks.iterateDirectory = [](const FilePath &filePath,
const std::function<bool(const FilePath &)> &callBack, const FilePath::IterateDirCallback &callBack,
const FileFilter &filter) { const FileFilter &filter) {
auto device = DeviceManager::deviceForPath(filePath); 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); device->iterateDirectory(filePath, callBack, filter);
}; };
@@ -552,6 +560,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
return device->writeFileContents(filePath, data, offset); 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) { deviceHooks.lastModified = [](const FilePath &filePath) {
auto device = DeviceManager::deviceForPath(filePath); auto device = DeviceManager::deviceForPath(filePath);
QTC_ASSERT(device, return QDateTime()); QTC_ASSERT(device, return QDateTime());

View File

@@ -356,7 +356,7 @@ FilePath IDevice::symLinkTarget(const FilePath &filePath) const
} }
void IDevice::iterateDirectory(const FilePath &filePath, void IDevice::iterateDirectory(const FilePath &filePath,
const std::function<bool(const FilePath &)> &callBack, const FilePath::IterateDirCallback &callBack,
const FileFilter &filter) const const FileFilter &filter) const
{ {
Q_UNUSED(filePath); Q_UNUSED(filePath);
@@ -365,6 +365,15 @@ void IDevice::iterateDirectory(const FilePath &filePath,
QTC_CHECK(false); 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, std::optional<QByteArray> IDevice::fileContents(const FilePath &filePath,
qint64 limit, qint64 limit,
qint64 offset) const qint64 offset) const
@@ -393,6 +402,28 @@ bool IDevice::writeFileContents(const FilePath &filePath, const QByteArray &data
return {}; 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, void IDevice::asyncWriteFileContents(const Continuation<bool> &cont,
const FilePath &filePath, const FilePath &filePath,
const QByteArray &data, const QByteArray &data,

View File

@@ -238,14 +238,20 @@ public:
const Utils::FilePaths &dirs) const; const Utils::FilePaths &dirs) const;
virtual Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const; virtual Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const;
virtual void iterateDirectory(const Utils::FilePath &filePath, 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; 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, virtual std::optional<QByteArray> fileContents(const Utils::FilePath &filePath,
qint64 limit, qint64 limit,
qint64 offset) const; qint64 offset) const;
virtual bool writeFileContents(const Utils::FilePath &filePath, virtual bool writeFileContents(const Utils::FilePath &filePath,
const QByteArray &data, const QByteArray &data,
qint64 offset) const; qint64 offset) const;
virtual Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const;
virtual QDateTime lastModified(const Utils::FilePath &filePath) const; virtual QDateTime lastModified(const Utils::FilePath &filePath) const;
virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const; virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const;
virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const; virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const;