// SPDX-License-Identifier: GPL-3.0-or-later // Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync #include "loadconfigstore.h" #include "plugininfo.h" #include #include #include #include #include #include #include #include LoadConfigStore::LoadConfigStore(QObject *parent) : QObject{parent} { if (load()) qCDebug(dcNymeaEnergy()) << "[LoadConfigStore]" << m_configs.count() << "config(s) de charge pilotée chargée(s) depuis" << filePath(); else qCDebug(dcNymeaEnergy()) << "[LoadConfigStore] Aucune config de charge pilotée — démarrage à vide."; } QString LoadConfigStore::filePath() const { const QString env = QString(qgetenv("NYMEA_ENERGY_LOAD_CONFIG")); if (!env.isEmpty()) return env; return NymeaSettings::storagePath() + "/energy-load-configuration.json"; } bool LoadConfigStore::load() { const QString path = filePath(); if (!QFileInfo::exists(path)) return false; QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qCWarning(dcNymeaEnergy()) << "[LoadConfigStore] Ouverture impossible:" << path << file.errorString(); return false; } const QByteArray data = file.readAll(); file.close(); QJsonParseError err; const QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (err.error != QJsonParseError::NoError) { qCWarning(dcNymeaEnergy()) << "[LoadConfigStore] JSON invalide:" << err.errorString(); return false; } LoadConfigs loaded; const QJsonArray loads = doc.object().value("loads").toArray(); for (const QJsonValue &v : loads) { const LoadConfig c = LoadConfig::fromMap(v.toObject().toVariantMap()); QString why; if (!c.isValid(&why)) { // Tolérance ascendante : on ignore une entrée corrompue plutôt que de tout perdre. qCWarning(dcNymeaEnergy()) << "[LoadConfigStore] Entrée ignorée (invalide):" << why; continue; } loaded.append(c); } m_configs = loaded; return true; } bool LoadConfigStore::setConfigs(const LoadConfigs &configs, QString *error) { // Validation EN BLOC : une seule entrée invalide → rejet total (rien persisté). for (const LoadConfig &c : configs) { QString why; if (!c.isValid(&why)) { if (error) *error = QStringLiteral("Config \"%1\" invalide : %2").arg(c.label(), why); return false; } } const LoadConfigs previous = m_configs; m_configs = configs; if (!save(error)) { m_configs = previous; // rollback mémoire si l'écriture échoue return false; } emit changed(); return true; } bool LoadConfigStore::save(QString *error) const { QJsonArray loads; for (const LoadConfig &c : m_configs) loads.append(QJsonObject::fromVariantMap(c.toMap())); QJsonObject root; root.insert("version", 1); root.insert("loads", loads); // Écriture atomique (QSaveFile = tmp + rename) — jamais de fichier mi-écrit. QSaveFile file(filePath()); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { if (error) *error = QStringLiteral("Écriture impossible : %1").arg(file.errorString()); return false; } file.write(QJsonDocument(root).toJson(QJsonDocument::Indented)); if (!file.commit()) { if (error) *error = QStringLiteral("Commit échoué : %1").arg(file.errorString()); return false; } qCDebug(dcNymeaEnergy()) << "[LoadConfigStore]" << m_configs.count() << "config(s) persistée(s) dans" << filePath(); return true; }