fix: define declared method + insert new bar entries to config file list + rename Hyprsunset -> Nightlight

This commit is contained in:
2026-09-30 14:26:56 +02:00
parent 133d453f6f
commit 10d763cfe5
23 changed files with 1445 additions and 1184 deletions
+157 -120
View File
@@ -30,184 +30,221 @@ constexpr int k_maxLoadRetries = 3;
} // namespace
SettingsFile::SettingsFile(QString path, QObject* parent)
: QObject(parent)
, m_path(std::move(path))
, m_watcher(new QFileSystemWatcher(this))
, m_saveDebounce(new QTimer(this))
, m_loadDebounce(new QTimer(this))
, m_loadRetries(0) {
m_saveDebounce->setSingleShot(true);
m_saveDebounce->setInterval(500); // Save at most once every 500ms
: QObject(parent)
, m_path(std::move(path))
, m_watcher(new QFileSystemWatcher(this))
, m_saveDebounce(new QTimer(this))
, m_loadDebounce(new QTimer(this))
, m_loadRetries(0) {
m_saveDebounce->setSingleShot(true);
m_saveDebounce->setInterval(500); // Save at most once every 500ms
m_loadDebounce->setSingleShot(true);
m_loadDebounce->setInterval(50); // Coalesce watcher events within 50ms
QObject::connect(m_loadDebounce, &QTimer::timeout, this, &SettingsFile::onLoadDebounced);
m_loadDebounce->setSingleShot(true);
m_loadDebounce->setInterval(50); // Coalesce watcher events within 50ms
QObject::connect(
m_loadDebounce, &QTimer::timeout, this, &SettingsFile::onLoadDebounced);
QObject::connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &SettingsFile::onFileChanged);
QObject::connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &SettingsFile::onDirChanged);
QObject::connect(
m_watcher,
&QFileSystemWatcher::fileChanged,
this,
&SettingsFile::onFileChanged);
QObject::connect(
m_watcher,
&QFileSystemWatcher::directoryChanged,
this,
&SettingsFile::onDirChanged);
initWatcher();
initWatcher();
}
std::optional<QJsonValue> SettingsFile::read() const {
return m_lastData;
return m_lastData;
}
void SettingsFile::write(const QJsonValue& json) {
m_pendingWrite = json;
save();
m_pendingWrite = json;
save();
}
void SettingsFile::onFileChanged() {
initWatcher(); // Re-add path in case the file was replaced
scheduleLoad();
initWatcher(); // Re-add path in case the file was replaced
scheduleLoad();
}
void SettingsFile::onDirChanged() {
// Ignore if the file is already watched
if (m_watcher->files().contains(m_path))
return;
// Ignore if the file is already watched
if (m_watcher->files().contains(m_path)) return;
initWatcher();
initWatcher();
if (QFile::exists(m_path))
scheduleLoad();
if (QFile::exists(m_path)) scheduleLoad();
}
void SettingsFile::initWatcher() {
const QFileInfo info(m_path);
const auto dir = info.absolutePath();
const QFileInfo info(m_path);
const auto dir = info.absolutePath();
if (QDir(dir).exists() && !m_watcher->directories().contains(dir))
m_watcher->addPath(dir);
if (QDir(dir).exists() && !m_watcher->directories().contains(dir))
m_watcher->addPath(dir);
if (info.exists() && !m_watcher->files().contains(m_path))
m_watcher->addPath(m_path);
if (info.exists() && !m_watcher->files().contains(m_path))
m_watcher->addPath(m_path);
}
void SettingsFile::scheduleLoad() {
// Ignore if a load is already pending
if (m_loadDebounce->isActive())
return;
// Ignore if a load is already pending
if (m_loadDebounce->isActive()) return;
m_loadRetries = 0;
m_loadDebounce->start();
m_loadRetries = 0;
m_loadDebounce->start();
}
void SettingsFile::onLoadDebounced() {
const auto isFinalTry = m_loadRetries >= k_maxLoadRetries;
const auto isFinalTry = m_loadRetries >= k_maxLoadRetries;
if (load(isFinalTry) == LoadResult::ParseError && !isFinalTry) {
// Likely a partial write, retry after another debounce
++m_loadRetries;
qCDebug(lcSettingsFile, "Retrying load of %s (%d/%d)", qUtf8Printable(m_path), m_loadRetries, k_maxLoadRetries);
m_loadDebounce->start();
}
if (load(isFinalTry) == LoadResult::ParseError && !isFinalTry) {
// Likely a partial write, retry after another debounce
++m_loadRetries;
qCDebug(
lcSettingsFile,
"Retrying load of %s (%d/%d)",
qUtf8Printable(m_path),
m_loadRetries,
k_maxLoadRetries);
m_loadDebounce->start();
}
}
SettingsFile::LoadResult SettingsFile::load(bool reportErrors) {
QFile file(m_path);
QFile file(m_path);
if (!file.exists()) {
if (m_lastData) {
m_lastData = std::nullopt;
emit changed();
return LoadResult::Changed;
}
return LoadResult::Unchanged;
}
if (!file.exists()) {
if (m_lastData) {
m_lastData = std::nullopt;
emit changed();
return LoadResult::Changed;
}
return LoadResult::Unchanged;
}
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(lcSettingsFile, "Failed to open %s for reading: %s", qUtf8Printable(m_path),
qUtf8Printable(file.errorString()));
emit readFailed(QStringLiteral("Failed to open: %1").arg(file.errorString()));
return LoadResult::Error;
}
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(
lcSettingsFile,
"Failed to open %s for reading: %s",
qUtf8Printable(m_path),
qUtf8Printable(file.errorString()));
emit readFailed(
QStringLiteral("Failed to open: %1").arg(file.errorString()));
return LoadResult::Error;
}
const auto data = file.readAll();
file.close();
const auto data = file.readAll();
file.close();
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(data, &error);
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
if (reportErrors) {
qCWarning(lcSettingsFile, "Failed to parse %s as JSON: %s", qUtf8Printable(m_path),
qUtf8Printable(error.errorString()));
emit readFailed(QStringLiteral("Failed to parse: %1").arg(error.errorString()));
} else {
qCDebug(lcSettingsFile, "Failed to parse %s as JSON: %s", qUtf8Printable(m_path),
qUtf8Printable(error.errorString()));
}
return LoadResult::ParseError;
}
if (error.error != QJsonParseError::NoError) {
if (reportErrors) {
qCWarning(
lcSettingsFile,
"Failed to parse %s as JSON: %s",
qUtf8Printable(m_path),
qUtf8Printable(error.errorString()));
emit readFailed(
QStringLiteral("Failed to parse: %1").arg(error.errorString()));
} else {
qCDebug(
lcSettingsFile,
"Failed to parse %s as JSON: %s",
qUtf8Printable(m_path),
qUtf8Printable(error.errorString()));
}
return LoadResult::ParseError;
}
const auto json = doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
const auto json = doc.isObject() ? QJsonValue(doc.object())
: QJsonValue(doc.array());
if (json == m_lastData)
return LoadResult::Unchanged;
if (json == m_lastData) return LoadResult::Unchanged;
// Clear pending write, stuff loaded from file should take precedence
m_pendingWrite = std::nullopt;
// Clear pending write, stuff loaded from file should take precedence
m_pendingWrite = std::nullopt;
m_lastData = json;
emit changed();
m_lastData = json;
emit changed();
qCDebug(lcSettingsFile) << "Read JSON from" << m_path;
qCDebug(lcSettingsFile) << " " << json;
qCDebug(lcSettingsFile) << "Read JSON from" << m_path;
qCDebug(lcSettingsFile) << " " << json;
return LoadResult::Changed;
return LoadResult::Changed;
}
void SettingsFile::save() {
if (!m_pendingWrite)
return;
if (!m_pendingWrite) return;
if (m_saveDebounce->isActive()) {
// Queue save for debounce end
QObject::connect(m_saveDebounce, &QTimer::timeout, this, &SettingsFile::save,
// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ConnectionType is OR-able by design
static_cast<Qt::ConnectionType>(Qt::UniqueConnection | Qt::SingleShotConnection));
return;
}
if (m_saveDebounce->isActive()) {
// Queue save for debounce end
QObject::connect(
m_saveDebounce,
&QTimer::timeout,
this,
&SettingsFile::save,
// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) ConnectionType is OR-able by design
static_cast<Qt::ConnectionType>(
Qt::UniqueConnection | Qt::SingleShotConnection));
return;
}
// Debounce regardless of whether the save actually succeeded
m_saveDebounce->start();
// Debounce regardless of whether the save actually succeeded
m_saveDebounce->start();
const auto json = m_pendingWrite.value();
const auto data = (json.isObject() ? QJsonDocument(json.toObject()) : QJsonDocument(json.toArray())).toJson();
m_pendingWrite = std::nullopt;
const auto json = m_pendingWrite.value();
const auto data = (json.isObject() ? QJsonDocument(json.toObject())
: QJsonDocument(json.toArray()))
.toJson();
m_pendingWrite = std::nullopt;
const auto dir = QFileInfo(m_path).absolutePath();
const auto dir = QFileInfo(m_path).absolutePath();
if (!QDir().mkpath(dir)) {
qCWarning(lcSettingsFile) << "Failed to create dir" << dir;
emit writeFailed(QStringLiteral("Failed to create parent directory"));
return;
}
if (!QDir().mkpath(dir)) {
qCWarning(lcSettingsFile) << "Failed to create dir" << dir;
emit writeFailed(QStringLiteral("Failed to create parent directory"));
return;
}
// Write atomically
QSaveFile file(m_path);
// Write atomically
QSaveFile file(m_path);
if (!file.open(QIODevice::WriteOnly)) {
qCWarning(lcSettingsFile, "Failed to open %s for writing: %s", qUtf8Printable(m_path),
qUtf8Printable(file.errorString()));
emit writeFailed(QStringLiteral("Failed to open: %1").arg(file.errorString()));
return;
}
if (!file.open(QIODevice::WriteOnly)) {
qCWarning(
lcSettingsFile,
"Failed to open %s for writing: %s",
qUtf8Printable(m_path),
qUtf8Printable(file.errorString()));
emit writeFailed(
QStringLiteral("Failed to open: %1").arg(file.errorString()));
return;
}
file.write(data);
file.write(data);
if (!file.commit()) {
qCWarning(lcSettingsFile, "Failed to write %s: %s", qUtf8Printable(m_path), qUtf8Printable(file.errorString()));
emit writeFailed(QStringLiteral("Failed to write: %1").arg(file.errorString()));
return;
}
if (!file.commit()) {
qCWarning(
lcSettingsFile,
"Failed to write %s: %s",
qUtf8Printable(m_path),
qUtf8Printable(file.errorString()));
emit writeFailed(
QStringLiteral("Failed to write: %1").arg(file.errorString()));
return;
}
m_lastData = json;
qCDebug(lcSettingsFile) << "Saved" << m_path;
m_lastData = json;
qCDebug(lcSettingsFile) << "Saved" << m_path;
initWatcher(); // The file may have just been created, or replaced by the rename
initWatcher(); // The file may have just been created, or replaced by the rename
}
} // namespace ZShell::settings