diff --git a/bin/app/user/ApplicationSettings_for_package.xml b/bin/app/user/ApplicationSettings_for_package.xml index 50eeeec9..885046b5 100644 --- a/bin/app/user/ApplicationSettings_for_package.xml +++ b/bin/app/user/ApplicationSettings_for_package.xml @@ -1,14 +1,17 @@ - - 4 - 500 - 500 - - - - ./data/projects/tutorial/tutorial.coatiproject - ./data/projects/tictactoe/tictactoe.coatiproject - - + + 500 + 500 + + + 4 + + + + ./data/projects/tutorial/tutorial.coatiproject + ./data/projects/tictactoe/tictactoe.coatiproject + + + 1 diff --git a/bin/app/user/ApplicationSettings_template.xml b/bin/app/user/ApplicationSettings_template.xml index 8611acb8..3029b232 100644 --- a/bin/app/user/ApplicationSettings_template.xml +++ b/bin/app/user/ApplicationSettings_template.xml @@ -7,6 +7,8 @@ + + diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 72534d12..42c1d9d8 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -207,6 +207,8 @@ add_files( settings/ProjectSettings.h settings/Settings.cpp settings/Settings.h + settings/SettingsMigrator.cpp + settings/SettingsMigrator.h utility/file/FileInfo.cpp utility/file/FileInfo.h diff --git a/src/lib/settings/ApplicationSettings.cpp b/src/lib/settings/ApplicationSettings.cpp index 67686a29..28e67e9a 100644 --- a/src/lib/settings/ApplicationSettings.cpp +++ b/src/lib/settings/ApplicationSettings.cpp @@ -3,6 +3,8 @@ #include "utility/ResourcePaths.h" #include "utility/utility.h" +const size_t ApplicationSettings::VERSION = 1; + std::shared_ptr ApplicationSettings::s_instance; std::shared_ptr ApplicationSettings::getInstance() @@ -23,6 +25,36 @@ ApplicationSettings::~ApplicationSettings() { } +bool ApplicationSettings::load(const FilePath& filePath) +{ + bool loaded = Settings::load(filePath); + if (!loaded) + { + return false; + } + + SettingsMigrator migrator; + + migrator.addMigration(1, + "source/header_search_paths/header_search_path", + "indexing/cxx/header_search_paths/header_search_path"); + migrator.addMigration(1, + "source/framework_search_paths/framework_search_path", + "indexing/cxx/framework_search_paths/framework_search_path"); + migrator.addMigration(1, + "application/indexer_thread_count", + "indexing/indexer_thread_count"); + + bool migrated = migrator.migrate(this, ApplicationSettings::VERSION); + + if (migrated) + { + save(); + } + + return true; +} + bool ApplicationSettings::operator==(const ApplicationSettings& other) const { return diff --git a/src/lib/settings/ApplicationSettings.h b/src/lib/settings/ApplicationSettings.h index a2b2a769..73eda20f 100644 --- a/src/lib/settings/ApplicationSettings.h +++ b/src/lib/settings/ApplicationSettings.h @@ -10,9 +10,14 @@ class ApplicationSettings { public: static std::shared_ptr getInstance(); + + static const size_t VERSION; + ApplicationSettings(); ~ApplicationSettings(); + bool load(const FilePath& filePath); + bool operator==(const ApplicationSettings& other) const; int getMaxRecentProjectsCount() const; diff --git a/src/lib/settings/Settings.cpp b/src/lib/settings/Settings.cpp index 43803967..12a008e6 100644 --- a/src/lib/settings/Settings.cpp +++ b/src/lib/settings/Settings.cpp @@ -37,6 +37,7 @@ bool Settings::load(const FilePath& filePath) else { clear(); + m_filePath = filePath; LOG_WARNING("File for Settings not found: " + filePath.str()); return false; } @@ -44,7 +45,7 @@ bool Settings::load(const FilePath& filePath) void Settings::save() { - if (m_config.get() && m_filePath.exists()) + if (m_config.get() && !m_filePath.empty()) { m_config->save(m_filePath.str()); } @@ -58,14 +59,7 @@ void Settings::save(const FilePath& filePath) { setFilePath(filePath); - if (m_config) - { - m_config->save(filePath.str()); - } - else - { - LOG_WARNING("Settings were not saved: " + filePath.str()); - } + save(); } void Settings::clear() @@ -79,6 +73,33 @@ const FilePath& Settings::getFilePath() const return m_filePath; } +size_t Settings::getVersion() const +{ + return getValue("version", 0); +} + +void Settings::setVersion(size_t version) +{ + setValue("version", version); +} + +Settings Settings::createFromText(const std::shared_ptr textAccess) +{ + Settings settings; + settings.m_config = ConfigManager::createAndLoad(textAccess); + return settings; +} + +std::string Settings::getAsText() const +{ + if (m_config) + { + return m_config->toString(); + } + + return ""; +} + Settings::Settings() { clear(); @@ -126,6 +147,11 @@ bool Settings::isValueDefined(const std::string& key) const return m_config->isValueDefined(key); } +void Settings::removeValues(const std::string& key) +{ + m_config->removeValues(key); +} + void Settings::enableWarnings() const { m_config->setWarnOnEmptyKey(true); diff --git a/src/lib/settings/Settings.h b/src/lib/settings/Settings.h index df05bc8c..1ab55a5a 100644 --- a/src/lib/settings/Settings.h +++ b/src/lib/settings/Settings.h @@ -8,6 +8,8 @@ #include "utility/ConfigManager.h" #include "utility/file/FilePath.h" +#include "settings/SettingsMigrator.h" + class Settings { public: @@ -15,14 +17,20 @@ public: Settings& operator=(const Settings& other); virtual ~Settings(); - virtual bool load(const FilePath& filePath); - virtual void save(); - virtual void save(const FilePath& filePath); + bool load(const FilePath& filePath); + void save(); + void save(const FilePath& filePath); void clear(); virtual const FilePath& getFilePath() const; + size_t getVersion() const; + void setVersion(size_t version); + + static Settings createFromText(const std::shared_ptr textAccess); + std::string getAsText() const; + protected: Settings(); @@ -47,9 +55,13 @@ protected: bool isValueDefined(const std::string& key) const; + void removeValues(const std::string& key); + void enableWarnings() const; void disableWarnings() const; + friend bool SettingsMigrator::migrate(Settings* settings, size_t targetVersion) const; + private: FilePath m_filePath; std::shared_ptr m_config; diff --git a/src/lib/settings/SettingsMigrator.cpp b/src/lib/settings/SettingsMigrator.cpp new file mode 100644 index 00000000..e9d86790 --- /dev/null +++ b/src/lib/settings/SettingsMigrator.cpp @@ -0,0 +1,55 @@ +#include "settings/SettingsMigrator.h" + +#include "settings/Settings.h" + +SettingsMigrator::SettingsMigrator() +{ +} + +SettingsMigrator::~SettingsMigrator() +{ +} + +void SettingsMigrator::addMigration(size_t targetVersion, std::string oldKey, std::string newKey) +{ + Migration migration; + migration.targetVersion = targetVersion; + migration.oldKey = oldKey; + migration.newKey = newKey; + + m_migrations.emplace(targetVersion, migration); +} + +bool SettingsMigrator::migrate(Settings* settings, size_t targetVersion) const +{ + size_t originVersion = settings->getVersion(); + + if (originVersion < targetVersion) + { + for (; originVersion <= targetVersion; originVersion++) + { + std::pair::const_iterator, std::multimap::const_iterator> ret; + ret = m_migrations.equal_range(originVersion); + + for (std::multimap::const_iterator it = ret.first; it != ret.second; it++) + { + const Migration& migration = it->second; + + if (!settings->isValueDefined(migration.newKey)) + { + settings->setValues( + migration.newKey, + settings->getValues(migration.oldKey, std::vector()) + ); + settings->removeValues(migration.oldKey); + } + } + } + + settings->setVersion(targetVersion); + + return true; + } + + return false; +} diff --git a/src/lib/settings/SettingsMigrator.h b/src/lib/settings/SettingsMigrator.h new file mode 100644 index 00000000..343030b2 --- /dev/null +++ b/src/lib/settings/SettingsMigrator.h @@ -0,0 +1,32 @@ +#ifndef SETTNGS_MIGRATOR_H +#define SETTNGS_MIGRATOR_H + +#include +#include + +class Settings; + +class SettingsMigrator +{ +public: + static SettingsMigrator createApplicationSettingsMigrator(); + + SettingsMigrator(); + virtual ~SettingsMigrator(); + + void addMigration(size_t targetVersion, std::string oldKey, std::string newKey); + + bool migrate(Settings* settings, size_t targetVersion) const; + +private: + struct Migration + { + size_t targetVersion; + std::string oldKey; + std::string newKey; + }; + + std::multimap m_migrations; +}; + +#endif // SETTINGS_MIGRATOR_H diff --git a/src/lib/utility/ConfigManager.cpp b/src/lib/utility/ConfigManager.cpp index dc658b27..78ccfa4a 100644 --- a/src/lib/utility/ConfigManager.cpp +++ b/src/lib/utility/ConfigManager.cpp @@ -220,6 +220,11 @@ void ConfigManager::setValues(const std::string& key, const std::vector& v setValues(key, stringValues); } +void ConfigManager::removeValues(const std::string& key) +{ + m_values.erase(key); +} + bool ConfigManager::isValueDefined(const std::string& key) const { std::multimap::const_iterator it = m_values.find(key); diff --git a/src/lib/utility/ConfigManager.h b/src/lib/utility/ConfigManager.h index c717f202..6361b384 100644 --- a/src/lib/utility/ConfigManager.h +++ b/src/lib/utility/ConfigManager.h @@ -38,6 +38,8 @@ public: void setValues(const std::string& key, const std::vector& values); void setValues(const std::string& key, const std::vector& values); + void removeValues(const std::string& key); + bool isValueDefined(const std::string& key) const; bool load(const std::shared_ptr textAccess); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 40b94f1f..663a50bb 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -21,6 +21,7 @@ add_files( MessageQueueTestSuite.h NetworkProtocolHelperTestSuite.h SettingsTestSuite.h + SettingsMigratorTestSuite.h SearchIndexTestSuite.h SqliteStorageTestSuite.h StorageTestSuite.h diff --git a/src/test/SettingsMigratorTestSuite.h b/src/test/SettingsMigratorTestSuite.h new file mode 100644 index 00000000..557e3cf0 --- /dev/null +++ b/src/test/SettingsMigratorTestSuite.h @@ -0,0 +1,289 @@ +#include + +#include "settings/Settings.h" +#include "settings/SettingsMigrator.h" + +class SettingsMigratorTestSuite : public CxxTest::TestSuite +{ +public: + void test_migrator_changes_nothing_without_migrations_except_version() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_name() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "int"); + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_path() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " \n" + " 2\n" + " \n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "sub/int"); + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_group_name() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " \n" + " 2\n" + " 3\n" + " 4\n" + " \n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " \n" + " 2\n" + " 3\n" + " 4\n" + " \n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "values/value", "vals/value"); + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_group_element_name() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " \n" + " 2\n" + " 3\n" + " 4\n" + " \n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " \n" + " 2\n" + " 3\n" + " 4\n" + " \n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "values/value", "values/val"); + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_only_up_specified_version() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " 1\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "int"); + migrator.addMigration(2, "int", "val"); + migrator.migrate(&settingsBefore, 1); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_only_from_specified_version() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + " 1\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " 2\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "int"); + migrator.addMigration(2, "int", "val"); + migrator.migrate(&settingsBefore, 2); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_for_multiple_versions() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " 2\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "int"); + migrator.addMigration(2, "int", "val"); + migrator.migrate(&settingsBefore, 2); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_for_multiple_migrations() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + " hi there\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " hi there\n" + " 2\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "val"); + migrator.addMigration(1, "element", "ele"); + migrator.migrate(&settingsBefore, 2); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + + void test_migrator_changes_for_multiple_versions_and_migrations() + { + Settings settingsBefore = createSettings( + "\n" + "\n" + " 2\n" + " two\n" + " three\n" + " four\n" + "\n" + ); + + Settings settingsAfter = createSettings( + "\n" + "\n" + " 2\n" + " \n" + " two\n" + " three\n" + " four\n" + " \n" + " 3\n" + "\n" + ); + + SettingsMigrator migrator; + migrator.addMigration(1, "value", "int/val"); + migrator.addMigration(1, "element", "ele"); + migrator.addMigration(2, "int/val", "int"); + migrator.addMigration(3, "ele", "elements/element"); + migrator.migrate(&settingsBefore, 3); + + TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText()); + } + +private: + Settings createSettings(const std::string& text) + { + return Settings::createFromText(TextAccess::createFromString(text)); + } +};