feat: add load/save + fail signals

This commit is contained in:
2 * r + 2 * t 2026-04-11 20:10:27 +10:00
parent e283ee2a9f
commit d5d6f70f52
6 changed files with 61 additions and 38 deletions

View file

@ -18,7 +18,7 @@ QString configDir() {
} // namespace
GlobalConfig::GlobalConfig(QObject* parent)
: ConfigObject(parent)
: RootConfig(parent)
, m_appearance(new AppearanceConfig(this))
, m_general(new GeneralConfig(this))
, m_background(new BackgroundConfig(this))
@ -47,7 +47,7 @@ GlobalConfig::GlobalConfig(QObject* parent)
}
GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent)
: ConfigObject(parent)
: RootConfig(parent)
, m_appearance(new AppearanceConfig(this))
, m_general(new GeneralConfig(this))
, m_background(new BackgroundConfig(this))
@ -120,14 +120,6 @@ GlobalConfig* GlobalConfig::create(QQmlEngine* engine, QJSEngine* jsEngine) {
return config;
}
void GlobalConfig::save() {
saveToFile();
}
void GlobalConfig::reload() {
reloadFromFile();
}
// Config (attached type)
Config::Config(ConfigScope* scope, QObject* parent)

View file

@ -22,7 +22,7 @@
namespace caelestia::config {
class GlobalConfig : public ConfigObject {
class GlobalConfig : public RootConfig {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
@ -50,9 +50,6 @@ public:
[[nodiscard]] Q_INVOKABLE GlobalConfig* defaults();
static GlobalConfig* create(QQmlEngine*, QJSEngine*);
Q_INVOKABLE void save();
Q_INVOKABLE void reload();
~GlobalConfig() override;
void bindAppearanceTokens();

View file

@ -298,12 +298,16 @@ void ConfigObject::setupFileBackend(const QString& path) {
QFile file(m_filePath);
if (!file.open(QIODevice::WriteOnly)) {
qCWarning(lcConfig) << "Failed to write" << m_filePath;
qCWarning(lcConfig, "Failed to write %s", qUtf8Printable(m_filePath));
if (auto* root = qobject_cast<RootConfig*>(this))
Q_EMIT root->fileSaveFailed(QStringLiteral("Failed to open file for writing"));
return;
}
auto json = m_sparse ? toSparseJsonObject() : toJsonObject();
file.write(QJsonDocument(json).toJson(QJsonDocument::Indented));
if (auto* root = qobject_cast<RootConfig*>(this))
Q_EMIT root->fileSaved();
});
m_cooldownTimer->setSingleShot(true);
@ -330,12 +334,12 @@ void ConfigObject::saveToFile() {
m_cooldownTimer->start();
}
void ConfigObject::reloadFromFile() {
bool ConfigObject::reloadFromFile() {
QFile file(m_filePath);
if (!file.open(QIODevice::ReadOnly)) {
qCDebug(lcConfig) << "Failed to open" << m_filePath;
return;
qCDebug(lcConfig, "Failed to open %s", qUtf8Printable(m_filePath));
return false;
}
QJsonParseError error{};
@ -344,14 +348,15 @@ void ConfigObject::reloadFromFile() {
if (error.error != QJsonParseError::NoError) {
if (m_retryTimer && m_parseRetries < 3) {
m_parseRetries++;
qCDebug(lcConfig, "Failed to parse %s: %s - retrying (%d/3)", qPrintable(m_filePath),
qPrintable(error.errorString()), 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", qPrintable(m_filePath), qPrintable(error.errorString()));
qCWarning(
lcConfig, "Failed to parse %s: %s", qUtf8Printable(m_filePath), qUtf8Printable(error.errorString()));
m_parseRetries = 0;
}
return;
return false;
}
m_parseRetries = 0;
@ -366,6 +371,8 @@ void ConfigObject::reloadFromFile() {
qCDebug(lcConfig) << "Re-syncing" << metaObject()->className() << "from global after reload";
resyncFromGlobal();
}
return true;
}
void ConfigObject::onFileChanged() {
@ -376,8 +383,29 @@ void ConfigObject::onFileChanged() {
m_parseRetries = 0;
if (m_retryTimer)
m_retryTimer->stop();
reloadFromFile();
bool ok = reloadFromFile();
if (auto* root = qobject_cast<RootConfig*>(this)) {
if (ok)
Q_EMIT root->fileLoaded();
else
Q_EMIT root->fileLoadFailed(QStringLiteral("Failed to load config file"));
}
}
}
// RootConfig
RootConfig::RootConfig(QObject* parent)
: ConfigObject(parent) {}
void RootConfig::save() {
saveToFile();
}
void RootConfig::reload() {
if (reloadFromFile())
Q_EMIT fileLoaded();
}
} // namespace caelestia::config

View file

@ -59,7 +59,7 @@ public:
// automatic file watching, debounced saving, and reload.
void setupFileBackend(const QString& path);
void saveToFile();
void reloadFromFile();
bool reloadFromFile();
// Per-monitor overlay support (Qt Resolve Mask pattern).
// Eagerly syncs non-overridden properties from a global ConfigObject.
@ -115,4 +115,21 @@ private:
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();
Q_SIGNAL void fileLoaded();
Q_SIGNAL void fileLoadFailed(const QString& error);
Q_SIGNAL void fileSaved();
Q_SIGNAL void fileSaveFailed(const QString& error);
};
} // namespace caelestia::config

View file

@ -18,7 +18,7 @@ QString configDir() {
} // namespace
TokenConfig::TokenConfig(QObject* parent)
: ConfigObject(parent)
: RootConfig(parent)
, m_appearance(new AppearanceTokens(this))
, m_bar(new BarTokens(this))
, m_dashboard(new DashboardTokens(this))
@ -41,7 +41,7 @@ TokenConfig::TokenConfig(QObject* parent)
}
TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent)
: ConfigObject(parent)
: RootConfig(parent)
, m_appearance(new AppearanceTokens(this))
, m_bar(new BarTokens(this))
, m_dashboard(new DashboardTokens(this))
@ -80,14 +80,6 @@ TokenConfig* TokenConfig::create(QQmlEngine* engine, QJSEngine*) {
return new TokenConfig(engine);
}
void TokenConfig::save() {
saveToFile();
}
void TokenConfig::reload() {
reloadFromFile();
}
// Tokens (attached type)
Tokens::Tokens(ConfigScope* scope, QObject* parent)

View file

@ -289,7 +289,7 @@ public:
: ConfigObject(parent) {}
};
class TokenConfig : public ConfigObject {
class TokenConfig : public RootConfig {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
@ -312,9 +312,6 @@ public:
[[nodiscard]] Q_INVOKABLE TokenConfig* defaults();
static TokenConfig* create(QQmlEngine*, QJSEngine*);
Q_INVOKABLE void save();
Q_INVOKABLE void reload();
~TokenConfig() override;
private: