#include "ConfigHelper.h" #include #include #include #include #include #include #include #include #include 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); } QString ConfigHelper::configPath() const { return m_configPath; } QString ConfigHelper::installRoot() const { QString relativeRoot = getValue("Runtime", "install_root").trimmed(); if (relativeRoot.isEmpty()) relativeRoot = "."; return QDir::cleanPath(QDir(runtimeRoot()).filePath(relativeRoot)); } QString ConfigHelper::runtimeRoot() const { return QDir::cleanPath(QApplication::applicationDirPath()); } QString ConfigHelper::updateRoot() const { return QDir(runtimeRoot()).filePath("update"); } QString ConfigHelper::runtimeRelativePath() const { QString relative = QDir::fromNativeSeparators(QDir(installRoot()).relativeFilePath(runtimeRoot())); relative = QDir::cleanPath(relative); if (relative == ".") return QString(); while (relative.startsWith("./")) relative = relative.mid(2); 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; } bool ConfigHelper::migrateLegacyIniIfNeeded() { if (QFile::exists(m_configPath)) return true; const QString legacyPath = QApplication::applicationDirPath() + "/client.ini"; if (!QFile::exists(legacyPath)) return false; QSettings ini(legacyPath, QSettings::IniFormat); QJsonObject config; const auto copyText = [&](const QString& section, const QString& key, const QString& fallback = QString()) { const QString value = ini.value(section + "/" + key, fallback).toString(); config.insert(key, value); }; copyText("App", "app_id"); copyText("App", "app_name", "Marsco Demo App"); copyText("App", "channel", "stable"); copyText("App", "current_version", "1.0.0"); copyText("App", "client_protocol", "3"); copyText("App", "launch_token"); copyText("License", "license_key"); copyText("Server", "api_base_url"); copyText("Server", "client_token"); copyText("Update", "request_timeout_ms", "5000"); copyText("Update", "temp_folder", "update_temp"); copyText("Update", "device_id"); copyText("Runtime", "install_root", "."); copyText("Runtime", "main_executable", "MainApp.exe"); copyText("Runtime", "launcher_executable", "Launcher.exe"); copyText("Runtime", "updater_executable", "Updater.exe"); copyText("Runtime", "bootstrap_executable", "Bootstrap.exe"); copyText("Runtime", "health_check_timeout_ms", "15000"); config.insert("platform", "windows"); config.insert("arch", "x64"); QDir().mkpath(QFileInfo(m_configPath).path()); QSaveFile output(m_configPath); if (!output.open(QIODevice::WriteOnly)) return false; output.write(QJsonDocument(config).toJson(QJsonDocument::Indented)); const bool saved = output.commit(); if (saved) qDebug() << "Migrated legacy client.ini to" << m_configPath; return saved; }