From dbbd1101fc823ff832152d0c936cf39863bd8666 Mon Sep 17 00:00:00 2001 From: Alexandre Petitjean Date: Wed, 26 Jun 2024 00:13:25 +0200 Subject: [PATCH] Improve settings handling --- src/gui/mainWindow.cpp | 44 +++---- src/gui/settingsDialog.cpp | 2 +- src/main.cpp | 4 +- src/monitor/kimaiEventsMonitor.cpp | 2 +- src/settings/settings.cpp | 189 ++++++++++++++--------------- src/settings/settings.h | 20 ++- 6 files changed, 133 insertions(+), 128 deletions(-) diff --git a/src/gui/mainWindow.cpp b/src/gui/mainWindow.cpp index 35cb130..cc8ad8b 100644 --- a/src/gui/mainWindow.cpp +++ b/src/gui/mainWindow.cpp @@ -31,7 +31,7 @@ MainWindow::MainWindow() : mUi(std::make_unique()) { mUi->setupUi(this); - auto settings = Settings::get(); + const auto& settings = SettingsHandler::instance().get(); /* * Setup icon @@ -163,7 +163,7 @@ MainWindow::MainWindow() : mUi(std::make_unique()) if (settings.kemai.checkUpdateAtStartup) { QTimer::singleShot(FirstRequestDelayMs, [&]() { - auto ignoreVersion = QVersionNumber::fromString(Settings::get().kemai.ignoredVersion); + auto ignoreVersion = QVersionNumber::fromString(SettingsHandler::instance().get().kemai.ignoredVersion); auto currentVersion = QVersionNumber::fromString(KEMAI_VERSION); mUpdater.checkAvailableNewVersion(currentVersion >= ignoreVersion ? currentVersion : ignoreVersion, true); }); @@ -188,19 +188,19 @@ void MainWindow::closeEvent(QCloseEvent* event) mLoggerWidget.close(); } - auto settings = Settings::get(); + auto settings = SettingsHandler::instance().get(); if (settings.kemai.closeToSystemTray) { hide(); event->ignore(); } settings.kemai.geometry = saveGeometry(); - Settings::save(settings); + SettingsHandler::instance().set(settings); } void MainWindow::hideEvent(QHideEvent* event) { - auto settings = Settings::get(); + auto settings = SettingsHandler::instance().get(); if (settings.kemai.minimizeToSystemTray) { if (event->spontaneous() && isMinimized()) @@ -210,7 +210,7 @@ void MainWindow::hideEvent(QHideEvent* event) } } settings.kemai.geometry = saveGeometry(); - Settings::save(settings); + SettingsHandler::instance().set(settings); } void MainWindow::createKemaiSession(const Settings::Profile& profile) @@ -229,8 +229,8 @@ void MainWindow::createKemaiSession(const Settings::Profile& profile) mStatusInstanceLabel.setText(tr("Not connected")); } - auto settings = Settings::get(); - if (settings.isReady()) + auto settings = SettingsHandler::instance().get(); + if (settings.hasValidProfile()) { auto kimaiClient = std::make_shared(); @@ -251,7 +251,7 @@ void MainWindow::createKemaiSession(const Settings::Profile& profile) // Save profile connection settings.kemai.lastConnectedProfile = profile.id; - Settings::save(settings); + SettingsHandler::instance().set(settings); } } @@ -283,7 +283,7 @@ void MainWindow::setViewActionsEnabled(bool enable) void MainWindow::updateProfilesMenu() { - auto settings = Settings::get(); + const auto& settings = SettingsHandler::instance().get(); // Removes obsoletes profiles for (auto action : mActGroupProfiles->actions()) @@ -318,8 +318,8 @@ void MainWindow::updateProfilesMenu() void MainWindow::processAutoConnect() { - auto settings = Settings::get(); - if (settings.profiles.isEmpty()) + const auto& settings = SettingsHandler::instance().get(); + if (settings.profiles.empty()) { return; } @@ -387,10 +387,10 @@ void MainWindow::onSessionVersionChanged() void MainWindow::onActionSettingsTriggered() { SettingsDialog settingsDialog(mDesktopEventsMonitor, this); - settingsDialog.setSettings(Settings::get()); + settingsDialog.setSettings(SettingsHandler::instance().get()); if (settingsDialog.exec() == QDialog::Accepted) { - Settings::save(settingsDialog.settings()); + SettingsHandler::instance().set(settingsDialog.settings()); showSelectedView(); updateProfilesMenu(); @@ -410,10 +410,10 @@ void MainWindow::onActionCheckUpdateTriggered() void MainWindow::onActionOpenHostTriggered() { - auto settings = Settings::get(); - if (settings.isReady()) + const auto& settings = SettingsHandler::instance().get(); + if (settings.hasValidProfile()) { - QDesktopServices::openUrl(QUrl::fromUserInput(settings.profiles.first().host)); + QDesktopServices::openUrl(QUrl::fromUserInput(settings.profiles.front().host)); } } @@ -436,7 +436,7 @@ void MainWindow::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) switch (reason) { case QSystemTrayIcon::Trigger: { - auto settings = Settings::get(); + const auto& settings = SettingsHandler::instance().get(); if (isVisible() && (settings.kemai.minimizeToSystemTray || settings.kemai.closeToSystemTray)) { hide(); @@ -472,9 +472,9 @@ void MainWindow::onNewVersionCheckFinished(const VersionDetails& details) break; case QMessageBox::Ignore: { - auto settings = Settings::get(); + auto settings = SettingsHandler::instance().get(); settings.kemai.ignoredVersion = details.vn.toString(); - Settings::save(settings); + SettingsHandler::instance().set(settings); } break; @@ -508,7 +508,7 @@ void MainWindow::onProfilesActionGroupTriggered(QAction* action) { if (action->isChecked()) { - auto settings = Settings::get(); + auto settings = SettingsHandler::instance().get(); auto profileId = action->data().toUuid(); auto profile = settings.findProfile(profileId); if (profile.has_value()) @@ -521,7 +521,7 @@ void MainWindow::onProfilesActionGroupTriggered(QAction* action) void MainWindow::onDesktopIdleDetected() { - spdlog::info("System is idle since {} minutes. Stop current TimeSheet.", Settings::get().events.idleDelayMinutes); + spdlog::info("System is idle since {} minutes. Stop current TimeSheet.", SettingsHandler::instance().get().events.idleDelayMinutes); mActivityWidget->stopCurrentTimeSheet(); } diff --git a/src/gui/settingsDialog.cpp b/src/gui/settingsDialog.cpp index 5af61a2..65328bd 100644 --- a/src/gui/settingsDialog.cpp +++ b/src/gui/settingsDialog.cpp @@ -76,7 +76,7 @@ SettingsDialog::SettingsDialog(const std::shared_ptr& desk // show dialog if language changes from settings connect(mUi->cbLanguage, &QComboBox::currentTextChanged, [&](const QString&) { - auto settings = Settings::get(); + const auto& settings = SettingsHandler::instance().get(); if (settings.kemai.language != mUi->cbLanguage->currentData().toLocale()) { QMessageBox::warning(this, tr(""), tr("Language changed. Application restart is required.")); diff --git a/src/main.cpp b/src/main.cpp index 9fbcbd3..d5a2f53 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,7 +55,7 @@ int main(int argc, char* argv[]) QApplication::setApplicationVersion(KEMAI_VERSION); // Get kemai data directory and log file path - auto kemaiSettings = Settings::get(); + const auto& kemaiSettings = SettingsHandler::instance().get(); // Create Qt logger model before spdlog sinks auto loggerTreeModel = std::make_shared(); @@ -64,7 +64,7 @@ int main(int argc, char* argv[]) std::vector sinks; sinks.emplace_back(std::make_shared()); sinks.emplace_back(std::make_shared(loggerTreeModel)); - + if (!QDir(helpers::getLogDirPath()).mkpath(".")) // Ensure log dir exists before adding sink { sinks.emplace_back(std::make_shared(helpers::getLogFilePath().toStdString(), MaxLogFileSize, 3)); diff --git a/src/monitor/kimaiEventsMonitor.cpp b/src/monitor/kimaiEventsMonitor.cpp index 8536099..15f267c 100644 --- a/src/monitor/kimaiEventsMonitor.cpp +++ b/src/monitor/kimaiEventsMonitor.cpp @@ -37,7 +37,7 @@ bool KimaiEventsMonitor::hasCurrentTimeSheet() const void KimaiEventsMonitor::onSecondTimeout() { - auto settings = Settings::get(); + const auto& settings = SettingsHandler::instance().get(); if (settings.events.autoRefreshCurrentTimeSheet && mLastTimeSheetUpdate.has_value()) { if (mLastTimeSheetUpdate->secsTo(QDateTime::currentDateTime()) >= settings.events.autoRefreshCurrentTimeSheetDelaySeconds) diff --git a/src/settings/settings.cpp b/src/settings/settings.cpp index 4180101..e1fe10b 100644 --- a/src/settings/settings.cpp +++ b/src/settings/settings.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -22,12 +21,6 @@ static const auto gCfgVersion_2 = 2; // Add AutoRefreshCurrentTimeSheet set static const auto gCfgVersion_3 = 3; // Add API Token to profile static const auto gCurrentCfgVersion = gCfgVersion_3; -/* - * Global settings instance to avoid json reload - */ -static QMutex gSettingsMutex; -static std::optional gSettings = std::nullopt; - /* * Static helpers */ @@ -41,7 +34,7 @@ QString getJsonSettingsPath() return QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)).absoluteFilePath("settings.json"); } -void migrateIniToJson(const QSettings& qset) +Settings loadFromLegacyIni(const QSettings& qset) { Settings settings; @@ -73,22 +66,21 @@ void migrateIniToJson(const QSettings& qset) settings.kemai.geometry = qset.value(kemaiGrpPrefix + "geometry").toByteArray(); settings.kemai.language = qset.value(kemaiGrpPrefix + "language", QLocale::system()).toLocale(); - // Save to json format - Settings::save(settings); + return settings; } /* * Class impl */ -bool Settings::isReady() const +bool Settings::hasValidProfile() const { - if (!profiles.isEmpty()) + if (!profiles.empty()) { - const auto profile = profiles.first(); - const auto haveHost = !profile.host.isEmpty(); - const auto haveLegacyAuth = !profile.username.isEmpty() && !profile.token.isEmpty(); - const auto haveAPIToken = !profile.apiToken.isEmpty(); + const auto profile = profiles.begin(); + const auto haveHost = !profile->host.isEmpty(); + const auto haveLegacyAuth = !profile->username.isEmpty() && !profile->token.isEmpty(); + const auto haveAPIToken = !profile->apiToken.isEmpty(); return haveHost && (haveLegacyAuth || haveAPIToken); } @@ -100,7 +92,12 @@ QList::iterator Settings::findProfileIt(const QUuid& profileI return std::find_if(profiles.begin(), profiles.end(), [&profileId](const auto& profile) { return profile.id == profileId; }); } -std::optional Settings::findProfile(const QUuid& profileId) +QList::const_iterator Settings::findProfileIt(const QUuid& profileId) const +{ + return std::find_if(profiles.begin(), profiles.end(), [&profileId](const auto& profile) { return profile.id == profileId; }); +} + +std::optional Settings::findProfile(const QUuid& profileId) const { auto it = findProfileIt(profileId); if (it != profiles.end()) @@ -110,96 +107,96 @@ std::optional Settings::findProfile(const QUuid& profileId) return std::nullopt; } -Settings Settings::get() +SettingsHandler& SettingsHandler::instance() { - QMutexLocker lock(&gSettingsMutex); - if (gSettings.has_value()) - { - return gSettings.value(); - } - - auto qset = getQSettingsInstance(); - auto jsonSettingsPath = getJsonSettingsPath(); - - // Migrate from ini settings to json - if (QFile::exists(qset.fileName()) && !QFile::exists(jsonSettingsPath)) - { - migrateIniToJson(qset); - } - - // First run - if (!QFile::exists(jsonSettingsPath)) - { - return {}; - } - - QFile jsonFile(jsonSettingsPath); - jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); - auto jsonDocument = QJsonDocument::fromJson(jsonFile.readAll()); - auto root = jsonDocument.object(); - jsonFile.close(); - - gSettings = Settings{}; - - const auto cfgVersion = root.value("version").toInt(); - - for (const auto& certificationValue : root.value("trustedCertificates").toArray()) - { - gSettings->trustedCertificates.append(certificationValue.toString()); - } - - auto kemaiObject = root.value("kemai").toObject(); - gSettings->kemai.closeToSystemTray = kemaiObject.value("closeToSystemTray").toBool(); - gSettings->kemai.minimizeToSystemTray = kemaiObject.value("minimizeToSystemTray").toBool(); - gSettings->kemai.checkUpdateAtStartup = kemaiObject.value("checkUpdateAtStartup").toBool(); - gSettings->kemai.ignoredVersion = kemaiObject.value("ignoredVersion").toString(); - gSettings->kemai.geometry = QByteArray::fromBase64(kemaiObject.value("geometry").toString().toLocal8Bit()); - gSettings->kemai.language = QLocale(kemaiObject.value("language").toString()); - if (kemaiObject.contains("lastConnectedProfile")) - { - gSettings->kemai.lastConnectedProfile = QUuid(kemaiObject.value("lastConnectedProfile").toString()); - } + static SettingsHandler gSettingsHandler; + return gSettingsHandler; +} - for (const auto& profileValue : root.value("profiles").toArray()) +const Settings& SettingsHandler::get() +{ + if (!mSettings.has_value()) { - const auto profileObject = profileValue.toObject(); + mSettings = Settings{}; - Settings::Profile profile; - profile.id = QUuid(profileObject.value("id").toString()); - profile.name = profileObject.value("name").toString(); - profile.host = profileObject.value("host").toString(); - profile.username = profileObject.value("username").toString(); - profile.token = profileObject.value("token").toString(); + auto qset = getQSettingsInstance(); + auto jsonSettingsPath = getJsonSettingsPath(); - if (cfgVersion >= gCfgVersion_3) + // Migrate from ini settings to json + if (QFile::exists(qset.fileName()) && !QFile::exists(jsonSettingsPath)) { - profile.apiToken = profileObject.value("apiToken").toString(); + auto settings = loadFromLegacyIni(qset); + set(settings); + return mSettings.value(); } - gSettings->profiles << profile; - } - - if (root.contains("events")) - { - auto eventsObject = root.value("events").toObject(); - gSettings->events.stopOnLock = eventsObject.value("stopOnLock").toBool(); - gSettings->events.stopOnIdle = eventsObject.value("stopOnIdle").toBool(); - gSettings->events.idleDelayMinutes = eventsObject.value("idleDelayMinutes").toInt(); - - if (cfgVersion >= gCfgVersion_2) + // Load from previous save + if (QFile::exists(jsonSettingsPath)) { - gSettings->events.autoRefreshCurrentTimeSheet = eventsObject.value("autoRefreshCurrentTimeSheet").toBool(); - gSettings->events.autoRefreshCurrentTimeSheetDelaySeconds = eventsObject.value("autoRefreshCurrentTimeSheetDelaySeconds").toInt(); + QFile jsonFile(jsonSettingsPath); + jsonFile.open(QIODevice::ReadOnly | QIODevice::Text); + auto jsonDocument = QJsonDocument::fromJson(jsonFile.readAll()); + auto root = jsonDocument.object(); + jsonFile.close(); + + const auto cfgVersion = root.value("version").toInt(); + + for (const auto& certificationValue : root.value("trustedCertificates").toArray()) + { + mSettings->trustedCertificates.append(certificationValue.toString()); + } + + auto kemaiObject = root.value("kemai").toObject(); + mSettings->kemai.closeToSystemTray = kemaiObject.value("closeToSystemTray").toBool(); + mSettings->kemai.minimizeToSystemTray = kemaiObject.value("minimizeToSystemTray").toBool(); + mSettings->kemai.checkUpdateAtStartup = kemaiObject.value("checkUpdateAtStartup").toBool(); + mSettings->kemai.ignoredVersion = kemaiObject.value("ignoredVersion").toString(); + mSettings->kemai.geometry = QByteArray::fromBase64(kemaiObject.value("geometry").toString().toLocal8Bit()); + mSettings->kemai.language = QLocale(kemaiObject.value("language").toString()); + if (kemaiObject.contains("lastConnectedProfile")) + { + mSettings->kemai.lastConnectedProfile = QUuid(kemaiObject.value("lastConnectedProfile").toString()); + } + + for (const auto& profileValue : root.value("profiles").toArray()) + { + const auto profileObject = profileValue.toObject(); + + Settings::Profile profile; + profile.id = QUuid(profileObject.value("id").toString()); + profile.name = profileObject.value("name").toString(); + profile.host = profileObject.value("host").toString(); + profile.username = profileObject.value("username").toString(); + profile.token = profileObject.value("token").toString(); + + if (cfgVersion >= gCfgVersion_3) + { + profile.apiToken = profileObject.value("apiToken").toString(); + } + + mSettings->profiles << profile; + } + + if (root.contains("events")) + { + auto eventsObject = root.value("events").toObject(); + mSettings->events.stopOnLock = eventsObject.value("stopOnLock").toBool(); + mSettings->events.stopOnIdle = eventsObject.value("stopOnIdle").toBool(); + mSettings->events.idleDelayMinutes = eventsObject.value("idleDelayMinutes").toInt(); + + if (cfgVersion >= gCfgVersion_2) + { + mSettings->events.autoRefreshCurrentTimeSheet = eventsObject.value("autoRefreshCurrentTimeSheet").toBool(); + mSettings->events.autoRefreshCurrentTimeSheetDelaySeconds = eventsObject.value("autoRefreshCurrentTimeSheetDelaySeconds").toInt(); + } + } } } - - return gSettings.value(); + return mSettings.value(); } -void Settings::save(const Settings& settings) +void SettingsHandler::set(const Settings& settings) { - QMutexLocker lock(&gSettingsMutex); - QJsonArray profilesArray; for (const auto& profile : settings.profiles) { @@ -236,8 +233,6 @@ void Settings::save(const Settings& settings) root["kemai"] = kemaiObject; root["events"] = eventsObject; - QJsonDocument jsonDocument(root); - QFileInfo jsonFileInfo(getJsonSettingsPath()); if (!jsonFileInfo.exists()) { @@ -251,9 +246,9 @@ void Settings::save(const Settings& settings) jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream testStream(&jsonFile); - testStream << jsonDocument.toJson(); + testStream << QJsonDocument(root).toJson(); jsonFile.close(); - gSettings = settings; + mSettings = settings; } diff --git a/src/settings/settings.h b/src/settings/settings.h index 4d8cece..c61b40f 100644 --- a/src/settings/settings.h +++ b/src/settings/settings.h @@ -43,13 +43,23 @@ struct Settings int autoRefreshCurrentTimeSheetDelaySeconds = 5; } events; - bool isReady() const; + bool hasValidProfile() const; - QList::iterator findProfileIt(const QUuid& profileId); - std::optional findProfile(const QUuid& profileId); + QList::iterator findProfileIt(const QUuid& profileId); + QList::const_iterator findProfileIt(const QUuid& profileId) const; + std::optional findProfile(const QUuid& profileId) const; +}; + +class SettingsHandler +{ +public: + static SettingsHandler& instance(); + + const Settings& get(); + void set(const Settings& settings); - static Settings get(); - static void save(const Settings& settings); +private: + std::optional mSettings = std::nullopt; }; } // namespace kemai