refactor: move file logic to RootConfig + to own file
This commit is contained in:
parent
697784abce
commit
72e0a218fd
7 changed files with 197 additions and 195 deletions
|
|
@ -3,6 +3,7 @@ qml_module(caelestia-config
|
||||||
SOURCES
|
SOURCES
|
||||||
config.cpp
|
config.cpp
|
||||||
configobject.cpp
|
configobject.cpp
|
||||||
|
rootconfig.cpp
|
||||||
configscope.cpp
|
configscope.cpp
|
||||||
appearanceconfig.cpp
|
appearanceconfig.cpp
|
||||||
tokens.cpp
|
tokens.cpp
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "lockconfig.hpp"
|
#include "lockconfig.hpp"
|
||||||
#include "notifsconfig.hpp"
|
#include "notifsconfig.hpp"
|
||||||
#include "osdconfig.hpp"
|
#include "osdconfig.hpp"
|
||||||
|
#include "rootconfig.hpp"
|
||||||
#include "serviceconfig.hpp"
|
#include "serviceconfig.hpp"
|
||||||
#include "sessionconfig.hpp"
|
#include "sessionconfig.hpp"
|
||||||
#include "sidebarconfig.hpp"
|
#include "sidebarconfig.hpp"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
#include "configobject.hpp"
|
#include "configobject.hpp"
|
||||||
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qfile.h>
|
|
||||||
#include <qfileinfo.h>
|
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
|
||||||
#include <qjsonvalue.h>
|
#include <qjsonvalue.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qmetaobject.h>
|
#include <qmetaobject.h>
|
||||||
|
|
@ -15,6 +11,8 @@ namespace caelestia::config {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcConfig, "caelestia.config", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcConfig, "caelestia.config", QtInfoMsg)
|
||||||
|
|
||||||
|
// ConfigObject
|
||||||
|
|
||||||
ConfigObject::ConfigObject(QObject* parent)
|
ConfigObject::ConfigObject(QObject* parent)
|
||||||
: QObject(parent) {}
|
: QObject(parent) {}
|
||||||
|
|
||||||
|
|
@ -169,24 +167,6 @@ void ConfigObject::syncFromGlobal(ConfigObject* global) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigObject::markPropertyLoaded(const QString& name) {
|
|
||||||
m_loadedKeys.insert(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigObject::resetOption(const QString& name) {
|
|
||||||
m_loadedKeys.remove(name);
|
|
||||||
|
|
||||||
// If synced from global, re-copy the global value
|
|
||||||
if (m_global) {
|
|
||||||
int idx = metaObject()->indexOfProperty(name.toUtf8().constData());
|
|
||||||
if (idx >= 0) {
|
|
||||||
auto prop = metaObject()->property(idx);
|
|
||||||
if (prop.isWritable())
|
|
||||||
prop.write(this, prop.read(m_global));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigObject::resyncFromGlobal() {
|
void ConfigObject::resyncFromGlobal() {
|
||||||
if (!m_global)
|
if (!m_global)
|
||||||
return;
|
return;
|
||||||
|
|
@ -214,6 +194,24 @@ void ConfigObject::resyncFromGlobal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConfigObject::markPropertyLoaded(const QString& name) {
|
||||||
|
m_loadedKeys.insert(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigObject::resetOption(const QString& name) {
|
||||||
|
m_loadedKeys.remove(name);
|
||||||
|
|
||||||
|
// If synced from global, re-copy the global value
|
||||||
|
if (m_global) {
|
||||||
|
int idx = metaObject()->indexOfProperty(name.toUtf8().constData());
|
||||||
|
if (idx >= 0) {
|
||||||
|
auto prop = metaObject()->property(idx);
|
||||||
|
if (prop.isWritable())
|
||||||
|
prop.write(this, prop.read(m_global));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ConfigObject::onGlobalPropertiesChanged(const QMap<QString, QVariant>& changed) {
|
void ConfigObject::onGlobalPropertiesChanged(const QMap<QString, QVariant>& changed) {
|
||||||
for (auto it = changed.begin(); it != changed.end(); ++it) {
|
for (auto it = changed.begin(); it != changed.end(); ++it) {
|
||||||
if (m_loadedKeys.contains(it.key()))
|
if (m_loadedKeys.contains(it.key()))
|
||||||
|
|
@ -222,9 +220,7 @@ void ConfigObject::onGlobalPropertiesChanged(const QMap<QString, QVariant>& chan
|
||||||
int idx = metaObject()->indexOfProperty(it.key().toUtf8().constData());
|
int idx = metaObject()->indexOfProperty(it.key().toUtf8().constData());
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
metaObject()->property(idx).write(this, it.value());
|
metaObject()->property(idx).write(this, it.value());
|
||||||
// Remove the key that was added by markPropertyLoaded in the setter —
|
m_loadedKeys.remove(it.key()); // setter added it — remove since this is a synced value
|
||||||
// this is a synced value, not an explicit override
|
|
||||||
m_loadedKeys.remove(it.key());
|
|
||||||
qCDebug(lcConfig) << metaObject()->className() << "synced" << it.key() << "=" << it.value()
|
qCDebug(lcConfig) << metaObject()->className() << "synced" << it.key() << "=" << it.value()
|
||||||
<< "from global change";
|
<< "from global change";
|
||||||
}
|
}
|
||||||
|
|
@ -253,134 +249,4 @@ void ConfigObject::emitBatchedChanges() {
|
||||||
emit propertiesChanged(changes);
|
emit propertiesChanged(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigObject::setupFileBackend(const QString& path) {
|
|
||||||
m_filePath = path;
|
|
||||||
|
|
||||||
m_watcher = new QFileSystemWatcher(this);
|
|
||||||
m_saveTimer = new QTimer(this);
|
|
||||||
m_cooldownTimer = new QTimer(this);
|
|
||||||
m_retryTimer = new QTimer(this);
|
|
||||||
|
|
||||||
m_retryTimer->setSingleShot(true);
|
|
||||||
m_retryTimer->setInterval(50);
|
|
||||||
connect(m_retryTimer, &QTimer::timeout, this, &ConfigObject::reloadFromFile);
|
|
||||||
|
|
||||||
m_saveTimer->setSingleShot(true);
|
|
||||||
m_saveTimer->setInterval(500);
|
|
||||||
connect(m_saveTimer, &QTimer::timeout, this, [this] {
|
|
||||||
QDir().mkpath(QFileInfo(m_filePath).absolutePath());
|
|
||||||
|
|
||||||
QFile file(m_filePath);
|
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
|
||||||
qCWarning(lcConfig, "Failed to write %s", qUtf8Printable(m_filePath));
|
|
||||||
if (auto* root = qobject_cast<RootConfig*>(this))
|
|
||||||
emit root->saveFailed(QStringLiteral("Failed to open file for writing"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto json = toJsonObject();
|
|
||||||
file.write(QJsonDocument(json).toJson(QJsonDocument::Indented));
|
|
||||||
file.close();
|
|
||||||
if (auto* root = qobject_cast<RootConfig*>(this))
|
|
||||||
emit root->saved();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_cooldownTimer->setSingleShot(true);
|
|
||||||
m_cooldownTimer->setInterval(2000);
|
|
||||||
connect(m_cooldownTimer, &QTimer::timeout, this, [this] {
|
|
||||||
m_recentlySaved = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ConfigObject::onFileChanged);
|
|
||||||
|
|
||||||
qCDebug(lcConfig) << "Setting up file backend for" << metaObject()->className() << "at" << path;
|
|
||||||
|
|
||||||
reloadFromFile();
|
|
||||||
|
|
||||||
if (QFile::exists(m_filePath))
|
|
||||||
m_watcher->addPath(m_filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigObject::saveToFile() {
|
|
||||||
if (!m_saveTimer)
|
|
||||||
return;
|
|
||||||
m_saveTimer->start();
|
|
||||||
m_recentlySaved = true;
|
|
||||||
m_cooldownTimer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConfigObject::reloadFromFile() {
|
|
||||||
QFile file(m_filePath);
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
qCDebug(lcConfig, "Failed to open %s", qUtf8Printable(m_filePath));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonParseError error{};
|
|
||||||
auto doc = QJsonDocument::fromJson(file.readAll(), &error);
|
|
||||||
|
|
||||||
if (error.error != QJsonParseError::NoError) {
|
|
||||||
if (m_retryTimer && m_parseRetries < 3) {
|
|
||||||
m_parseRetries++;
|
|
||||||
qCDebug(lcConfig, "Failed to parse %s: %s - retrying (%d/3)", qUtf8Printable(m_filePath),
|
|
||||||
qUtf8Printable(error.errorString()), m_parseRetries);
|
|
||||||
m_retryTimer->start();
|
|
||||||
} else {
|
|
||||||
qCWarning(
|
|
||||||
lcConfig, "Failed to parse %s: %s", qUtf8Printable(m_filePath), qUtf8Printable(error.errorString()));
|
|
||||||
m_parseRetries = 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_parseRetries = 0;
|
|
||||||
|
|
||||||
qCDebug(lcConfig) << "Reloading" << metaObject()->className() << "from" << m_filePath;
|
|
||||||
|
|
||||||
clearLoadedKeys();
|
|
||||||
loadFromJson(doc.object());
|
|
||||||
|
|
||||||
// Re-sync non-loaded properties from global after reload
|
|
||||||
if (m_global) {
|
|
||||||
qCDebug(lcConfig) << "Re-syncing" << metaObject()->className() << "from global after reload";
|
|
||||||
resyncFromGlobal();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigObject::onFileChanged() {
|
|
||||||
if (!m_watcher->files().contains(m_filePath))
|
|
||||||
m_watcher->addPath(m_filePath);
|
|
||||||
|
|
||||||
if (!m_recentlySaved) {
|
|
||||||
m_parseRetries = 0;
|
|
||||||
if (m_retryTimer)
|
|
||||||
m_retryTimer->stop();
|
|
||||||
|
|
||||||
bool ok = reloadFromFile();
|
|
||||||
if (auto* root = qobject_cast<RootConfig*>(this)) {
|
|
||||||
if (ok)
|
|
||||||
emit root->loaded();
|
|
||||||
else
|
|
||||||
emit root->loadFailed(QStringLiteral("Failed to load config file"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootConfig
|
|
||||||
|
|
||||||
RootConfig::RootConfig(QObject* parent)
|
|
||||||
: ConfigObject(parent) {}
|
|
||||||
|
|
||||||
void RootConfig::save() {
|
|
||||||
saveToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RootConfig::reload() {
|
|
||||||
if (reloadFromFile())
|
|
||||||
emit loaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace caelestia::config
|
} // namespace caelestia::config
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qfilesystemwatcher.h>
|
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qmap.h>
|
#include <qmap.h>
|
||||||
|
|
@ -55,14 +54,7 @@ public:
|
||||||
void loadFromJson(const QJsonObject& obj);
|
void loadFromJson(const QJsonObject& obj);
|
||||||
[[nodiscard]] QJsonObject toJsonObject() const;
|
[[nodiscard]] QJsonObject toJsonObject() const;
|
||||||
|
|
||||||
// File-backed config support. Call setupFileBackend() to enable
|
|
||||||
// automatic file watching, debounced saving, and reload.
|
|
||||||
void setupFileBackend(const QString& path);
|
|
||||||
void saveToFile();
|
|
||||||
bool reloadFromFile();
|
|
||||||
|
|
||||||
// Per-monitor overlay support (Qt Resolve Mask pattern).
|
// Per-monitor overlay support (Qt Resolve Mask pattern).
|
||||||
// Eagerly syncs non-overridden properties from a global ConfigObject.
|
|
||||||
void syncFromGlobal(ConfigObject* global);
|
void syncFromGlobal(ConfigObject* global);
|
||||||
void resyncFromGlobal();
|
void resyncFromGlobal();
|
||||||
void clearLoadedKeys();
|
void clearLoadedKeys();
|
||||||
|
|
@ -71,8 +63,6 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void resetOption(const QString& name);
|
Q_INVOKABLE void resetOption(const QString& name);
|
||||||
|
|
||||||
[[nodiscard]] bool recentlySaved() const { return m_recentlySaved; }
|
|
||||||
|
|
||||||
template <typename T> static bool updateMember(T& member, const T& value) {
|
template <typename T> static bool updateMember(T& member, const T& value) {
|
||||||
if constexpr (std::is_floating_point_v<T>) {
|
if constexpr (std::is_floating_point_v<T>) {
|
||||||
if (qFuzzyCompare(member + 1.0, value + 1.0))
|
if (qFuzzyCompare(member + 1.0, value + 1.0))
|
||||||
|
|
@ -93,20 +83,9 @@ protected:
|
||||||
void notifyPropertyChanged(const QString& name, const QVariant& value);
|
void notifyPropertyChanged(const QString& name, const QVariant& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onFileChanged();
|
|
||||||
void onGlobalPropertiesChanged(const QMap<QString, QVariant>& changed);
|
void onGlobalPropertiesChanged(const QMap<QString, QVariant>& changed);
|
||||||
void emitBatchedChanges();
|
void emitBatchedChanges();
|
||||||
|
|
||||||
QString m_filePath;
|
|
||||||
bool m_recentlySaved = false;
|
|
||||||
|
|
||||||
// File backend (heap-allocated only when setupFileBackend is called)
|
|
||||||
QFileSystemWatcher* m_watcher = nullptr;
|
|
||||||
QTimer* m_saveTimer = nullptr;
|
|
||||||
QTimer* m_cooldownTimer = nullptr;
|
|
||||||
QTimer* m_retryTimer = nullptr;
|
|
||||||
int m_parseRetries = 0;
|
|
||||||
|
|
||||||
// Per-monitor overlay state
|
// Per-monitor overlay state
|
||||||
ConfigObject* m_global = nullptr;
|
ConfigObject* m_global = nullptr;
|
||||||
QSet<QString> m_loadedKeys;
|
QSet<QString> m_loadedKeys;
|
||||||
|
|
@ -114,22 +93,4 @@ private:
|
||||||
QTimer* m_batchTimer = nullptr;
|
QTimer* m_batchTimer = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Intermediate base for singleton config roots (GlobalConfig, TokenConfig).
|
|
||||||
// Provides save/reload with file lifecycle signals.
|
|
||||||
class RootConfig : public ConfigObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit RootConfig(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
Q_INVOKABLE void save();
|
|
||||||
Q_INVOKABLE void reload();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void loaded();
|
|
||||||
void loadFailed(const QString& error);
|
|
||||||
void saved();
|
|
||||||
void saveFailed(const QString& error);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace caelestia::config
|
} // namespace caelestia::config
|
||||||
|
|
|
||||||
127
plugin/src/Caelestia/Config/rootconfig.cpp
Normal file
127
plugin/src/Caelestia/Config/rootconfig.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
#include "rootconfig.hpp"
|
||||||
|
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qfile.h>
|
||||||
|
#include <qfileinfo.h>
|
||||||
|
#include <qjsondocument.h>
|
||||||
|
|
||||||
|
namespace caelestia::config {
|
||||||
|
|
||||||
|
RootConfig::RootConfig(QObject* parent)
|
||||||
|
: ConfigObject(parent) {}
|
||||||
|
|
||||||
|
void RootConfig::setupFileBackend(const QString& path) {
|
||||||
|
m_filePath = path;
|
||||||
|
|
||||||
|
m_watcher = new QFileSystemWatcher(this);
|
||||||
|
m_saveTimer = new QTimer(this);
|
||||||
|
m_cooldownTimer = new QTimer(this);
|
||||||
|
m_retryTimer = new QTimer(this);
|
||||||
|
|
||||||
|
m_retryTimer->setSingleShot(true);
|
||||||
|
m_retryTimer->setInterval(50);
|
||||||
|
connect(m_retryTimer, &QTimer::timeout, this, &RootConfig::reloadFromFile);
|
||||||
|
|
||||||
|
m_saveTimer->setSingleShot(true);
|
||||||
|
m_saveTimer->setInterval(500);
|
||||||
|
connect(m_saveTimer, &QTimer::timeout, this, [this] {
|
||||||
|
QDir().mkpath(QFileInfo(m_filePath).absolutePath());
|
||||||
|
|
||||||
|
QFile file(m_filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
qCWarning(lcConfig, "Failed to write %s", qUtf8Printable(m_filePath));
|
||||||
|
emit saveFailed(QStringLiteral("Failed to open file for writing"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto json = toJsonObject();
|
||||||
|
file.write(QJsonDocument(json).toJson(QJsonDocument::Indented));
|
||||||
|
file.close();
|
||||||
|
emit saved();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_cooldownTimer->setSingleShot(true);
|
||||||
|
m_cooldownTimer->setInterval(2000);
|
||||||
|
connect(m_cooldownTimer, &QTimer::timeout, this, [this] {
|
||||||
|
m_recentlySaved = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &RootConfig::onFileChanged);
|
||||||
|
|
||||||
|
qCDebug(lcConfig) << "Setting up file backend for" << metaObject()->className() << "at" << path;
|
||||||
|
|
||||||
|
reloadFromFile();
|
||||||
|
|
||||||
|
if (QFile::exists(m_filePath))
|
||||||
|
m_watcher->addPath(m_filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootConfig::saveToFile() {
|
||||||
|
if (!m_saveTimer)
|
||||||
|
return;
|
||||||
|
m_saveTimer->start();
|
||||||
|
m_recentlySaved = true;
|
||||||
|
m_cooldownTimer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RootConfig::reloadFromFile() {
|
||||||
|
QFile file(m_filePath);
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qCDebug(lcConfig, "Failed to open %s", qUtf8Printable(m_filePath));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError error{};
|
||||||
|
auto doc = QJsonDocument::fromJson(file.readAll(), &error);
|
||||||
|
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
if (m_retryTimer && m_parseRetries < 3) {
|
||||||
|
m_parseRetries++;
|
||||||
|
qCDebug(lcConfig, "Failed to parse %s: %s - retrying (%d/3)", qUtf8Printable(m_filePath),
|
||||||
|
qUtf8Printable(error.errorString()), m_parseRetries);
|
||||||
|
m_retryTimer->start();
|
||||||
|
} else {
|
||||||
|
qCWarning(
|
||||||
|
lcConfig, "Failed to parse %s: %s", qUtf8Printable(m_filePath), qUtf8Printable(error.errorString()));
|
||||||
|
m_parseRetries = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parseRetries = 0;
|
||||||
|
|
||||||
|
qCDebug(lcConfig) << "Reloading" << metaObject()->className() << "from" << m_filePath;
|
||||||
|
|
||||||
|
clearLoadedKeys();
|
||||||
|
loadFromJson(doc.object());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootConfig::onFileChanged() {
|
||||||
|
if (!m_watcher->files().contains(m_filePath))
|
||||||
|
m_watcher->addPath(m_filePath);
|
||||||
|
|
||||||
|
if (!m_recentlySaved) {
|
||||||
|
m_parseRetries = 0;
|
||||||
|
if (m_retryTimer)
|
||||||
|
m_retryTimer->stop();
|
||||||
|
|
||||||
|
if (reloadFromFile())
|
||||||
|
emit loaded();
|
||||||
|
else
|
||||||
|
emit loadFailed(QStringLiteral("Failed to load config file"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootConfig::save() {
|
||||||
|
saveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RootConfig::reload() {
|
||||||
|
if (reloadFromFile())
|
||||||
|
emit loaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace caelestia::config
|
||||||
46
plugin/src/Caelestia/Config/rootconfig.hpp
Normal file
46
plugin/src/Caelestia/Config/rootconfig.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configobject.hpp"
|
||||||
|
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
namespace caelestia::config {
|
||||||
|
|
||||||
|
// Intermediate base for singleton config roots (GlobalConfig, TokenConfig).
|
||||||
|
// Provides file-backed persistence, save/reload, and lifecycle signals.
|
||||||
|
class RootConfig : public ConfigObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RootConfig(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void setupFileBackend(const QString& path);
|
||||||
|
void saveToFile();
|
||||||
|
bool reloadFromFile();
|
||||||
|
|
||||||
|
[[nodiscard]] bool recentlySaved() const { return m_recentlySaved; }
|
||||||
|
|
||||||
|
Q_INVOKABLE void save();
|
||||||
|
Q_INVOKABLE void reload();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void loaded();
|
||||||
|
void loadFailed(const QString& error);
|
||||||
|
void saved();
|
||||||
|
void saveFailed(const QString& error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onFileChanged();
|
||||||
|
|
||||||
|
QString m_filePath;
|
||||||
|
bool m_recentlySaved = false;
|
||||||
|
|
||||||
|
QFileSystemWatcher* m_watcher = nullptr;
|
||||||
|
QTimer* m_saveTimer = nullptr;
|
||||||
|
QTimer* m_cooldownTimer = nullptr;
|
||||||
|
QTimer* m_retryTimer = nullptr;
|
||||||
|
int m_parseRetries = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace caelestia::config
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include "anim.hpp"
|
#include "anim.hpp"
|
||||||
#include "appearanceconfig.hpp"
|
#include "appearanceconfig.hpp"
|
||||||
#include "configobject.hpp"
|
#include "rootconfig.hpp"
|
||||||
|
|
||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue