fix: define declared method + insert new bar entries to config file list + rename Hyprsunset -> Nightlight
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user