Utils: Split off file access interface from IDevice

The file accessing functions form now a class hierarchy by themselves,
the devices return a suitable point.

The previous implementation was mildly confusing by the special handling
of the DesktopDevice, fallbacks and remote cases in the same function
leading to unnecessary boilerplate when adding new functions and
codepaths that sometimes passed the FilePath API twice.

Implemented are a "DesktopDeviceFileAccess" taking care of the
previous !needsDevice() branches and a "UnixDeviceFileAccess"
covering the current docker and RL uses.

As a side-effect this unifies to a large degree the current docker
and RL code paths with were occasionally deviating from each other
while they shouldn't.

Change-Id: I4ff59d4be2a07d13e2ca5e9ace26a84160a87c9d
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
hjk
2022-10-10 17:32:56 +02:00
parent e0832ce7fc
commit 1fa3255242
21 changed files with 1233 additions and 1737 deletions

View File

@@ -32,6 +32,7 @@
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
#include <utils/devicefileaccess.h>
#include <utils/deviceshell.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
@@ -113,6 +114,25 @@ private:
FilePath m_devicePath;
};
class DockerDeviceFileAccess : public UnixDeviceFileAccess
{
public:
DockerDeviceFileAccess(DockerDevicePrivate *dev)
: m_dev(dev)
{}
RunResult runInShell(const QString &executable,
const QStringList &arguments,
const QByteArray &stdInData) const override;
std::optional<QByteArray> fileContents(
const FilePath &filePath,
qint64 limit,
qint64 offset) const override;
DockerDevicePrivate *m_dev = nullptr;
};
class DockerDevicePrivate : public QObject
{
public:
@@ -129,8 +149,6 @@ public:
return runInShell(cmd, stdInData).exitCode == 0;
}
std::optional<QByteArray> fileContents(const FilePath &filePath, qint64 limit, qint64 offset);
void updateContainerAccess();
void changeMounts(QStringList newMounts);
bool ensureReachable(const FilePath &other);
@@ -177,8 +195,8 @@ public:
QString m_container;
Environment m_cachedEnviroment;
bool m_useFind = true; // prefer find over ls and hacks, but be able to use ls as fallback
bool m_isShutdown = false;
DockerDeviceFileAccess m_fileAccess{this};
};
class DockerProcessImpl : public Utils::ProcessInterface
@@ -337,9 +355,28 @@ Tasks DockerDevicePrivate::validateMounts() const
return result;
}
RunResult DockerDeviceFileAccess::runInShell(
const QString &executable,
const QStringList &arguments,
const QByteArray &stdInData) const
{
QTC_ASSERT(m_dev, return {});
return m_dev->runInShell({FilePath::fromString(executable), arguments}, stdInData);
}
std::optional<QByteArray> DockerDeviceFileAccess::fileContents(
const FilePath &filePath,
qint64 limit,
qint64 offset) const
{
m_dev->updateContainerAccess();
return UnixDeviceFileAccess::fileContents(filePath, limit, offset);
}
DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data)
: d(new DockerDevicePrivate(this, settings, data))
{
setFileAccess(&d->m_fileAccess);
setDisplayType(Tr::tr("Docker"));
setOsType(OsTypeOtherUnix);
setDefaultDisplayName(Tr::tr("Docker Image"));
@@ -778,165 +815,6 @@ bool DockerDevice::handlesFile(const FilePath &filePath) const
return false;
}
bool DockerDevice::isExecutableFile(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-x", path}});
}
bool DockerDevice::isReadableFile(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-r", path, "-a", "-f", path}});
}
bool DockerDevice::isWritableFile(const Utils::FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-w", path, "-a", "-f", path}});
}
bool DockerDevice::isReadableDirectory(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-r", path, "-a", "-d", path}});
}
bool DockerDevice::isWritableDirectory(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-w", path, "-a", "-d", path}});
}
bool DockerDevice::isFile(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-f", path}});
}
bool DockerDevice::isDirectory(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-d", path}});
}
bool DockerDevice::createDirectory(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"mkdir", {"-p", path}});
}
bool DockerDevice::exists(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"test", {"-e", path}});
}
bool DockerDevice::ensureExistingFile(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
const QString path = filePath.path();
return d->runInShellSuccess({"touch", {path}});
}
bool DockerDevice::removeFile(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
return d->runInShellSuccess({"rm", {filePath.path()}});
}
bool DockerDevice::removeRecursively(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return false);
QTC_ASSERT(filePath.path().startsWith('/'), return false);
const QString path = filePath.cleanPath().path();
// We are expecting this only to be called in a context of build directories or similar.
// Chicken out in some cases that _might_ be user code errors.
QTC_ASSERT(path.startsWith('/'), return false);
const int levelsNeeded = path.startsWith("/home/") ? 4 : 3;
QTC_ASSERT(path.count('/') >= levelsNeeded, return false);
return d->runInShellSuccess({"rm", {"-rf", "--", path}});
}
bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) const
{
QTC_ASSERT(handlesFile(filePath), return false);
QTC_ASSERT(handlesFile(target), return false);
return d->runInShellSuccess({"cp", {filePath.path(), target.path()}});
}
bool DockerDevice::renameFile(const FilePath &filePath, const FilePath &target) const
{
QTC_ASSERT(handlesFile(filePath), return false);
QTC_ASSERT(handlesFile(target), return false);
return d->runInShellSuccess({"mv", {filePath.path(), target.path()}});
}
QDateTime DockerDevice::lastModified(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return {});
const RunResult result = d->runInShell({"stat", {"-L", "-c", "%Y", filePath.path()}});
qint64 secs = result.stdOut.toLongLong();
const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC);
return dt;
}
FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return {});
const RunResult result = d->runInShell({"readlink", {"-n", "-e", filePath.path()}});
const QString out = QString::fromUtf8(result.stdOut);
return out.isEmpty() ? FilePath() : filePath.withNewPath(out);
}
qint64 DockerDevice::fileSize(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return -1);
const RunResult result = d->runInShell({"stat", {"-L", "-c", "%s", filePath.path()}});
return result.stdOut.toLongLong();
}
QFileDevice::Permissions DockerDevice::permissions(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return {});
const RunResult result = d->runInShell({"stat", {"-L", "-c", "%a", filePath.path()}});
const uint bits = result.stdOut.toUInt(nullptr, 8);
QFileDevice::Permissions perm = {};
#define BIT(n, p) if (bits & (1<<n)) perm |= QFileDevice::p
BIT(0, ExeOther);
BIT(1, WriteOther);
BIT(2, ReadOther);
BIT(3, ExeGroup);
BIT(4, WriteGroup);
BIT(5, ReadGroup);
BIT(6, ExeUser);
BIT(7, WriteUser);
BIT(8, ReadUser);
#undef BIT
return perm;
}
bool DockerDevice::setPermissions(const FilePath &filePath,
QFileDevice::Permissions permissions) const
{
Q_UNUSED(permissions)
QTC_ASSERT(handlesFile(filePath), return {});
QTC_CHECK(false); // FIXME: Implement.
return false;
}
bool DockerDevice::ensureReachable(const FilePath &other) const
{
if (other.needsDevice())
@@ -947,43 +825,6 @@ bool DockerDevice::ensureReachable(const FilePath &other) const
return d->ensureReachable(other.parentDir());
}
void DockerDevice::iterateDirectory(const FilePath &filePath,
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);
}
std::optional<QByteArray> DockerDevice::fileContents(const FilePath &filePath,
qint64 limit,
qint64 offset) const
{
QTC_ASSERT(handlesFile(filePath), return {});
return d->fileContents(filePath, limit, offset);
}
bool DockerDevice::writeFileContents(const FilePath &filePath,
const QByteArray &data,
qint64 offset) const
{
QTC_ASSERT(handlesFile(filePath), return {});
CommandLine cmd({"dd", {"of=" + filePath.path()}});
if (offset != 0) {
cmd.addArg("bs=1");
cmd.addArg(QString("seek=%1").arg(offset));
}
return d->runInShellSuccess(cmd, data);
}
FilePathInfo DockerDevice::filePathInfo(const FilePath &filePath) const
{
QTC_ASSERT(handlesFile(filePath), return {});
const RunResult stat = d->runInShell({"stat", {"-L", "-c", "%f %Y %s", filePath.path()}});
return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut));
}
Environment DockerDevice::systemEnvironment() const
{
return d->environment();
@@ -995,28 +836,6 @@ void DockerDevice::aboutToBeRemoved() const
detector.undoAutoDetect(id().toString());
}
std::optional<QByteArray> DockerDevicePrivate::fileContents(const FilePath &filePath,
qint64 limit,
qint64 offset)
{
updateContainerAccess();
QStringList args = {"if=" + filePath.path(), "status=none"};
if (limit > 0 || offset > 0) {
const qint64 gcd = std::gcd(limit, offset);
args += {QString("bs=%1").arg(gcd),
QString("count=%1").arg(limit / gcd),
QString("seek=%1").arg(offset / gcd)};
}
const RunResult r = m_shell->runInShell({"dd", args});
if (r.exitCode != 0)
return {};
return r.stdOut;
}
void DockerDevicePrivate::fetchSystemEnviroment()
{
updateContainerAccess();