logic: Application settings migration system

* Added version number to ApplicationSettings
* Added SettingsMigrator for defining migration rules for different ApplicationSettings versions
* Migrate ApplicationSettings when loading
This commit is contained in:
Eberhard Graether
2016-08-25 12:40:54 +02:00
parent cc8ea7c8fb
commit 03bbce45c9
13 changed files with 489 additions and 23 deletions
@@ -1,14 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<config>
<application>
<indexer_thread_count>4</indexer_thread_count>
<window_base_width>500</window_base_width>
<window_base_height>500</window_base_height>
</application>
<user>
<recent_projects>
<recent_project>./data/projects/tutorial/tutorial.coatiproject</recent_project>
<recent_project>./data/projects/tictactoe/tictactoe.coatiproject</recent_project>
</recent_projects>
</user>
<application>
<window_base_height>500</window_base_height>
<window_base_width>500</window_base_width>
</application>
<indexing>
<indexer_thread_count>4</indexer_thread_count>
</indexing>
<user>
<recent_projects>
<recent_project>./data/projects/tutorial/tutorial.coatiproject</recent_project>
<recent_project>./data/projects/tictactoe/tictactoe.coatiproject</recent_project>
</recent_projects>
</user>
<version>1</version>
</config>
@@ -7,6 +7,8 @@
<!-- COLOR: int int int int - rgba e.g. 125 125 125 255 -->
<config>
<version><!-- INTEGER: Version number --></version>
<application>
<font_name><!-- STRING: font name --></font_name>
<font_size><!-- INTEGER: font size in pt --></font_size>
+2
View File
@@ -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
+32
View File
@@ -3,6 +3,8 @@
#include "utility/ResourcePaths.h"
#include "utility/utility.h"
const size_t ApplicationSettings::VERSION = 1;
std::shared_ptr<ApplicationSettings> ApplicationSettings::s_instance;
std::shared_ptr<ApplicationSettings> 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
+5
View File
@@ -10,9 +10,14 @@ class ApplicationSettings
{
public:
static std::shared_ptr<ApplicationSettings> getInstance();
static const size_t VERSION;
ApplicationSettings();
~ApplicationSettings();
bool load(const FilePath& filePath);
bool operator==(const ApplicationSettings& other) const;
int getMaxRecentProjectsCount() const;
+35 -9
View File
@@ -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<int>("version", 0);
}
void Settings::setVersion(size_t version)
{
setValue<int>("version", version);
}
Settings Settings::createFromText(const std::shared_ptr<TextAccess> 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);
+15 -3
View File
@@ -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> 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<ConfigManager> m_config;
+55
View File
@@ -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<std::multimap<size_t, Migration>::const_iterator, std::multimap<size_t, Migration>::const_iterator> ret;
ret = m_migrations.equal_range(originVersion);
for (std::multimap<size_t, Migration>::const_iterator it = ret.first; it != ret.second; it++)
{
const Migration& migration = it->second;
if (!settings->isValueDefined(migration.newKey))
{
settings->setValues<std::string>(
migration.newKey,
settings->getValues<std::string>(migration.oldKey, std::vector<std::string>())
);
settings->removeValues(migration.oldKey);
}
}
}
settings->setVersion(targetVersion);
return true;
}
return false;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef SETTNGS_MIGRATOR_H
#define SETTNGS_MIGRATOR_H
#include <map>
#include <string>
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<size_t, Migration> m_migrations;
};
#endif // SETTINGS_MIGRATOR_H
+5
View File
@@ -220,6 +220,11 @@ void ConfigManager::setValues(const std::string& key, const std::vector<bool>& 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<std::string, std::string>::const_iterator it = m_values.find(key);
+2
View File
@@ -38,6 +38,8 @@ public:
void setValues(const std::string& key, const std::vector<float>& values);
void setValues(const std::string& key, const std::vector<bool>& values);
void removeValues(const std::string& key);
bool isValueDefined(const std::string& key) const;
bool load(const std::shared_ptr<TextAccess> textAccess);
+1
View File
@@ -21,6 +21,7 @@ add_files(
MessageQueueTestSuite.h
NetworkProtocolHelperTestSuite.h
SettingsTestSuite.h
SettingsMigratorTestSuite.h
SearchIndexTestSuite.h
SqliteStorageTestSuite.h
StorageTestSuite.h
+289
View File
@@ -0,0 +1,289 @@
#include <cxxtest/TestSuite.h>
#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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
" <version>1</version>\n"
"</config>\n"
);
SettingsMigrator migrator;
migrator.migrate(&settingsBefore, 1);
TS_ASSERT_EQUALS(settingsBefore.getAsText(), settingsAfter.getAsText());
}
void test_migrator_changes_name()
{
Settings settingsBefore = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <int>2</int>\n"
" <version>1</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <sub>\n"
" <int>2</int>\n"
" </sub>\n"
" <version>1</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <values>\n"
" <value>2</value>\n"
" <value>3</value>\n"
" <value>4</value>\n"
" </values>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <vals>\n"
" <value>2</value>\n"
" <value>3</value>\n"
" <value>4</value>\n"
" </vals>\n"
" <version>1</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <values>\n"
" <value>2</value>\n"
" <value>3</value>\n"
" <value>4</value>\n"
" </values>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <values>\n"
" <val>2</val>\n"
" <val>3</val>\n"
" <val>4</val>\n"
" </values>\n"
" <version>1</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <int>2</int>\n"
" <version>1</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <int>2</int>\n"
" <version>1</version>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <val>2</val>\n"
" <version>2</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <val>2</val>\n"
" <version>2</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
" <element>hi there</element>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <val>2</val>\n"
" <ele>hi there</ele>\n"
" <version>2</version>\n"
"</config>\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(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <value>2</value>\n"
" <element>two</element>\n"
" <element>three</element>\n"
" <element>four</element>\n"
"</config>\n"
);
Settings settingsAfter = createSettings(
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<config>\n"
" <int>2</int>\n"
" <elements>\n"
" <element>two</element>\n"
" <element>three</element>\n"
" <element>four</element>\n"
" </elements>\n"
" <version>3</version>\n"
"</config>\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));
}
};