From: Shane Synan Date: Mon, 27 Jun 2016 23:54:21 +0000 (-0400) Subject: Settings upgrade logic, classic for old installs X-Git-Tag: travis-deploy-test~434 X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=commitdiff_plain;h=d2ac8f78a0e050d2efa397c434b249d6b3391576 Settings upgrade logic, classic for old installs Add "VersionMinor" to Settings for backwards/forwards-compatible changes. Existing installs without this setting are distinguished by checking if other settings keys exist. Add upgrade logic to QtUiApplication to preserve previous settings when changing defaults. This avoids unwanted surprises if someone likes the defaults, doesn't save them, then upgrades their client. Otherwise, Qt would only save settings when changed. (Core currently does not need version-compatible settings migration logic, but it can be added in the future if needed.) Add a method to settings to check if a key exists, used when checking if a non-default value was chosen. Upgrading settings to version 2 will preserve Quassel's UI for those with the classic look, but new installs default to modern style. In the future, this can be used to implement any other default changes. --- diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 8756ca6a..f0defb8d 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -22,7 +22,12 @@ #include "settings.h" -const int VERSION = 1; +const int VERSION = 1; /// Settings version for backwords/forwards incompatible changes + +// This is used if no VersionMinor key exists, e.g. upgrading from a Quassel version before this +// change. This shouldn't be increased from 1; instead, change the logic in Core::Core() and +// QtUiApplication::init() to handle upgrading and downgrading. +const int VERSION_MINOR_INITIAL = 1; /// Initial settings version for compatible changes QHash Settings::settingsCache; QHash Settings::settingsChangeNotifier; @@ -86,6 +91,36 @@ uint Settings::version() } +uint Settings::versionMinor() +{ + // Don't cache this value; ignore the group + create_qsettings; + // '0' means new configuration, anything else indicates an existing configuration. Application + // initialization should check this value and manage upgrades/downgrades, e.g. in Core::Core() + // and QtUiApplication::init(). + uint verMinor = s.value("Config/VersionMinor", 0).toUInt(); + + // As previous Quassel versions didn't implement this, we need to check if any settings other + // than Config/Version exist. If so, assume it's version 1. + if (verMinor == 0 && s.allKeys().count() > 1) { + // More than 1 key exists, but version's never been set. Assume and set version 1. + setVersionMinor(VERSION_MINOR_INITIAL); + return VERSION_MINOR_INITIAL; + } else { + return verMinor; + } +} + + +void Settings::setVersionMinor(const uint versionMinor) +{ + // Don't cache this value; ignore the group + create_qsettings; + // Set the value directly. + s.setValue("Config/VersionMinor", versionMinor); +} + + QStringList Settings::allLocalKeys() { create_qsettings; @@ -150,6 +185,16 @@ const QVariant &Settings::localValue(const QString &key, const QVariant &def) return cacheValue(normKey); } +bool Settings::localKeyExists(const QString &key) +{ + QString normKey = normalizedKey(group, key); + if (isCached(normKey)) + return true; + + create_qsettings; + return s.contains(normKey); +} + void Settings::removeLocalKey(const QString &key) { diff --git a/src/common/settings.h b/src/common/settings.h index ef302c17..e9bf28d0 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -53,8 +53,36 @@ public: //! Sets up notification and calls the given slot to set the initial value void initAndNotify(const QString &key, QObject *receiver, const char *slot, const QVariant &defaultValue = QVariant()); + /** + * Get the major configuration version + * + * This indicates the backwards/forwards incompatible version of configuration. + * + * @return Major configuration version (the X in XX.YY) + */ virtual uint version(); + /** + * Get the minor configuration version + * + * This indicates the backwards/forwards compatible version of configuration. + * + * @see Settings::setVersionMinor() + * @return Minor configuration version (the Y in XX.YY) + */ + virtual uint versionMinor(); + + /** + * Set the minor configuration version + * + * When making backwards/forwards compatible changes, call this with the new version number. + * This does not implement any upgrade logic; implement that when checking Settings::version(), + * e.g. in Core::Core() and QtUiApplication::init(). + * + * @param[in] versionMinor New minor version number + */ + virtual void setVersionMinor(const uint versionMinor); + protected: inline Settings(QString group_, QString appName_) : group(group_), appName(appName_) {} inline virtual ~Settings() {} @@ -68,6 +96,14 @@ protected: virtual void setLocalValue(const QString &key, const QVariant &data); virtual const QVariant &localValue(const QString &key, const QVariant &def = QVariant()); + /** + * Gets if a key exists in settings + * + * @param[in] key ID of local settings key + * @returns True if key exists in settings, otherwise false + */ + virtual bool localKeyExists(const QString &key); + virtual void removeLocalKey(const QString &key); QString group; diff --git a/src/qtui/qtuiapplication.cpp b/src/qtui/qtuiapplication.cpp index a05ce94b..755a0326 100644 --- a/src/qtui/qtuiapplication.cpp +++ b/src/qtui/qtuiapplication.cpp @@ -139,11 +139,9 @@ bool QtUiApplication::init() // MIGRATION end - // check settings version - // so far, we only have 1 - QtUiSettings s; - if (s.version() != 1) { - qCritical() << "Invalid client settings version, terminating!"; + // Settings upgrade/downgrade handling + if (!migrateSettings()) { + qCritical() << "Could not load or upgrade client settings, terminating!"; return false; } @@ -179,6 +177,133 @@ void QtUiApplication::quit() } +bool QtUiApplication::migrateSettings() +{ + // -------- + // Check major settings version. This represents incompatible changes between settings + // versions. So far, we only have 1. + QtUiSettings s; + uint versionMajor = s.version(); + if (versionMajor != 1) { + qCritical() << qPrintable(QString("Invalid client settings version '%1'") + .arg(versionMajor)); + return false; + } + + // -------- + // Check minor settings version, handling upgrades/downgrades as needed + // Current minor version + const uint VERSION_MINOR_CURRENT = 2; + // Stored minor version + uint versionMinor = s.versionMinor(); + + if (versionMinor == VERSION_MINOR_CURRENT) { + // At latest version, no need to migrate defaults or other settings + return true; + } else if (versionMinor == 0) { + // New configuration, store as current version + qDebug() << qPrintable(QString("Set up new client settings v%1.%2") + .arg(versionMajor).arg(VERSION_MINOR_CURRENT)); + s.setVersionMinor(VERSION_MINOR_CURRENT); + + // Update the settings stylesheet for first setup. We don't know if older content exists, + // if the configuration got erased separately, etc. + QtUiStyle qtUiStyle; + qtUiStyle.generateSettingsQss(); + return true; + } else if (versionMinor < VERSION_MINOR_CURRENT) { + // We're upgrading - apply the neccessary upgrades from each interim version + // curVersion will never equal VERSION_MINOR_CURRENT, as it represents the version before + // the most recent applySettingsMigration() call. + for (uint curVersion = versionMinor; curVersion < VERSION_MINOR_CURRENT; curVersion++) { + if (!applySettingsMigration(s, curVersion + 1)) { + // Something went wrong, time to bail out + qCritical() << qPrintable(QString("Could not migrate client settings from v%1.%2 " + "to v%1.%3") + .arg(versionMajor).arg(curVersion).arg(curVersion + 1)); + // Keep track of the last successful upgrade to avoid repeating it on next start + s.setVersionMinor(curVersion); + return false; + } + } + // Migration successful! + qDebug() << qPrintable(QString("Successfully migrated client settings from v%1.%2 to " + "v%1.%3") + .arg(versionMajor).arg(versionMinor).arg(VERSION_MINOR_CURRENT)); + // Store the new minor version + s.setVersionMinor(VERSION_MINOR_CURRENT); + return true; + } else { + // versionMinor > VERSION_MINOR_CURRENT + // The user downgraded to an older version of Quassel. Let's hope for the best. + // Don't change the minorVersion as the newer version's upgrade logic has already run. + qWarning() << qPrintable(QString("Client settings v%1.%2 is newer than latest known v%1.%3," + " things might not work!") + .arg(versionMajor).arg(versionMinor).arg(VERSION_MINOR_CURRENT)); + return true; + } +} + + +bool QtUiApplication::applySettingsMigration(QtUiSettings settings, const uint newVersion) +{ + switch (newVersion) { + // Version 0 and 1 aren't valid upgrade paths - one represents no version, the other is the + // oldest version. Ignore those, start from 2 and higher. + // Each missed version will be called in sequence. E.g. to upgrade from '1' to '3', this + // function will be called with '2', then '3'. + case 2: + { + // Use explicit scope via { ... } to avoid cross-initialization + + // New default changes: sender brackets disabled, sender colors and sender CTCP + // colors enabled. Preserve the older default values for keys that haven't been saved. + + // -------- + // ChatView settings + const QString timestampFormatId = "ChatView/__default__/TimestampFormat"; + if (!settings.valueExists(timestampFormatId)) { + // New default value is " hh:mm:ss", preserve old default of "[hh:mm:ss]" + settings.setValue(timestampFormatId, "[hh:mm:ss]"); + } + + const QString showSenderBracketsId = "ChatView/__default__/ShowSenderBrackets"; + if (!settings.valueExists(showSenderBracketsId)) { + // New default is false, preserve previous behavior by setting to true + settings.setValue(showSenderBracketsId, true); + } + // -------- + + // -------- + // QtUiStyle settings + QtUiStyleSettings settingsUiStyleColors("Colors"); + const QString useSenderColorsId = "UseSenderColors"; + if (!settingsUiStyleColors.valueExists(useSenderColorsId)) { + // New default is true, preserve previous behavior by setting to false + settingsUiStyleColors.setValue(useSenderColorsId, false); + } + + const QString useSenderActionColorsId = "UseSenderActionColors"; + if (!settingsUiStyleColors.valueExists(useSenderActionColorsId)) { + // New default is true, preserve previous behavior by setting to false + settingsUiStyleColors.setValue(useSenderActionColorsId, false); + } + + // Update the settings stylesheet with old defaults + QtUiStyle qtUiStyle; + qtUiStyle.generateSettingsQss(); + // -------- + + // Migration complete! + return true; + } + default: + // Something unexpected happened + return false; + } +} + + void QtUiApplication::commitData(QSessionManager &manager) { Q_UNUSED(manager) diff --git a/src/qtui/qtuiapplication.h b/src/qtui/qtuiapplication.h index 773bd748..e5e5d078 100644 --- a/src/qtui/qtuiapplication.h +++ b/src/qtui/qtuiapplication.h @@ -31,6 +31,7 @@ #include "quassel.h" #include "uisettings.h" +#include "qtuisettings.h" class QtUi; @@ -64,6 +65,28 @@ protected: virtual void quit(); private: + /** + * Migrate settings if neccessary and possible + * + * If unsuccessful (major version changed, minor version upgrade failed), returning false, the + * settings are in an unknown state and the client should quit. + * + * @return True if settings successfully migrated, otherwise false + */ + bool migrateSettings(); + + /** + * Migrate from one minor settings version to the next + * + * Settings can only be migrated one version at a time. Start from the current version, calling + * this function for each intermediate version up until the latest version. + * + * @param[in] settings Current settings instance + * @param[in] newVersion Next target version for migration, at most 1 from the current version + * @return True if minor revision of settings successfully migrated, otherwise false + */ + bool applySettingsMigration(QtUiSettings settings, const uint newVersion); + bool _aboutToQuit; }; diff --git a/src/uisupport/uisettings.h b/src/uisupport/uisettings.h index c0b83527..dccae38f 100644 --- a/src/uisupport/uisettings.h +++ b/src/uisupport/uisettings.h @@ -32,6 +32,14 @@ public: virtual inline void setValue(const QString &key, const QVariant &data) { setLocalValue(key, data); } virtual inline QVariant value(const QString &key, const QVariant &def = QVariant()) { return localValue(key, def); } + /** + * Gets if a value exists in settings + * + * @param[in] key ID of local settings key + * @returns True if key exists in settings, otherwise false + */ + virtual inline bool valueExists(const QString &key) { return localKeyExists(key); } + inline void remove(const QString &key) { removeLocalKey(key); } };