diff options
Diffstat (limited to 'src/djinterop')
37 files changed, 6354 insertions, 1481 deletions
diff --git a/src/djinterop/crate.cpp b/src/djinterop/crate.cpp index 4f3fdaf..856ff28 100644 --- a/src/djinterop/crate.cpp +++ b/src/djinterop/crate.cpp @@ -20,7 +20,7 @@ #include <djinterop/djinterop.hpp> #include <djinterop/impl/crate_impl.hpp> -#include <djinterop/impl/util.hpp> +#include <djinterop/util.hpp> namespace djinterop { diff --git a/src/djinterop/database.cpp b/src/djinterop/database.cpp index fcff0d3..5be4c78 100644 --- a/src/djinterop/database.cpp +++ b/src/djinterop/database.cpp @@ -19,10 +19,10 @@ #include <djinterop/djinterop.hpp> #include <djinterop/enginelibrary/el_database_impl.hpp> -#include <djinterop/enginelibrary/schema.hpp> +#include <djinterop/enginelibrary/schema/schema.hpp> #include <djinterop/impl/database_impl.hpp> -#include <djinterop/impl/util.hpp> #include <djinterop/transaction_guard.hpp> +#include <djinterop/util.hpp> namespace djinterop { @@ -124,6 +124,11 @@ semantic_version database::version() const return pimpl_->version(); } +std::string database::version_name() const +{ + return pimpl_->version_name(); +} + database::database(std::shared_ptr<database_impl> pimpl) : pimpl_{std::move(pimpl)} { diff --git a/src/djinterop/enginelibrary.cpp b/src/djinterop/enginelibrary.cpp index c1a3f1a..562b0f6 100644 --- a/src/djinterop/enginelibrary.cpp +++ b/src/djinterop/enginelibrary.cpp @@ -15,78 +15,42 @@ along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. */ -#include <sys/stat.h> #include <cmath> #include <string> -#if defined(_WIN32) -#include <direct.h> -#endif #include <djinterop/djinterop.hpp> -#include <djinterop/enginelibrary/el_database_impl.hpp> -#include <djinterop/enginelibrary/el_transaction_guard_impl.hpp> +#include "enginelibrary/el_database_impl.hpp" +#include "enginelibrary/el_transaction_guard_impl.hpp" +#include "enginelibrary/schema/schema.hpp" +#include "util.hpp" -namespace djinterop +namespace djinterop::enginelibrary { -namespace enginelibrary +/// Gets a descriptive name for a given schema version. +std::string version_name(const semantic_version& version) { - -static bool dir_exists(const std::string& directory) -{ - struct stat buf; - return stat(directory.c_str(), &buf) == 0; -} - -static void ensure_dir_exists(const std::string &directory, bool &created) -{ - created = false; - if (!dir_exists(directory)) - { -#if defined(_WIN32) - if (_mkdir(directory.c_str()) != 0) -#else - if (mkdir(directory.c_str(), 0755) != 0) -#endif - { - throw std::runtime_error{ - "Failed to create directory"}; - } - - created = true; - } + auto schema_creator_validator = + schema::make_schema_creator_validator(version); + return schema_creator_validator->name(); } database create_database( - std::string directory, const semantic_version& schema_version) + const std::string& directory, const semantic_version& schema_version) { - if (!el_storage::schema_version_supported(schema_version)) - { - throw unsupported_database_version{"Unsupported database version", - schema_version}; - } - - // Ensure the target directory exists. - bool dir_created; - ensure_dir_exists(directory, dir_created); - - // Create schema. - auto storage = std::make_shared<el_storage>(std::move(directory)); - el_transaction_guard_impl trans{storage}; - storage->create_and_validate_schema(schema_version); - database db{std::make_shared<el_database_impl>(storage)}; - trans.commit(); - return db; + auto storage = std::make_shared<el_storage>(directory, schema_version); + return database{std::make_shared<el_database_impl>(storage)}; } database create_or_load_database( - std::string directory, const semantic_version& schema_version, bool &created) + const std::string& directory, const semantic_version& schema_version, + bool& created) { - if (database_exists(directory)) + try { created = false; return load_database(directory); } - else + catch (database_not_found& e) { created = true; return create_database(directory, schema_version); @@ -95,24 +59,22 @@ database create_or_load_database( bool database_exists(const std::string& directory) { - // We have to find out whether the engine library exists. Naively, we'd do - // this by checking if the m.db and p.db files exist. However, if a previous - // attempt to create the engine library failed after creating the files and - // before creating the schemata, then the files exist but the enginelibrary - // doesn't exist. - if (!dir_exists(directory)) + try + { + load_database(directory); + } + catch (database_not_found& e) { - // No EL DB directory. return false; } - el_storage storage{std::move(directory)}; - return storage.schema_created(); + return true; } -database load_database(std::string directory) +database load_database(const std::string& directory) { - return database{std::make_shared<el_database_impl>(std::move(directory))}; + auto storage = std::make_shared<el_storage>(directory); + return database{std::make_shared<el_database_impl>(storage)}; } std::string music_db_path(const database& db) @@ -185,5 +147,4 @@ std::string perfdata_db_path(const database& db) return db.directory() + "/p.db"; } -} // namespace enginelibrary -} // namespace djinterop +} // namespace djinterop::enginelibrary diff --git a/src/djinterop/enginelibrary/el_crate_impl.cpp b/src/djinterop/enginelibrary/el_crate_impl.cpp index 0567b02..c67b1b8 100644 --- a/src/djinterop/enginelibrary/el_crate_impl.cpp +++ b/src/djinterop/enginelibrary/el_crate_impl.cpp @@ -145,10 +145,25 @@ crate el_crate_impl::create_sub_crate(std::string name) } }; - storage_->db << "INSERT INTO Crate (title, path) VALUES (?, ?)" - << name.data() << (path + name + ";"); - - int64_t sub_id = storage_->db.last_insert_rowid(); + int64_t sub_id; + if (storage_->version >= version_1_9_1) + { + // Newer schemas consider crates to be a kind of 'list', and so the + // `Crate` table has been replaced with a VIEW onto `List`. The main + // difference is that `List` does not have an integer primary key, so + // the new id will need to be determined in advance. + storage_->db << "SELECT IFNULL(MAX(id), 0) + 1 FROM Crate" >> sub_id; + storage_->db << "INSERT INTO Crate (id, title, path) VALUES (?, ?, ?)" + << sub_id << name.data() << (path + name + ";"); + } + else + { + // Older schema versions have a dedicated table for crates that has + // an integer primary key, which will be filled automatically. + storage_->db << "INSERT INTO Crate (title, path) VALUES (?, ?)" + << name.data() << (path + name + ";"); + sub_id = storage_->db.last_insert_rowid(); + } storage_->db << "INSERT INTO CrateParentList (crateOriginId, " "crateParentId) VALUES (?, ?)" diff --git a/src/djinterop/enginelibrary/el_database_impl.cpp b/src/djinterop/enginelibrary/el_database_impl.cpp index bb82e24..acd7fda 100644 --- a/src/djinterop/enginelibrary/el_database_impl.cpp +++ b/src/djinterop/enginelibrary/el_database_impl.cpp @@ -21,9 +21,9 @@ #include <djinterop/enginelibrary/el_storage.hpp> #include <djinterop/enginelibrary/el_track_impl.hpp> #include <djinterop/enginelibrary/el_transaction_guard_impl.hpp> -#include <djinterop/enginelibrary/schema.hpp> -#include <djinterop/impl/util.hpp> +#include <djinterop/enginelibrary/schema/schema.hpp> #include <djinterop/transaction_guard.hpp> +#include <djinterop/util.hpp> namespace djinterop { @@ -50,14 +50,6 @@ void ensure_valid_crate_name(const std::string& name) } // namespace -el_database_impl::el_database_impl(std::string directory) : - storage_{std::make_shared<el_storage>(std::move(directory))} -{ - // TODO (haslersn): On construction, should we check that the database - // version is supported? This would give more guarantees to a user that - // obtains a database object. -} - el_database_impl::el_database_impl(std::shared_ptr<el_storage> storage) : storage_{std::move(storage)} { @@ -113,10 +105,25 @@ crate el_database_impl::create_root_crate(std::string name) ensure_valid_crate_name(name); el_transaction_guard_impl trans{storage_}; - storage_->db << "INSERT INTO Crate (title, path) VALUES (?, ?)" - << name.data() << std::string{name} + ';'; - - int64_t id = storage_->db.last_insert_rowid(); + int64_t id; + if (storage_->version >= version_1_9_1) + { + // Newer schemas consider crates to be a kind of 'list', and so the + // `Crate` table has been replaced with a VIEW onto `List`. The main + // difference is that `List` does not have an integer primary key, so + // the new id will need to be determined in advance. + storage_->db << "SELECT IFNULL(MAX(id), 0) + 1 FROM Crate" >> id; + storage_->db << "INSERT INTO Crate (id, title, path) VALUES (?, ?, ?)" + << id << name.data() << std::string{name} + ';'; + } + else + { + // Older schema versions have a dedicated table for crates that has + // an integer primary key, which will be filled automatically. + storage_->db << "INSERT INTO Crate (title, path) VALUES (?, ?)" + << name.data() << std::string{name} + ';'; + id = storage_->db.last_insert_rowid(); + } storage_->db << "INSERT INTO CrateParentList (crateOriginId, " "crateParentId) VALUES (?, ?)" @@ -220,16 +227,14 @@ std::string el_database_impl::directory() bool el_database_impl::is_supported() { - return djinterop::enginelibrary::is_supported(version()); + return schema::is_supported(version()); } void el_database_impl::verify() { - // Verify music schema - verify_music_schema(storage_->db); - - // Verify performance schema - verify_performance_schema(storage_->db); + auto schema_creator_validator = + schema::make_schema_creator_validator(version()); + schema_creator_validator->verify(storage_->db); } void el_database_impl::remove_crate(crate cr) @@ -322,11 +327,12 @@ std::string el_database_impl::uuid() semantic_version el_database_impl::version() { - semantic_version version; - storage_->db << "SELECT schemaVersionMajor, schemaVersionMinor, " - "schemaVersionPatch FROM Information" >> - std::tie(version.maj, version.min, version.pat); - return version; + return storage_->version; +} + +std::string el_database_impl::version_name() +{ + return storage_->schema_creator_validator->name(); } } // namespace enginelibrary diff --git a/src/djinterop/enginelibrary/el_database_impl.hpp b/src/djinterop/enginelibrary/el_database_impl.hpp index 1feda91..2ba840f 100644 --- a/src/djinterop/enginelibrary/el_database_impl.hpp +++ b/src/djinterop/enginelibrary/el_database_impl.hpp @@ -29,7 +29,6 @@ namespace enginelibrary class el_database_impl : public database_impl { public: - el_database_impl(std::string directory); el_database_impl(std::shared_ptr<el_storage> storage); transaction_guard begin_transaction() override; @@ -53,6 +52,7 @@ public: const std::string& relative_path) override; std::string uuid() override; semantic_version version() override; + std::string version_name() override; private: std::shared_ptr<el_storage> storage_; diff --git a/src/djinterop/enginelibrary/el_storage.cpp b/src/djinterop/enginelibrary/el_storage.cpp index dff4639..bc4b253 100644 --- a/src/djinterop/enginelibrary/el_storage.cpp +++ b/src/djinterop/enginelibrary/el_storage.cpp @@ -15,51 +15,96 @@ along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. */ -#include <djinterop/enginelibrary/el_storage.hpp> -#include <djinterop/enginelibrary/schema.hpp> +#include "el_storage.hpp" -namespace djinterop +#include <djinterop/database.hpp> +#include <djinterop/exceptions.hpp> + +#include "../util.hpp" +#include "schema/schema.hpp" + +namespace djinterop::enginelibrary { -namespace enginelibrary +namespace { -el_storage::el_storage(std::string directory) - : directory{directory}, db{":memory:"} +sqlite::database make_attached_db(const std::string& directory, bool must_exist) { - // TODO (mr-smidge): Throw custom database_not_found if files don't exist. - // TODO (haslersn): Should we check that directory is an absolute path? + if (!dir_exists(directory)) + { + if (must_exist) + { + throw database_not_found{directory}; + } + else + { + // Note: only creates leaf directory, not entire tree. + create_dir(directory); + } + } + + sqlite::database db{":memory:"}; db << "ATTACH ? as 'music'" << (directory + "/m.db"); db << "ATTACH ? as 'perfdata'" << (directory + "/p.db"); + return db; } -bool el_storage::schema_version_supported(semantic_version schema_version) -{ - return is_supported(schema_version); -} - -void el_storage::create_and_validate_schema(semantic_version schema_version) -{ - create_music_schema(db, schema_version); - verify_music_schema(db); - create_performance_schema(db, schema_version); - verify_performance_schema(db); -} - -bool el_storage::schema_created() const +semantic_version get_version(sqlite::database& db) { + // Check that the `Information` table has been created. std::string sql = "SELECT SUM(rows) FROM (" " SELECT COUNT(*) AS rows " - " FROM music.sqlite_master WHERE " - " type = 'table' " + " FROM music.sqlite_master " + " WHERE name = 'Information' " " UNION ALL " " SELECT COUNT(*) AS rows " " FROM perfdata.sqlite_master " - " WHERE type = 'table'" + " WHERE name = 'Information' " ")"; int32_t table_count; db << sql >> table_count; - return table_count != 0; + if (table_count != 2) + { + throw database_inconsistency{ + "Did not find an `Information` table for both the music and " + "performance databases"}; + } + + semantic_version music_version; + semantic_version perfdata_version; + db << "SELECT schemaVersionMajor, schemaVersionMinor, " + "schemaVersionPatch FROM music.Information" >> + std::tie(music_version.maj, music_version.min, music_version.pat); + db << "SELECT schemaVersionMajor, schemaVersionMinor, " + "schemaVersionPatch FROM music.Information" >> + std::tie( + perfdata_version.maj, perfdata_version.min, perfdata_version.pat); + if (music_version != perfdata_version) + { + throw database_inconsistency{ + "The stated schema versions do not match between the music and " + "performance data databases!"}; + } + + return music_version; +} + +} // anonymous namespace + +el_storage::el_storage(const std::string& directory) : + directory{directory}, db{make_attached_db(directory, true)}, + version{get_version(db)}, + schema_creator_validator{schema::make_schema_creator_validator(version)} +{ +} + +el_storage::el_storage(const std::string& directory, semantic_version version) : + directory{directory}, db{make_attached_db(directory, false)}, + version{version}, schema_creator_validator{ + schema::make_schema_creator_validator(version)} +{ + // Create the desired schema on the new database. + schema_creator_validator->create(db); } -} // namespace enginelibrary -} // namespace djinterop +} // namespace djinterop::enginelibrary diff --git a/src/djinterop/enginelibrary/el_storage.hpp b/src/djinterop/enginelibrary/el_storage.hpp index 7e08a4e..8b9fdf6 100644 --- a/src/djinterop/enginelibrary/el_storage.hpp +++ b/src/djinterop/enginelibrary/el_storage.hpp @@ -20,34 +20,31 @@ #include <string> #include <sqlite_modern_cpp.h> + #include <djinterop/semantic_version.hpp> -namespace djinterop -{ -namespace enginelibrary +#include "schema/schema.hpp" + +namespace djinterop::enginelibrary { class el_storage { public: - /// Returns a boolean indicating whether a given schema version is - /// supported. - static bool schema_version_supported(semantic_version schema_version); - - el_storage(std::string directory); + /// Construct by loading from an existing DB directory. + el_storage(const std::string& directory); - /// Create and validate schema in an empty EL storage DB. - void create_and_validate_schema(semantic_version schema_version); - - /// Returns a boolean indicating whether the EL DB has any schema. - bool schema_created() const; - - std::string directory; + /// Construct by making a new, empty DB of a given version. + el_storage(const std::string& directory, semantic_version version); + const std::string directory; // TODO - don't expose mutable SQLite connection - allow txn guard to be // obtained from el_storage by other EL classes. mutable sqlite::database db; + + const semantic_version version; + std::unique_ptr<schema::schema_creator_validator> schema_creator_validator; + int64_t last_savepoint = 0; }; -} // namespace enginelibrary -} // namespace djinterop +} // namespace djinterop::enginelibrary diff --git a/src/djinterop/enginelibrary/el_track_impl.cpp b/src/djinterop/enginelibrary/el_track_impl.cpp index 2067566..707253e 100644 --- a/src/djinterop/enginelibrary/el_track_impl.cpp +++ b/src/djinterop/enginelibrary/el_track_impl.cpp @@ -24,7 +24,7 @@ #include <djinterop/enginelibrary/el_database_impl.hpp> #include <djinterop/enginelibrary/el_track_impl.hpp> #include <djinterop/enginelibrary/el_transaction_guard_impl.hpp> -#include <djinterop/impl/util.hpp> +#include <djinterop/util.hpp> namespace djinterop { @@ -59,8 +59,7 @@ stdx::optional<int64_t> to_timestamp( return result; } -/// Calculate the quantisation number for waveforms, given a quantisation -/// number. +/// Calculate the quantisation number for waveforms, given a sample rate. /// /// A few numbers written to the waveform performance data are rounded /// to multiples of a particular "quantisation number", that is equal to diff --git a/src/djinterop/enginelibrary/schema.cpp b/src/djinterop/enginelibrary/schema.cpp deleted file mode 100644 index 7868267..0000000 --- a/src/djinterop/enginelibrary/schema.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - This file is part of libdjinterop. - - libdjinterop is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - libdjinterop is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <string> - -#include <sqlite_modern_cpp.h> - -#include <djinterop/djinterop.hpp> -#include <djinterop/enginelibrary/schema.hpp> -#include <djinterop/enginelibrary/schema_1_6_0.hpp> -#include <djinterop/enginelibrary/schema_1_7_1.hpp> - -namespace djinterop -{ -namespace enginelibrary -{ -template <typename F1, typename F2> -static void dispatch( - const semantic_version &version, F1 func_1_6_0, F2 func_1_7_1) -{ - if (version == version_1_6_0) - func_1_6_0(); - else if (version == version_1_7_1) - func_1_7_1(); - else - throw unsupported_database_version{version}; -} - -static semantic_version get_version(sqlite::database &db) -{ - semantic_version version; - db << "SELECT schemaVersionMajor, schemaVersionMinor, schemaVersionPatch " - "FROM Information" >> - std::tie(version.maj, version.min, version.pat); - return version; -} - -bool is_supported(const semantic_version &version) -{ - // TODO - add support for DB schema version 1.9.1 (new "List" tables, old - // ones as VIEWs) - return (version == version_1_6_0 || version == version_1_7_1); -} - -semantic_version verify_music_schema(sqlite::database &db) -{ - auto version = get_version(db); - dispatch( - version, [&db] { verify_music_schema_1_6_0(db); }, - [&db] { verify_music_schema_1_7_1(db); }); - - return version; -} - -semantic_version verify_performance_schema(sqlite::database &db) -{ - auto version = get_version(db); - dispatch( - version, [&db] { verify_performance_schema_1_6_0(db); }, - [&db] { verify_performance_schema_1_7_1(db); }); - - return version; -} - -void create_music_schema(sqlite::database &db, const semantic_version &version) -{ - dispatch( - version, [&db] { create_music_schema_1_6_0(db); }, - [&db] { create_music_schema_1_7_1(db); }); -} - -void create_performance_schema( - sqlite::database &db, const semantic_version &version) -{ - dispatch( - version, [&db] { create_performance_schema_1_6_0(db); }, - [&db] { create_performance_schema_1_7_1(db); }); -} - -} // namespace enginelibrary -} // namespace djinterop diff --git a/src/djinterop/enginelibrary/schema/schema.cpp b/src/djinterop/enginelibrary/schema/schema.cpp new file mode 100644 index 0000000..ede5297 --- /dev/null +++ b/src/djinterop/enginelibrary/schema/schema.cpp @@ -0,0 +1,69 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/schema/schema.hpp> +#include <djinterop/enginelibrary/schema/schema_1_6_0.hpp> +#include <djinterop/enginelibrary/schema/schema_1_7_1.hpp> +#include <djinterop/enginelibrary/schema/schema_1_9_1.hpp> +#include <djinterop/enginelibrary/schema/schema_1_11_1.hpp> +#include <djinterop/enginelibrary/schema/schema_1_13_0.hpp> +#include <djinterop/enginelibrary/schema/schema_1_13_1.hpp> +#include <djinterop/enginelibrary/schema/schema_1_13_2.hpp> +#include <djinterop/enginelibrary/schema/schema_1_15_0.hpp> +#include <djinterop/enginelibrary/schema/schema_1_17_0.hpp> +#include <djinterop/enginelibrary/schema/schema_1_18_0.hpp> + +namespace djinterop::enginelibrary::schema +{ +std::unique_ptr<schema_creator_validator> make_schema_creator_validator( + const semantic_version& version) +{ + if (version == version_1_6_0) + return std::make_unique<schema_1_6_0>(); + else if (version == version_1_7_1) + return std::make_unique<schema_1_7_1>(); + else if (version == version_1_9_1) + return std::make_unique<schema_1_9_1>(); + else if (version == version_1_11_1) + return std::make_unique<schema_1_11_1>(); + else if (version == version_1_13_0) + return std::make_unique<schema_1_13_0>(); + else if (version == version_1_13_1) + return std::make_unique<schema_1_13_1>(); + else if (version == version_1_13_2) + return std::make_unique<schema_1_13_2>(); + else if (version == version_1_15_0) + return std::make_unique<schema_1_15_0>(); + else if (version == version_1_17_0) + return std::make_unique<schema_1_17_0>(); + else if (version == version_1_18_0) + return std::make_unique<schema_1_18_0>(); + else + throw unsupported_database_version{version}; +} + +bool is_supported(const semantic_version& version) +{ + return version == version_1_6_0 || version == version_1_7_1 || + version == version_1_9_1 || version == version_1_11_1 || + version == version_1_13_0 || version == version_1_13_1 || + version == version_1_13_2 || version == version_1_15_0 || + version == version_1_17_0 || vers |