a-la-karte/src/runnermanagerclient.cpp

846 lines
27 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
#include "runnermanagerclient.h"
#include "runner1interface.h"
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusMetaType>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusReply>
#include <QDBusVariant>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QSharedPointer>
#include <QTimer>
#include <QUrl>
#include <QUuid>
#include <functional>
static const QString kRunnerService = QStringLiteral("org.kde.ALaKarte.Runner1");
static const QString kRunnerPath = QStringLiteral("/org/kde/ALaKarte/Runner1");
static const QString kRunnerInterface = QStringLiteral("org.kde.ALaKarte.Runner1");
namespace
{
static QVariant unwrapDbusVariant(QVariant v)
{
if (v.canConvert<QDBusVariant>()) {
v = v.value<QDBusVariant>().variant();
}
return v;
}
static QVariantMap unwrapVariantMap(QVariant v)
{
v = unwrapDbusVariant(v);
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
const QDBusArgument arg = v.value<QDBusArgument>();
return qdbus_cast<QVariantMap>(arg);
}
if (v.canConvert<QVariantMap>()) {
return v.toMap();
}
return {};
}
static QVariantMap normalizeMap(QVariantMap m)
{
for (auto it = m.begin(); it != m.end(); ++it) {
it.value() = unwrapDbusVariant(it.value());
if (it.key() == QLatin1String("envOverrides")) {
it.value() = unwrapVariantMap(it.value());
}
}
return m;
}
static QVariantMap normalizeResultWithProfile(QVariantMap result)
{
result = normalizeMap(result);
if (result.contains(QStringLiteral("profile"))) {
QVariantMap profile = unwrapVariantMap(result.value(QStringLiteral("profile")));
profile = normalizeMap(profile);
result.insert(QStringLiteral("profile"), profile);
}
return result;
}
}
static QString stableIdForUrl(const QString &type, const QString &url)
{
const QByteArray digest = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Sha1).toHex();
return type + QStringLiteral(":url:") + QString::fromLatin1(digest);
}
static QString stableIdKeyForInputUrl(QString urlString)
{
urlString = urlString.trimmed();
if (urlString.startsWith(QLatin1String("~/"))) {
urlString = QDir::homePath() + QLatin1Char('/') + urlString.mid(2);
}
QUrl url(urlString);
if (url.isValid() && url.scheme().toLower() == QLatin1String("file")) {
QFileInfo fi(url.toLocalFile());
const QString canonical = fi.canonicalFilePath();
const QString abs = fi.absoluteFilePath();
const QString path = canonical.isEmpty() ? abs : canonical;
return QUrl::fromLocalFile(path).toString(QUrl::FullyEncoded);
}
if (url.scheme().isEmpty()) {
QFileInfo fi(urlString);
if (fi.isAbsolute() || urlString.startsWith(QLatin1String("./")) || urlString.startsWith(QLatin1String("../"))) {
const QString canonical = fi.exists() ? fi.canonicalFilePath() : QString();
const QString abs = fi.absoluteFilePath();
const QString path = canonical.isEmpty() ? abs : canonical;
return QUrl::fromLocalFile(path).toString(QUrl::FullyEncoded);
}
}
if (url.isValid() && !url.scheme().isEmpty()) {
return url.toString(QUrl::FullyEncoded);
}
return urlString;
}
RunnerManagerClient::RunnerManagerClient(QObject *parent)
: QObject(parent)
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (bus.isConnected()) {
bus.connect(kRunnerService, kRunnerPath, kRunnerInterface, QStringLiteral("InstallStarted"), this, SLOT(onInstallStarted(QString, QVariantMap)));
bus.connect(kRunnerService,
kRunnerPath,
kRunnerInterface,
QStringLiteral("InstallProgress"),
this,
SLOT(onInstallProgress(QString, qlonglong, qlonglong)));
bus.connect(kRunnerService, kRunnerPath, kRunnerInterface, QStringLiteral("InstallFinished"), this, SLOT(onInstallFinished(QString, QVariantMap)));
bus.connect(kRunnerService, kRunnerPath, kRunnerInterface, QStringLiteral("GameProfilesChanged"), this, SLOT(onGameProfilesChanged()));
}
if (QCoreApplication::instance()) {
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
shutdownSpawnedRunnerDaemon();
});
}
refreshRunners();
refreshGameProfiles();
}
RunnerManagerClient::~RunnerManagerClient()
{
shutdownSpawnedRunnerDaemon();
}
void RunnerManagerClient::shutdownSpawnedRunnerDaemon()
{
if (!m_runnerdProcess) {
return;
}
QProcess *p = m_runnerdProcess;
const auto state = p->state();
if (state == QProcess::NotRunning) {
return;
}
// Avoid our finished() handler nulling the pointer while we're shutting down.
p->disconnect(this);
p->terminate();
if (!p->waitForFinished(3000)) {
p->kill();
p->waitForFinished(3000);
}
p->deleteLater();
m_runnerdProcess = nullptr;
}
void RunnerManagerClient::ensureRunnerDaemon()
{
QDBusConnection bus = QDBusConnection::sessionBus();
if (!bus.isConnected() || !bus.interface()) {
return;
}
if (bus.interface()->isServiceRegistered(kRunnerService)) {
return;
}
bool activatable = false;
{
const QDBusReply<QStringList> names = bus.interface()->activatableServiceNames();
if (names.isValid()) {
activatable = names.value().contains(kRunnerService);
}
}
const QDBusReply<void> reply = bus.interface()->startService(kRunnerService);
if (reply.isValid()) {
return;
}
if (bus.interface()->isServiceRegistered(kRunnerService)) {
return;
}
if (activatable) {
setLastError(reply.error().message());
return;
}
#if defined(QT_NO_DEBUG)
setLastError(QStringLiteral("Runner service is not available (missing DBus activation for org.kde.ALaKarte.Runner1)"));
return;
#endif
if (m_runnerdSpawnAttempted) {
return;
}
m_runnerdSpawnAttempted = true;
QString program = QCoreApplication::applicationDirPath() + QLatin1String("/alakarte-runnerd");
if (!QFileInfo::exists(program)) {
program = QStringLiteral("alakarte-runnerd");
}
if (!m_runnerdProcess) {
m_runnerdProcess = new QProcess(this);
m_runnerdProcess->setProcessChannelMode(QProcess::MergedChannels);
connect(m_runnerdProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) {
setLastError(QStringLiteral("Failed to start runner service"));
});
connect(m_runnerdProcess, &QProcess::finished, this, [this](int, QProcess::ExitStatus) {
m_runnerdProcess->deleteLater();
m_runnerdProcess = nullptr;
});
}
m_runnerdProcess->setProgram(program);
m_runnerdProcess->setArguments({});
m_runnerdProcess->start();
}
bool RunnerManagerClient::busy() const
{
return m_busy;
}
QString RunnerManagerClient::installId() const
{
return m_installId;
}
qint64 RunnerManagerClient::receivedBytes() const
{
return m_receivedBytes;
}
qint64 RunnerManagerClient::totalBytes() const
{
return m_totalBytes;
}
QString RunnerManagerClient::status() const
{
return m_status;
}
QString RunnerManagerClient::lastError() const
{
return m_lastError;
}
QVariantList RunnerManagerClient::runners() const
{
return m_runners;
}
QVariantList RunnerManagerClient::gameProfiles() const
{
return m_gameProfiles;
}
static QString normalizeHex(QString s)
{
s = s.trimmed().toLower();
const int colon = s.indexOf(QLatin1Char(':'));
if (colon >= 0) {
const QString prefix = s.left(colon);
if (prefix == QLatin1String("sha256") || prefix == QLatin1String("sha512")) {
s = s.mid(colon + 1);
}
}
QString out;
out.reserve(s.size());
for (const QChar &c : s) {
if ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) || (c >= QLatin1Char('a') && c <= QLatin1Char('f'))) {
out.push_back(c);
}
}
return out;
}
void RunnerManagerClient::installRunnerFromUrl(const QString &url, const QString &sha256, const QString &name, const QString &type, bool overwrite)
{
const QString trimmedUrl = url.trimmed();
if (trimmedUrl.isEmpty() || m_busy) {
return;
}
const QString effectiveType = type.trimmed().isEmpty() ? QStringLiteral("proton") : type.trimmed();
setLastError(QString());
setProgress(0, 0);
setStatus(QStringLiteral("Starting..."));
const QString newInstallId = QUuid::createUuid().toString(QUuid::WithoutBraces);
setInstallId(newInstallId);
setBusy(true);
QVariantMap spec;
spec.insert(QStringLiteral("installId"), newInstallId);
spec.insert(QStringLiteral("url"), trimmedUrl);
spec.insert(QStringLiteral("type"), effectiveType);
spec.insert(QStringLiteral("name"), name.trimmed());
spec.insert(QStringLiteral("overwrite"), overwrite);
const QString expected = normalizeHex(sha256);
if (!expected.isEmpty()) {
if (expected.size() == 128) {
spec.insert(QStringLiteral("sha512"), expected);
} else {
spec.insert(QStringLiteral("sha256"), expected);
}
}
const QString stableId = stableIdForUrl(effectiveType, stableIdKeyForInputUrl(trimmedUrl));
spec.insert(QStringLiteral("id"), stableId);
ensureRunnerDaemon();
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, spec, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.InstallRunnerFromUrl(spec);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, spec, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
setStatus(QStringLiteral("Failed to call InstallRunnerFromUrl"));
setLastError(reply.error().message());
setBusy(false);
return;
}
QVariantMap result = reply.value();
const bool ok = result.value(QStringLiteral("ok")).toBool();
if (!ok) {
const QString err = result.value(QStringLiteral("error")).toString();
setLastError(err);
setStatus(err.isEmpty() ? QStringLiteral("Failed") : err);
setBusy(false);
return;
}
setStatus(QStringLiteral("Started"));
});
};
(*callPtr)();
}
void RunnerManagerClient::deletePrefix(const QString &gameId, const QString &prefixPath)
{
const QString id = gameId.trimmed();
const QString p = prefixPath.trimmed();
if (id.isEmpty()) {
return;
}
ensureRunnerDaemon();
QVariantMap spec;
spec.insert(QStringLiteral("gameId"), id);
if (!p.isEmpty()) {
spec.insert(QStringLiteral("prefixPath"), p);
}
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, spec, id, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.DeletePrefix(spec);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
setLastError(reply.error().message());
return;
}
const QVariantMap result = reply.value();
if (!result.value(QStringLiteral("ok")).toBool()) {
setLastError(result.value(QStringLiteral("error")).toString());
return;
}
const QString removed = result.value(QStringLiteral("prefixPath")).toString();
Q_EMIT prefixDeleted(id, removed);
});
};
(*callPtr)();
}
void RunnerManagerClient::cancelCurrentInstall()
{
if (!m_busy || m_installId.isEmpty()) {
return;
}
setStatus(QStringLiteral("Cancelling..."));
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
iface.CancelInstall(m_installId);
}
void RunnerManagerClient::onInstallStarted(const QString &installId, const QVariantMap &)
{
if (installId.isEmpty() || installId != m_installId) {
return;
}
setStatus(QStringLiteral("Downloading..."));
}
void RunnerManagerClient::onInstallProgress(const QString &installId, qlonglong received, qlonglong total)
{
if (installId.isEmpty() || installId != m_installId) {
return;
}
setProgress(static_cast<qint64>(received), static_cast<qint64>(total));
}
void RunnerManagerClient::onInstallFinished(const QString &installId, const QVariantMap &result)
{
if (installId.isEmpty() || installId != m_installId) {
return;
}
const bool ok = result.value(QStringLiteral("ok")).toBool();
if (!ok) {
const QString err = result.value(QStringLiteral("error")).toString();
setLastError(err);
setStatus(err.isEmpty() ? QStringLiteral("Failed") : err);
} else {
setStatus(QStringLiteral("Installed"));
}
setBusy(false);
refreshRunners();
}
void RunnerManagerClient::onGameProfilesChanged()
{
refreshGameProfiles();
}
void RunnerManagerClient::refreshRunners()
{
ensureRunnerDaemon();
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.ListRunners();
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantList> reply = *w;
w->deleteLater();
if (reply.isError()) {
if (!m_refreshRetryPending && (reply.error().type() == QDBusError::ServiceUnknown || reply.error().type() == QDBusError::NoReply)) {
m_refreshRetryPending = true;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [this]() {
m_refreshRetryPending = false;
refreshRunners();
});
}
return;
}
const QVariantList raw = reply.value();
QVariantList list;
list.reserve(raw.size());
for (const QVariant &v : raw) {
if (v.metaType() == QMetaType::fromType<QVariantMap>()) {
list.push_back(v.toMap());
continue;
}
if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
const QDBusArgument arg = v.value<QDBusArgument>();
const QVariantMap map = qdbus_cast<QVariantMap>(arg);
list.push_back(map);
continue;
}
if (v.canConvert<QVariantMap>()) {
list.push_back(v.toMap());
continue;
}
list.push_back(QVariantMap{});
}
if (m_runners == list) {
return;
}
m_runners = list;
Q_EMIT runnersChanged();
});
}
void RunnerManagerClient::refreshGameProfiles()
{
ensureRunnerDaemon();
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.ListGameProfiles();
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantList> reply = *w;
w->deleteLater();
if (reply.isError()) {
if (!m_refreshProfilesRetryPending && (reply.error().type() == QDBusError::ServiceUnknown || reply.error().type() == QDBusError::NoReply)) {
m_refreshProfilesRetryPending = true;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [this]() {
m_refreshProfilesRetryPending = false;
refreshGameProfiles();
});
}
return;
}
const QVariantList raw = reply.value();
QVariantList list;
list.reserve(raw.size());
for (const QVariant &v : raw) {
QVariantMap map;
if (v.metaType() == QMetaType::fromType<QVariantMap>()) {
map = v.toMap();
} else if (v.metaType() == QMetaType::fromType<QDBusArgument>()) {
const QDBusArgument arg = v.value<QDBusArgument>();
map = qdbus_cast<QVariantMap>(arg);
} else if (v.canConvert<QVariantMap>()) {
map = v.toMap();
}
list.push_back(normalizeMap(map));
}
if (m_gameProfiles == list) {
return;
}
m_gameProfiles = list;
Q_EMIT gameProfilesChanged();
});
}
void RunnerManagerClient::requestGameProfile(const QString &gameId)
{
const QString id = gameId.trimmed();
if (id.isEmpty()) {
return;
}
ensureRunnerDaemon();
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, id, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.GetGameProfile(id);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
QVariantMap err;
err.insert(QStringLiteral("ok"), false);
err.insert(QStringLiteral("error"), reply.error().message());
Q_EMIT gameProfileFetched(id, err);
return;
}
QVariantMap result = normalizeResultWithProfile(reply.value());
Q_EMIT gameProfileFetched(id, result);
});
};
(*callPtr)();
}
void RunnerManagerClient::setGameProfile(const QVariantMap &spec)
{
const QString id = spec.value(QStringLiteral("gameId")).toString().trimmed();
if (id.isEmpty()) {
return;
}
ensureRunnerDaemon();
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, spec, id, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.SetGameProfile(spec);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
QVariantMap err;
err.insert(QStringLiteral("ok"), false);
err.insert(QStringLiteral("error"), reply.error().message());
Q_EMIT gameProfileSaved(id, err);
return;
}
QVariantMap result = normalizeResultWithProfile(reply.value());
Q_EMIT gameProfileSaved(id, result);
});
};
(*callPtr)();
}
void RunnerManagerClient::clearGameProfile(const QString &gameId)
{
const QString id = gameId.trimmed();
if (id.isEmpty()) {
return;
}
ensureRunnerDaemon();
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, id, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.ClearGameProfile(id);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
QVariantMap err;
err.insert(QStringLiteral("ok"), false);
err.insert(QStringLiteral("error"), reply.error().message());
Q_EMIT gameProfileCleared(id, err);
return;
}
QVariantMap result = normalizeMap(reply.value());
Q_EMIT gameProfileCleared(id, result);
});
};
(*callPtr)();
}
void RunnerManagerClient::uninstallRunner(const QString &runnerId)
{
const QString id = runnerId.trimmed();
if (id.isEmpty()) {
return;
}
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.UninstallRunner(id);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
setLastError(reply.error().message());
return;
}
const QVariantMap result = reply.value();
if (!result.value(QStringLiteral("ok")).toBool()) {
setLastError(result.value(QStringLiteral("error")).toString());
}
refreshRunners();
});
}
void RunnerManagerClient::ensurePrefix(const QString &gameId, const QString &runner, const QString &prefixPath)
{
const QString id = gameId.trimmed();
const QString r = runner.trimmed();
const QString p = prefixPath.trimmed();
if (id.isEmpty()) {
return;
}
ensureRunnerDaemon();
QVariantMap spec;
spec.insert(QStringLiteral("gameId"), id);
if (!r.isEmpty()) {
spec.insert(QStringLiteral("runner"), r);
}
if (!p.isEmpty()) {
spec.insert(QStringLiteral("prefixPath"), p);
}
auto attempts = QSharedPointer<int>::create(0);
auto callPtr = QSharedPointer<std::function<void()>>::create();
*callPtr = [this, spec, id, attempts, callPtr]() {
org::kde::ALaKarte::Runner1 iface(kRunnerService, kRunnerPath, QDBusConnection::sessionBus());
QDBusPendingCall call = iface.EnsurePrefix(spec);
auto *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, id, attempts, callPtr](QDBusPendingCallWatcher *w) {
QDBusPendingReply<QVariantMap> reply = *w;
w->deleteLater();
if (reply.isError()) {
const QDBusError::ErrorType t = reply.error().type();
if ((*attempts) < 1 && (t == QDBusError::ServiceUnknown || t == QDBusError::NoReply)) {
(*attempts)++;
ensureRunnerDaemon();
QTimer::singleShot(500, this, [callPtr]() {
(*callPtr)();
});
return;
}
setLastError(reply.error().message());
return;
}
const QVariantMap result = reply.value();
if (!result.value(QStringLiteral("ok")).toBool()) {
setLastError(result.value(QStringLiteral("error")).toString());
return;
}
const QString ensured = result.value(QStringLiteral("prefixPath")).toString();
if (!ensured.isEmpty()) {
Q_EMIT prefixEnsured(id, ensured);
}
});
};
(*callPtr)();
}
void RunnerManagerClient::setBusy(bool busy)
{
if (m_busy == busy) {
return;
}
m_busy = busy;
Q_EMIT busyChanged();
}
void RunnerManagerClient::setInstallId(const QString &installId)
{
if (m_installId == installId) {
return;
}
m_installId = installId;
Q_EMIT installIdChanged();
}
void RunnerManagerClient::setProgress(qint64 received, qint64 total)
{
if (m_receivedBytes == received && m_totalBytes == total) {
return;
}
m_receivedBytes = received;
m_totalBytes = total;
Q_EMIT progressChanged();
}
void RunnerManagerClient::setStatus(const QString &status)
{
if (m_status == status) {
return;
}
m_status = status;
Q_EMIT statusChanged();
}
void RunnerManagerClient::setLastError(const QString &error)
{
if (m_lastError == error) {
return;
}
m_lastError = error;
Q_EMIT lastErrorChanged();
}