forked from qt-creator/qt-creator
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:
@@ -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)
|
||||||
|
@@ -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 << ")";
|
||||||
|
|
||||||
|
@@ -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...)>;
|
||||||
|
56
src/libs/utils/filepathinfo.h
Normal file
56
src/libs/utils/filepathinfo.h
Normal 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
|
@@ -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.
|
||||||
|
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;
|
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,
|
static void findUsingLs(const QString ¤t,
|
||||||
@@ -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:
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
|
68
src/libs/utils/fsengine/filepathinfocache.h
Normal file
68
src/libs/utils/fsengine/filepathinfocache.h
Normal 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
|
@@ -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});
|
||||||
|
@@ -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,8 +624,7 @@ 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
|
||||||
@@ -641,8 +641,7 @@ PortsGatheringMethod DockerDevice::portsGatheringMethod() const
|
|||||||
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,8 +1217,7 @@ 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)
|
||||||
|
@@ -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;
|
||||||
|
@@ -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());
|
||||||
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user