diff --git a/src/common/module.mk b/src/common/module.mk index 7d9af0692..2b0152305 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -34,6 +34,7 @@ MODULE_OBJS := \ src/common/sdl_blitter/BlitterFactory.o \ src/common/repository/KeyValueRepositoryPropertyFile.o \ src/common/repository/KeyValueRepositoryJsonFile.o \ + src/common/repository/KeyValueRepositoryConfigfile.o \ src/common/repository/CompositeKVRJsonAdapter.o MODULE_DIRS += \ diff --git a/src/common/repository/KeyValueRepository.hxx b/src/common/repository/KeyValueRepository.hxx index ddc80e039..43b89f2a9 100644 --- a/src/common/repository/KeyValueRepository.hxx +++ b/src/common/repository/KeyValueRepository.hxx @@ -23,11 +23,6 @@ #include "Variant.hxx" #include "bspf.hxx" -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Woverloaded-virtual" -#endif - class KeyValueRepositoryAtomic; class KeyValueRepository @@ -45,6 +40,8 @@ class KeyValueRepository class KeyValueRepositoryAtomic : public KeyValueRepository { public: + using KeyValueRepository::save; + virtual bool has(const string& key) = 0; virtual bool get(const string& key, string& value) = 0; @@ -56,8 +53,4 @@ class KeyValueRepositoryAtomic : public KeyValueRepository { KeyValueRepositoryAtomic* atomic() override { return this; } }; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - #endif // KEY_VALUE_REPOSITORY_HXX diff --git a/src/common/repository/KeyValueRepositoryConfigfile.cxx b/src/common/repository/KeyValueRepositoryConfigfile.cxx index 4b8cfbeb5..bc4047c1f 100644 --- a/src/common/repository/KeyValueRepositoryConfigfile.cxx +++ b/src/common/repository/KeyValueRepositoryConfigfile.cxx @@ -20,29 +20,17 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const FilesystemNode& file) - : myFile{file} -{ -} + : KeyValueRepositoryFile(file) +{} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -std::map KeyValueRepositoryConfigfile::load() +std::map KeyValueRepositoryConfigfile::load(istream& in) { std::map values; string line, key, value; string::size_type equalPos, garbage; - stringstream in; - try - { - myFile.read(in); - } - catch(...) - { - Logger::error("ERROR: Couldn't load from settings file " + myFile.getShortPath()); - return values; - } - while(getline(in, line)) { // Strip all whitespace and tabs from the line @@ -72,9 +60,8 @@ std::map KeyValueRepositoryConfigfile::load() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool KeyValueRepositoryConfigfile::save(const std::map& values) +bool KeyValueRepositoryConfigfile::save(ostream& out, const std::map& values) { - stringstream out; out << "; Stella configuration file" << endl << ";" << endl << "; Lines starting with ';' are comments and are ignored." << endl @@ -93,17 +80,4 @@ bool KeyValueRepositoryConfigfile::save(const std::map& values) // Write out each of the key and value pairs for(const auto& [key, value]: values) out << key << " = " << value << endl; - - try - { - myFile.write(out); - - return true; - } - catch(...) - { - Logger::error("ERROR: Couldn't save to settings file " + myFile.getShortPath()); - - return false; - } } diff --git a/src/common/repository/KeyValueRepositoryConfigfile.hxx b/src/common/repository/KeyValueRepositoryConfigfile.hxx index ef46417a5..53485f18d 100644 --- a/src/common/repository/KeyValueRepositoryConfigfile.hxx +++ b/src/common/repository/KeyValueRepositoryConfigfile.hxx @@ -19,21 +19,19 @@ #define KEY_VALUE_REPOSITORY_CONFIGFILE_HXX #include "FSNode.hxx" -#include "KeyValueRepository.hxx" +#include "repository/KeyValueRepositoryFile.hxx" -class KeyValueRepositoryConfigfile : public KeyValueRepositoryAtomic +class KeyValueRepositoryConfigfile : public KeyValueRepositoryFile { public: + using KeyValueRepositoryFile::load; + using KeyValueRepositoryFile::save; - explicit KeyValueRepositoryConfigfile(const FilesystemNode& file); + KeyValueRepositoryConfigfile(const FilesystemNode& node); - std::map load() override; + static std::map load(istream& in); - bool save(const std::map& values) override; - - private: - - FilesystemNode myFile; + static bool save(ostream& out, const std::map& values); }; #endif // KEY_VALUE_REPOSITORY_CONFIGFILE_HXX diff --git a/src/common/repository/KeyValueRepositoryJsonFile.hxx b/src/common/repository/KeyValueRepositoryJsonFile.hxx index ae54fc5f3..a8c590a7e 100644 --- a/src/common/repository/KeyValueRepositoryJsonFile.hxx +++ b/src/common/repository/KeyValueRepositoryJsonFile.hxx @@ -26,6 +26,9 @@ class KeyValueRepositoryJsonFile : public KeyValueRepositoryFile { public: + using KeyValueRepositoryFile::load; + using KeyValueRepositoryFile::save; + KeyValueRepositoryJsonFile(const FilesystemNode& node); static std::map load(istream& in); diff --git a/src/common/repository/KeyValueRepositoryPropertyFile.hxx b/src/common/repository/KeyValueRepositoryPropertyFile.hxx index 13115f688..d2dbccbe4 100644 --- a/src/common/repository/KeyValueRepositoryPropertyFile.hxx +++ b/src/common/repository/KeyValueRepositoryPropertyFile.hxx @@ -26,6 +26,9 @@ class KeyValueRepositoryPropertyFile : public KeyValueRepositoryFile { public: + using KeyValueRepositoryFile::load; + using KeyValueRepositoryFile::save; + KeyValueRepositoryPropertyFile(const FilesystemNode& node); static std::map load(istream& in); diff --git a/src/common/repository/sqlite/SettingsDb.cxx b/src/common/repository/sqlite/SettingsDb.cxx index 1033da893..986aa1e6d 100644 --- a/src/common/repository/sqlite/SettingsDb.cxx +++ b/src/common/repository/sqlite/SettingsDb.cxx @@ -15,13 +15,22 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +#include + #include "SettingsDb.hxx" #include "Logger.hxx" #include "SqliteError.hxx" #include "repository/KeyValueRepositoryNoop.hxx" #include "repository/CompositeKeyValueRepositoryNoop.hxx" #include "repository/CompositeKVRJsonAdapter.hxx" +#include "repository/KeyValueRepositoryConfigfile.hxx" #include "KeyValueRepositorySqlite.hxx" +#include "SqliteTransaction.hxx" +#include "FSNode.hxx" + +namespace { + constexpr Int32 CURRENT_VERSION = 1; +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SettingsDb::SettingsDb(const string& databaseDirectory, const string& databaseName) @@ -46,14 +55,57 @@ void SettingsDb::initialize() myPropertyRepositoryHost = std::move(propertyRepositoryHost); myPropertyRepository = make_unique(*myPropertyRepositoryHost); + + if (myDb->getUserVersion() == 0) { + initializeDb(); + } else { + migrate(); + } } catch (const SqliteError& err) { Logger::error("sqlite DB " + databaseFileName() + " failed to initialize: " + err.what()); - myDb.reset(); - myPropertyRepositoryHost.reset(); - mySettingsRepository = make_unique(); myPropertyRepository = make_unique(); + + myDb.reset(); + myPropertyRepositoryHost.reset(); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SettingsDb::initializeDb() { + SqliteTransaction tx{*myDb}; + + FilesystemNode legacyConfigFile{myDatabaseDirectory}; + legacyConfigFile /= "stellarc"; + + FilesystemNode legacyConfigDatabase{myDatabaseDirectory}; + legacyConfigDatabase /= "settings.sqlite3"; + + if (legacyConfigFile.exists() && legacyConfigFile.isFile()) { + Logger::info("importing old settings from " + legacyConfigFile.getPath()); + + mySettingsRepository->save(KeyValueRepositoryConfigfile{legacyConfigFile}.load()); + } + + myDb->setUserVersion(CURRENT_VERSION); + + tx.commit(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SettingsDb::migrate() { + Int32 version = myDb->getUserVersion(); + switch (version) { + case 1: + return; + + default: { + stringstream ss; + ss << "invalid database version " << version; + + throw new SqliteError(ss.str()); + } } } diff --git a/src/common/repository/sqlite/SettingsDb.hxx b/src/common/repository/sqlite/SettingsDb.hxx index ea97cbd7d..655e576ec 100644 --- a/src/common/repository/sqlite/SettingsDb.hxx +++ b/src/common/repository/sqlite/SettingsDb.hxx @@ -31,12 +31,18 @@ class SettingsDb void initialize(); - KeyValueRepository& settingsRepository() const { return *mySettingsRepository; } + KeyValueRepositoryAtomic& settingsRepository() const { return *mySettingsRepository; } CompositeKeyValueRepository& propertyRepository() const { return *myPropertyRepository; } const string& databaseFileName() const { return myDatabaseName; } + private: + + void initializeDb(); + + void migrate(); + private: string myDatabaseDirectory; diff --git a/src/common/repository/sqlite/SqliteDatabase.cxx b/src/common/repository/sqlite/SqliteDatabase.cxx index 0e405d3ae..d6ab3c350 100644 --- a/src/common/repository/sqlite/SqliteDatabase.cxx +++ b/src/common/repository/sqlite/SqliteDatabase.cxx @@ -20,6 +20,7 @@ #include "SqliteDatabase.hxx" #include "Logger.hxx" #include "SqliteError.hxx" +#include "SqliteStatement.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SqliteDatabase::SqliteDatabase(const string& databaseDirectory, @@ -91,3 +92,24 @@ void SqliteDatabase::exec(const string& sql) const if (sqlite3_exec(myHandle, sql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) throw SqliteError(myHandle); } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 SqliteDatabase::getUserVersion() const +{ + SqliteStatement stmt(*this, "PRAGMA user_version"); + stmt.reset(); + + if (!stmt.step()) + throw SqliteError("failed to get user_version"); + + return stmt.columnInt(0); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SqliteDatabase::setUserVersion(Int32 version) const +{ + SqliteStatement stmt(*this, "PRAGMA user_version = %i", static_cast(version)); + stmt.reset(); + + stmt.step(); +} diff --git a/src/common/repository/sqlite/SqliteDatabase.hxx b/src/common/repository/sqlite/SqliteDatabase.hxx index cdbe7be5b..58d4e3e02 100644 --- a/src/common/repository/sqlite/SqliteDatabase.hxx +++ b/src/common/repository/sqlite/SqliteDatabase.hxx @@ -41,6 +41,9 @@ class SqliteDatabase template void exec(const string& sql, T arg1, Ts... args) const; + Int32 getUserVersion() const; + void setUserVersion(Int32 version) const; + private: string myDatabaseFile; diff --git a/src/common/repository/sqlite/SqliteStatement.cxx b/src/common/repository/sqlite/SqliteStatement.cxx index d3f93d1c4..d807281a2 100644 --- a/src/common/repository/sqlite/SqliteStatement.cxx +++ b/src/common/repository/sqlite/SqliteStatement.cxx @@ -50,6 +50,15 @@ SqliteStatement& SqliteStatement::bind(int index, const string& value) return *this; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SqliteStatement& SqliteStatement::bind(int index, Int32 value) +{ + if (sqlite3_bind_int(myStmt, index, value) != SQLITE_OK) + throw SqliteError(myHandle); + + return *this; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SqliteStatement::step() const { diff --git a/src/common/repository/sqlite/SqliteStatement.hxx b/src/common/repository/sqlite/SqliteStatement.hxx index 3ef4e3ec0..99b8a3f7b 100644 --- a/src/common/repository/sqlite/SqliteStatement.hxx +++ b/src/common/repository/sqlite/SqliteStatement.hxx @@ -35,6 +35,7 @@ class SqliteStatement { operator sqlite3_stmt*() const { return myStmt; } SqliteStatement& bind(int index, const string& value); + SqliteStatement& bind(int index, Int32 value); bool step() const; diff --git a/src/common/repository/sqlite/SqliteTransaction.cxx b/src/common/repository/sqlite/SqliteTransaction.cxx index 2bda587a0..5b54bc627 100644 --- a/src/common/repository/sqlite/SqliteTransaction.cxx +++ b/src/common/repository/sqlite/SqliteTransaction.cxx @@ -22,6 +22,11 @@ SqliteTransaction::SqliteTransaction(SqliteDatabase& db) : myDb{db} { + if (sqlite3_get_autocommit(db)) { + myTransactionClosed = true; + return; + } + myDb.exec("BEGIN TRANSACTION"); } diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 9608fd5ed..6d41b1120 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -223,7 +223,7 @@ void OSystem::loadConfig(const Settings::Options& options) if(!myHomeDir.isDirectory()) myHomeDir.makeDir(); - mySettingsDb = make_shared(myBaseDir.getPath(), "settings"); + mySettingsDb = make_shared(myBaseDir.getPath(), "stella"); mySettingsDb->initialize(); myConfigFile = FilesystemNode(mySettingsDb->databaseFileName());