feat(client): improve SDK packaging and registry config
This commit is contained in:
+567
-97
@@ -1,27 +1,410 @@
|
||||
#include "ConfigHelper.h"
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSaveFile>
|
||||
#include <QSettings>
|
||||
#include <QDebug>
|
||||
#include "ConfigHelper.h"
|
||||
#include <QApplication>
|
||||
#include <QByteArray>
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMessageBox>
|
||||
#include <QSaveFile>
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
#include <QDebug>
|
||||
#include <string>
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString kElevatedConfigSetFlag = QStringLiteral("--simcae-config-set");
|
||||
const QString kElevatedFileWriteFlag = QStringLiteral("--simcae-file-write");
|
||||
const QString kElevatedFileRemoveFlag = QStringLiteral("--simcae-file-remove");
|
||||
const QString kConfigPathPrefix = QStringLiteral("--config-path-b64=");
|
||||
const QString kConfigKeyPrefix = QStringLiteral("--config-key-b64=");
|
||||
const QString kConfigValuePrefix = QStringLiteral("--config-value-b64=");
|
||||
const QString kFilePathPrefix = QStringLiteral("--file-path-b64=");
|
||||
const QString kFileDataPrefix = QStringLiteral("--file-data-b64=");
|
||||
const QString kRegistryOrganization = QStringLiteral("Marsco");
|
||||
const QString kRegistryApplication = QStringLiteral("UpdateClientSDK");
|
||||
const QString kRegistryInstallationsGroup = QStringLiteral("installations");
|
||||
const QString kRegistryConfigGroup = QStringLiteral("config");
|
||||
const QString kRegistryMetaGroup = QStringLiteral("_meta");
|
||||
const QString kRegistrySourceHashKey = QStringLiteral("source_sha256");
|
||||
const QString kRegistrySourcePathKey = QStringLiteral("source_path");
|
||||
const QString kRegistryRuntimeRootKey = QStringLiteral("runtime_root");
|
||||
const QString kRegistryImportedAtKey = QStringLiteral("imported_at_utc");
|
||||
|
||||
QString encodeArgument(const QString& value)
|
||||
{
|
||||
return QString::fromLatin1(value.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||
}
|
||||
|
||||
QString encodeBytes(const QByteArray& value)
|
||||
{
|
||||
return QString::fromLatin1(value.toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||
}
|
||||
|
||||
QString decodeArgument(const QString& value)
|
||||
{
|
||||
return QString::fromUtf8(QByteArray::fromBase64(value.toLatin1(),
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||
}
|
||||
|
||||
QByteArray decodeBytes(const QString& value)
|
||||
{
|
||||
return QByteArray::fromBase64(value.toLatin1(),
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
}
|
||||
|
||||
QString findArgumentValue(const QStringList& arguments, const QString& prefix)
|
||||
{
|
||||
for (const QString& argument : arguments)
|
||||
if (argument.startsWith(prefix))
|
||||
return argument.mid(prefix.size());
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool writeBytesToFile(const QString& path, const QByteArray& bytes, QString* errorMessage)
|
||||
{
|
||||
QDir dir(QFileInfo(path).path());
|
||||
if (!dir.exists() && !dir.mkpath("."))
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot create directory: %1").arg(dir.path());
|
||||
return false;
|
||||
}
|
||||
|
||||
QSaveFile output(path);
|
||||
if (!output.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot open file for writing: %1").arg(output.errorString());
|
||||
return false;
|
||||
}
|
||||
if (output.write(bytes) != bytes.size())
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot write full file: %1").arg(output.errorString());
|
||||
output.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
if (!output.commit())
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot commit file: %1").arg(output.errorString());
|
||||
return false;
|
||||
}
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool removeFile(const QString& path, QString* errorMessage)
|
||||
{
|
||||
if (!QFile::exists(path))
|
||||
{
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
if (QFile::remove(path))
|
||||
{
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot remove file: %1").arg(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool writeConfigValueToFile(const QString& configPath, const QString& key,
|
||||
const QString& value, QString* errorMessage)
|
||||
{
|
||||
QFile input(configPath);
|
||||
QJsonObject config;
|
||||
if (input.exists())
|
||||
{
|
||||
if (!input.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot open config for reading: %1").arg(input.errorString());
|
||||
return false;
|
||||
}
|
||||
QJsonParseError parseError;
|
||||
const QByteArray raw = input.readAll();
|
||||
input.close();
|
||||
const QJsonDocument document = QJsonDocument::fromJson(raw, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Invalid config JSON: %1").arg(parseError.errorString());
|
||||
return false;
|
||||
}
|
||||
config = document.object();
|
||||
}
|
||||
|
||||
config.insert(key, value);
|
||||
QDir dir(QFileInfo(configPath).path());
|
||||
if (!dir.exists() && !dir.mkpath("."))
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("Cannot create config directory: %1").arg(dir.path());
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray payload = QJsonDocument(config).toJson(QJsonDocument::Indented);
|
||||
return writeBytesToFile(configPath, payload, errorMessage);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString windowsErrorMessage(DWORD errorCode)
|
||||
{
|
||||
if (errorCode == ERROR_CANCELLED)
|
||||
return QCoreApplication::translate("ConfigHelper", "The user canceled the administrator permission confirmation.");
|
||||
return QCoreApplication::translate("ConfigHelper", "Windows error %1").arg(errorCode);
|
||||
}
|
||||
|
||||
bool runElevatedSelfCommand(const QStringList& arguments, const QString& targetPath,
|
||||
const QString& originalError, QString* errorMessage)
|
||||
{
|
||||
const QMessageBox::StandardButton choice = QMessageBox::question(
|
||||
nullptr,
|
||||
QCoreApplication::translate("ConfigHelper", "Administrator Permission Required"),
|
||||
QCoreApplication::translate("ConfigHelper", "The current installation directory requires administrator permission to save configuration.\n\nTarget file: %1\nReason: %2\n\nClick OK, then choose Yes in the Windows permission confirmation dialog.")
|
||||
.arg(QDir::toNativeSeparators(targetPath), originalError),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
QMessageBox::Ok);
|
||||
if (choice != QMessageBox::Ok)
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QCoreApplication::translate("ConfigHelper", "The user canceled the administrator permission request.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString executable = QCoreApplication::applicationFilePath();
|
||||
if (executable.isEmpty() || !QFileInfo::exists(executable))
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QStringLiteral("Cannot locate current executable for elevated config write.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString parameters = arguments.join(QLatin1Char(' '));
|
||||
const QString workingDir = QFileInfo(executable).absolutePath();
|
||||
|
||||
std::wstring verb = L"runas";
|
||||
std::wstring file = executable.toStdWString();
|
||||
std::wstring params = parameters.toStdWString();
|
||||
std::wstring directory = workingDir.toStdWString();
|
||||
|
||||
SHELLEXECUTEINFOW info{};
|
||||
info.cbSize = sizeof(info);
|
||||
info.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
info.lpVerb = verb.c_str();
|
||||
info.lpFile = file.c_str();
|
||||
info.lpParameters = params.c_str();
|
||||
info.lpDirectory = directory.c_str();
|
||||
info.nShow = SW_HIDE;
|
||||
|
||||
if (!ShellExecuteExW(&info))
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
if (errorMessage)
|
||||
*errorMessage = QStringLiteral("Cannot request administrator permission: %1").arg(windowsErrorMessage(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
WaitForSingleObject(info.hProcess, INFINITE);
|
||||
DWORD exitCode = 1;
|
||||
GetExitCodeProcess(info.hProcess, &exitCode);
|
||||
CloseHandle(info.hProcess);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QStringLiteral("Elevated config write failed with exit code %1.").arg(exitCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeConfigValueWithElevation(const QString& configPath, const QString& key,
|
||||
const QString& value, const QString& originalError,
|
||||
QString* errorMessage)
|
||||
{
|
||||
const QStringList arguments{
|
||||
kElevatedConfigSetFlag,
|
||||
kConfigPathPrefix + encodeArgument(configPath),
|
||||
kConfigKeyPrefix + encodeArgument(key),
|
||||
kConfigValuePrefix + encodeArgument(value)
|
||||
};
|
||||
return runElevatedSelfCommand(arguments, configPath, originalError, errorMessage);
|
||||
}
|
||||
|
||||
bool writeBytesWithElevation(const QString& path, const QByteArray& bytes,
|
||||
const QString& originalError, QString* errorMessage)
|
||||
{
|
||||
const QStringList arguments{
|
||||
kElevatedFileWriteFlag,
|
||||
kFilePathPrefix + encodeArgument(path),
|
||||
kFileDataPrefix + encodeBytes(bytes)
|
||||
};
|
||||
return runElevatedSelfCommand(arguments, path, originalError, errorMessage);
|
||||
}
|
||||
|
||||
bool removeFileWithElevation(const QString& path, const QString& originalError, QString* errorMessage)
|
||||
{
|
||||
const QStringList arguments{
|
||||
kElevatedFileRemoveFlag,
|
||||
kFilePathPrefix + encodeArgument(path)
|
||||
};
|
||||
return runElevatedSelfCommand(arguments, path, originalError, errorMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ConfigHelper& ConfigHelper::instance()
|
||||
{
|
||||
static ConfigHelper obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
int ConfigHelper::runElevatedWriteCommandIfRequested()
|
||||
{
|
||||
const QStringList arguments = QCoreApplication::arguments();
|
||||
if (arguments.contains(kElevatedConfigSetFlag))
|
||||
{
|
||||
const QString configPath = decodeArgument(findArgumentValue(arguments, kConfigPathPrefix));
|
||||
const QString key = decodeArgument(findArgumentValue(arguments, kConfigKeyPrefix));
|
||||
const QString value = decodeArgument(findArgumentValue(arguments, kConfigValuePrefix));
|
||||
if (configPath.isEmpty() || key.isEmpty())
|
||||
{
|
||||
qWarning() << "Elevated config write arguments are incomplete.";
|
||||
return 2;
|
||||
}
|
||||
|
||||
QString errorMessage;
|
||||
if (!writeConfigValueToFile(configPath, key, value, &errorMessage))
|
||||
{
|
||||
qWarning() << "Elevated config write failed:" << errorMessage;
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arguments.contains(kElevatedFileWriteFlag))
|
||||
{
|
||||
const QString path = decodeArgument(findArgumentValue(arguments, kFilePathPrefix));
|
||||
const QByteArray bytes = decodeBytes(findArgumentValue(arguments, kFileDataPrefix));
|
||||
if (path.isEmpty())
|
||||
{
|
||||
qWarning() << "Elevated file write arguments are incomplete.";
|
||||
return 2;
|
||||
}
|
||||
|
||||
QString errorMessage;
|
||||
if (!writeBytesToFile(path, bytes, &errorMessage))
|
||||
{
|
||||
qWarning() << "Elevated file write failed:" << errorMessage;
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arguments.contains(kElevatedFileRemoveFlag))
|
||||
{
|
||||
const QString path = decodeArgument(findArgumentValue(arguments, kFilePathPrefix));
|
||||
if (path.isEmpty())
|
||||
{
|
||||
qWarning() << "Elevated file remove arguments are incomplete.";
|
||||
return 2;
|
||||
}
|
||||
|
||||
QString errorMessage;
|
||||
if (!removeFile(path, &errorMessage))
|
||||
{
|
||||
qWarning() << "Elevated file remove failed:" << errorMessage;
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ConfigHelper::writeFileWithElevationIfNeeded(const QString& path, const QByteArray& data,
|
||||
QString* errorMessage)
|
||||
{
|
||||
QString localError;
|
||||
if (writeBytesToFile(path, data, &localError))
|
||||
{
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (data.size() > 24 * 1024)
|
||||
{
|
||||
if (errorMessage)
|
||||
*errorMessage = QString("File is too large for elevated inline write: %1 bytes. Original error: %2")
|
||||
.arg(data.size()).arg(localError);
|
||||
return false;
|
||||
}
|
||||
return writeBytesWithElevation(path, data, localError, errorMessage);
|
||||
#else
|
||||
if (errorMessage)
|
||||
*errorMessage = localError;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ConfigHelper::removeFileWithElevationIfNeeded(const QString& path, QString* errorMessage)
|
||||
{
|
||||
QString localError;
|
||||
if (removeFile(path, &localError))
|
||||
{
|
||||
if (errorMessage)
|
||||
errorMessage->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
return removeFileWithElevation(path, localError, errorMessage);
|
||||
#else
|
||||
if (errorMessage)
|
||||
*errorMessage = localError;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
ConfigHelper& ConfigHelper::instance()
|
||||
{
|
||||
static ConfigHelper obj;
|
||||
return obj;
|
||||
}
|
||||
|
||||
ConfigHelper::ConfigHelper()
|
||||
{
|
||||
m_configPath = QApplication::applicationDirPath() + "/config/app_config.json";
|
||||
migrateLegacyIniIfNeeded();
|
||||
qDebug() << "Loading app config path:" << m_configPath;
|
||||
qDebug() << "File exists?" << QFile::exists(m_configPath);
|
||||
}
|
||||
ConfigHelper::ConfigHelper()
|
||||
{
|
||||
m_configPath = QApplication::applicationDirPath() + "/config/app_config.json";
|
||||
m_registryInstallId = QString::fromLatin1(QCryptographicHash::hash(
|
||||
QDir::cleanPath(QApplication::applicationDirPath()).toUtf8(),
|
||||
QCryptographicHash::Sha256).toHex());
|
||||
migrateLegacyIniIfNeeded();
|
||||
syncRegistryFromConfigFileIfChanged();
|
||||
qDebug() << "Loading app config path:" << m_configPath;
|
||||
qDebug() << "File exists?" << QFile::exists(m_configPath);
|
||||
qDebug() << "Registry installation id:" << m_registryInstallId;
|
||||
}
|
||||
|
||||
QString ConfigHelper::configPath() const
|
||||
{
|
||||
@@ -57,80 +440,167 @@ QString ConfigHelper::runtimeRelativePath() const
|
||||
return relative.trimmed();
|
||||
}
|
||||
|
||||
QString ConfigHelper::lastError() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString ConfigHelper::getValue(const QString& section, const QString& key) const
|
||||
{
|
||||
Q_UNUSED(section);
|
||||
QFile file(m_configPath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return QString();
|
||||
|
||||
QJsonParseError error;
|
||||
const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject())
|
||||
return QString();
|
||||
|
||||
return document.object().value(key).toVariant().toString();
|
||||
}
|
||||
|
||||
bool ConfigHelper::setValue(const QString& section, const QString& key, const QString& value)
|
||||
{
|
||||
Q_UNUSED(section);
|
||||
m_error.clear();
|
||||
|
||||
QFile input(m_configPath);
|
||||
QJsonObject config;
|
||||
if (input.exists())
|
||||
{
|
||||
if (!input.open(QIODevice::ReadOnly))
|
||||
{
|
||||
m_error = QString("Cannot open config for reading: %1").arg(input.errorString());
|
||||
return false;
|
||||
}
|
||||
QJsonParseError parseError;
|
||||
const QByteArray raw = input.readAll();
|
||||
input.close();
|
||||
const QJsonDocument document = QJsonDocument::fromJson(raw, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||
{
|
||||
m_error = QString("Invalid config JSON: %1").arg(parseError.errorString());
|
||||
return false;
|
||||
}
|
||||
config = document.object();
|
||||
}
|
||||
|
||||
config.insert(key, value);
|
||||
QDir dir(QFileInfo(m_configPath).path());
|
||||
if (!dir.exists() && !dir.mkpath("."))
|
||||
{
|
||||
m_error = QString("Cannot create config directory: %1").arg(dir.path());
|
||||
return false;
|
||||
}
|
||||
|
||||
QSaveFile output(m_configPath);
|
||||
if (!output.open(QIODevice::WriteOnly))
|
||||
{
|
||||
m_error = QString("Cannot open config for writing: %1").arg(output.errorString());
|
||||
return false;
|
||||
}
|
||||
const QByteArray payload = QJsonDocument(config).toJson(QJsonDocument::Indented);
|
||||
if (output.write(payload) != payload.size())
|
||||
{
|
||||
m_error = QString("Cannot write full config file: %1").arg(output.errorString());
|
||||
output.cancelWriting();
|
||||
return false;
|
||||
}
|
||||
if (!output.commit())
|
||||
{
|
||||
m_error = QString("Cannot commit config file: %1").arg(output.errorString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QString ConfigHelper::lastError() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
void ConfigHelper::enterRegistryGroup(QSettings& settings) const
|
||||
{
|
||||
settings.beginGroup(kRegistryInstallationsGroup);
|
||||
settings.beginGroup(m_registryInstallId);
|
||||
}
|
||||
|
||||
bool ConfigHelper::syncRegistryFromConfigFileIfChanged()
|
||||
{
|
||||
QFile file(m_configPath);
|
||||
if (!file.exists())
|
||||
return true;
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
m_error = QStringLiteral("Cannot open config for reading: %1").arg(file.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QByteArray raw = file.readAll();
|
||||
const QJsonDocument document = QJsonDocument::fromJson(raw, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||
{
|
||||
m_error = QStringLiteral("Invalid config JSON: %1").arg(parseError.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray normalizedConfig = QJsonDocument(document.object()).toJson(QJsonDocument::Compact);
|
||||
const QString sourceHash = QString::fromLatin1(
|
||||
QCryptographicHash::hash(normalizedConfig, QCryptographicHash::Sha256).toHex());
|
||||
|
||||
QSettings settings(QSettings::NativeFormat, QSettings::UserScope,
|
||||
kRegistryOrganization, kRegistryApplication);
|
||||
enterRegistryGroup(settings);
|
||||
settings.beginGroup(kRegistryMetaGroup);
|
||||
const QString previousHash = settings.value(kRegistrySourceHashKey).toString();
|
||||
settings.endGroup();
|
||||
|
||||
if (previousHash == sourceHash)
|
||||
return true;
|
||||
|
||||
const bool configChangedAfterPreviousImport = !previousHash.isEmpty();
|
||||
|
||||
settings.beginGroup(kRegistryConfigGroup);
|
||||
settings.remove(QString());
|
||||
const QJsonObject config = document.object();
|
||||
for (auto it = config.constBegin(); it != config.constEnd(); ++it)
|
||||
settings.setValue(it.key(), it.value().toVariant());
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup(kRegistryMetaGroup);
|
||||
settings.setValue(kRegistrySourceHashKey, sourceHash);
|
||||
settings.setValue(kRegistrySourcePathKey, QDir::toNativeSeparators(QFileInfo(m_configPath).absoluteFilePath()));
|
||||
settings.setValue(kRegistryRuntimeRootKey, QDir::toNativeSeparators(QApplication::applicationDirPath()));
|
||||
settings.setValue(kRegistryImportedAtKey, QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
settings.endGroup();
|
||||
settings.sync();
|
||||
|
||||
if (settings.status() != QSettings::NoError)
|
||||
{
|
||||
m_error = QStringLiteral("Cannot sync config to registry.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_error.clear();
|
||||
qDebug() << "Synced app_config.json to registry installation id:" << m_registryInstallId;
|
||||
|
||||
if (configChangedAfterPreviousImport)
|
||||
{
|
||||
const QString configDir = QFileInfo(m_configPath).absolutePath();
|
||||
const QStringList staleFiles{
|
||||
QDir(configDir).filePath(QStringLiteral("client_identity.dat")),
|
||||
QDir(configDir).filePath(QStringLiteral("version_policy.dat"))
|
||||
};
|
||||
for (const QString& staleFile : staleFiles)
|
||||
{
|
||||
QString removeError;
|
||||
if (!ConfigHelper::removeFileWithElevationIfNeeded(staleFile, &removeError))
|
||||
{
|
||||
m_error = QStringLiteral("Cannot remove stale runtime file after config change: %1. %2")
|
||||
.arg(staleFile, removeError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
qDebug() << "Removed stale client identity and policy after app_config.json changed.";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigHelper::readRegistryValue(const QString& key, QString* value) const
|
||||
{
|
||||
QSettings settings(QSettings::NativeFormat, QSettings::UserScope,
|
||||
kRegistryOrganization, kRegistryApplication);
|
||||
enterRegistryGroup(settings);
|
||||
settings.beginGroup(kRegistryConfigGroup);
|
||||
if (!settings.contains(key))
|
||||
{
|
||||
settings.endGroup();
|
||||
return false;
|
||||
}
|
||||
if (value)
|
||||
*value = settings.value(key).toString();
|
||||
settings.endGroup();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigHelper::writeRegistryValue(const QString& key, const QString& value)
|
||||
{
|
||||
QSettings settings(QSettings::NativeFormat, QSettings::UserScope,
|
||||
kRegistryOrganization, kRegistryApplication);
|
||||
enterRegistryGroup(settings);
|
||||
settings.beginGroup(kRegistryConfigGroup);
|
||||
settings.setValue(key, value);
|
||||
settings.endGroup();
|
||||
settings.sync();
|
||||
|
||||
if (settings.status() != QSettings::NoError)
|
||||
{
|
||||
m_error = QStringLiteral("Cannot write config value to registry: %1").arg(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigHelper::readFileValue(const QString& key) const
|
||||
{
|
||||
QFile file(m_configPath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return QString();
|
||||
|
||||
QJsonParseError error;
|
||||
const QJsonDocument document = QJsonDocument::fromJson(file.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError || !document.isObject())
|
||||
return QString();
|
||||
|
||||
return document.object().value(key).toVariant().toString();
|
||||
}
|
||||
|
||||
QString ConfigHelper::getValue(const QString& section, const QString& key) const
|
||||
{
|
||||
Q_UNUSED(section);
|
||||
QString value;
|
||||
if (readRegistryValue(key, &value))
|
||||
return value;
|
||||
return readFileValue(key);
|
||||
}
|
||||
|
||||
bool ConfigHelper::setValue(const QString& section, const QString& key, const QString& value)
|
||||
{
|
||||
Q_UNUSED(section);
|
||||
m_error.clear();
|
||||
|
||||
return writeRegistryValue(key, value);
|
||||
}
|
||||
|
||||
bool ConfigHelper::migrateLegacyIniIfNeeded()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user