diff options
author | haslersn <sebastian.hasler@gmx.net> | 2019-07-03 01:59:50 +0200 |
---|---|---|
committer | haslersn <sebastian.hasler@gmx.net> | 2019-07-08 00:39:01 +0200 |
commit | ca1c35317a6da26b8903e4db450ba213746fa4a8 (patch) | |
tree | d325c6f0fbc71c2dfd63db05873635ebbbf21272 /src | |
parent | d246cfdb8e7bc75761c7972b4e1203bdbc6ca44b (diff) |
treewide: Make database/crate/track polymorphic
NOTE: The pimpl is polymorphic. This way, the user facing objects
can still be passed by value.
Diffstat (limited to 'src')
38 files changed, 2830 insertions, 1868 deletions
diff --git a/src/djinterop/crate.cpp b/src/djinterop/crate.cpp new file mode 100644 index 0000000..3cda722 --- /dev/null +++ b/src/djinterop/crate.cpp @@ -0,0 +1,94 @@ +/* + 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 <vector> + +#include <djinterop/djinterop.hpp> +#include <djinterop/impl/crate_impl.hpp> + +namespace djinterop +{ +crate::crate(const crate& other) noexcept = default; + +crate::~crate() = default; + +crate& crate::operator=(const crate& other) noexcept = default; + +void crate::add_track(track tr) const +{ + pimpl_->add_track(tr); +} + +std::vector<crate> crate::children() const +{ + return pimpl_->children(); +} + +void crate::clear_tracks() const +{ + pimpl_->clear_tracks(); +} + +std::vector<crate> crate::descendants() const +{ + return pimpl_->descendants(); +} + +int64_t crate::id() const +{ + return pimpl_->id(); +} + +bool crate::is_valid() const +{ + return pimpl_->is_valid(); +} + +std::string crate::name() const +{ + return pimpl_->name(); +} + +boost::optional<crate> crate::parent() const +{ + return pimpl_->parent(); +} + +void crate::remove_track(track tr) const +{ + pimpl_->remove_track(tr); +} + +void crate::set_name(boost::string_view name) const +{ + pimpl_->set_name(name); +} + +void crate::set_parent(boost::optional<crate> parent) const +{ + pimpl_->set_parent(parent); +} + +std::vector<track> crate::tracks() const +{ + return pimpl_->tracks(); +} + +crate::crate(std::shared_ptr<crate_impl> pimpl) : pimpl_{std::move(pimpl)} {} + +} // namespace djinterop diff --git a/src/djinterop/database.cpp b/src/djinterop/database.cpp new file mode 100644 index 0000000..4643293 --- /dev/null +++ b/src/djinterop/database.cpp @@ -0,0 +1,130 @@ +/* + 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 <sqlite_modern_cpp.h> + +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/el_database_impl.hpp> +#include <djinterop/enginelibrary/schema.hpp> +#include <djinterop/impl/database_impl.hpp> +#include <djinterop/impl/util.hpp> + +namespace djinterop +{ +database::database(const database& db) = default; + +database::~database() = default; + +database& database::operator=(const database& db) = default; + +boost::optional<crate> database::crate_by_id(int64_t id) const +{ + return pimpl_->crate_by_id(id); +} + +std::vector<crate> database::crates() const +{ + return pimpl_->crates(); +} + +std::vector<crate> database::crates_by_name(boost::string_view name) const +{ + return pimpl_->crates_by_name(name); +} + +crate database::create_crate(boost::string_view name) const +{ + return pimpl_->create_crate(name); +} + +track database::create_track(boost::string_view relative_path) const +{ + return pimpl_->create_track(relative_path); +} + +std::string database::directory() const +{ + return pimpl_->directory(); +} + +bool database::is_supported() const +{ + return pimpl_->is_supported(); +} + +void database::verify() const +{ + pimpl_->verify(); +} + +std::string database::music_db_path() const +{ + return pimpl_->music_db_path(); +} + +std::string database::perfdata_db_path() const +{ + return pimpl_->perfdata_db_path(); +} + +void database::remove_crate(crate cr) const +{ + pimpl_->remove_crate(cr); +} + +void database::remove_track(track tr) const +{ + pimpl_->remove_track(tr); +} + +std::vector<crate> database::root_crates() const +{ + return pimpl_->root_crates(); +} + +boost::optional<track> database::track_by_id(int64_t id) const +{ + return pimpl_->track_by_id(id); +} + +std::vector<track> database::tracks() const +{ + return pimpl_->tracks(); +} + +std::vector<track> database::tracks_by_relative_path( + boost::string_view relative_path) const +{ + return pimpl_->tracks_by_relative_path(relative_path); +} + +std::string database::uuid() const +{ + return pimpl_->uuid(); +} + +semantic_version database::version() const +{ + return pimpl_->version(); +} + +database::database(std::shared_ptr<database_impl> pimpl) + : pimpl_{std::move(pimpl)} +{ +} + +} // namespace djinterop diff --git a/src/djinterop/enginelibrary.cpp b/src/djinterop/enginelibrary.cpp new file mode 100644 index 0000000..7c5b3d4 --- /dev/null +++ b/src/djinterop/enginelibrary.cpp @@ -0,0 +1,73 @@ +#include <sys/stat.h> +#include <string> +#if defined(_WIN32) +#include <direct.h> +#endif + +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/el_database_impl.hpp> +#include <djinterop/enginelibrary/schema.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +database load_database(std::string directory) +{ + return database{std::make_shared<el_database_impl>(std::move(directory))}; +} + +database make_database( + std::string directory, const semantic_version& default_version) +{ + if (!is_supported(default_version)) + { + throw unsupported_database_version{"Unsupported database version", + default_version}; + } + + auto music_db_path = directory + "/m.db"; + auto perfdata_db_path = directory + "/p.db"; + + struct stat buf; + bool music_db_exists = stat(music_db_path.c_str(), &buf) == 0; + bool perfdata_db_exists = stat(perfdata_db_path.c_str(), &buf) == 0; + + switch (music_db_exists + perfdata_db_exists) + { + case 1: + throw std::runtime_error{"Only one of m.db and p.db exist"}; + break; + case 0: + { + // Ensure the target directory exists + if (stat(directory.c_str(), &buf) != 0) + { + // Create the dir +#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 to hold new database"}; + } + } + sqlite::database m_db{music_db_path}; + create_music_schema(m_db, default_version); + verify_music_schema(m_db); + sqlite::database p_db{perfdata_db_path}; + create_performance_schema(p_db, default_version); + verify_performance_schema(p_db); + break; + } + default: // both exist, so we do nothing + break; + } + + return load_database(std::move(directory)); +} + +} // namespace enginelibrary +} // namespace djinterop diff --git a/src/djinterop/enginelibrary/el_crate_impl.cpp b/src/djinterop/enginelibrary/el_crate_impl.cpp new file mode 100644 index 0000000..4ea8934 --- /dev/null +++ b/src/djinterop/enginelibrary/el_crate_impl.cpp @@ -0,0 +1,237 @@ +#include <sqlite_modern_cpp.h> + +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/el_crate_impl.hpp> +#include <djinterop/enginelibrary/el_database_impl.hpp> +#include <djinterop/enginelibrary/el_storage.hpp> +#include <djinterop/enginelibrary/el_track_impl.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +using djinterop::crate; +using djinterop::track; + +namespace +{ +void update_path( + sqlite::database& music_db, crate cr, const std::string& parent_path) +{ + // update path + std::string path = parent_path + cr.name() + ';'; + music_db << "UPDATE Crate SET path = ? WHERE id = ?" << path << cr.id(); + + // recursive call in order to update the path of indirect descendants + for (crate cr2 : cr.children()) + { + update_path(music_db, cr2, path); + } +} + +} // namespace + +el_crate_impl::el_crate_impl(std::shared_ptr<el_storage> storage, int64_t id) + : crate_impl{id}, storage_{std::move(storage)} +{ +} + +void el_crate_impl::add_track(track tr) +{ + storage_->music_db << "BEGIN"; + + storage_->music_db + << "DELETE FROM CrateTrackList WHERE crateId = ? AND trackId = ?" + << id() << tr.id(); + + storage_->music_db + << "INSERT INTO CrateTrackList (crateId, trackId) VALUES (?, ?)" << id() + << tr.id(); + + storage_->music_db << "COMMIT"; +} + +std::vector<crate> el_crate_impl::children() +{ + std::vector<crate> results; + storage_->music_db + << "SELECT crateIdChild FROM CrateHierarchy WHERE crateId = ?" + << id() >> + [&](int64_t crate_id_child) { + results.emplace_back( + std::make_shared<el_crate_impl>(storage_, crate_id_child)); + }; + return results; +} + +void el_crate_impl::clear_tracks() +{ + storage_->music_db << "DELETE FROM CrateTrackList WHERE crateId = ?" + << id(); +} + +database el_crate_impl::db() +{ + return database{std::make_shared<el_database_impl>(storage_)}; +} + +std::vector<crate> el_crate_impl::descendants() +{ + std::vector<crate> results; + storage_->music_db + << "SELECT crateOriginId FROM CrateParentList WHERE crateParentId " + "= ? AND crateOriginId <> crateParentId" + << id() >> + [&](int64_t descendant_id) { + results.push_back(crate{ + std::make_shared<el_crate_impl>(storage_, descendant_id)}); + }; + return results; +} + +bool el_crate_impl::is_valid() +{ + bool valid = false; + storage_->music_db << "SELECT COUNT(*) FROM Crate WHERE id = ?" << id() >> + [&](int count) { + if (count == 1) + { + valid = true; + } + else if (count > 1) + { + throw crate_database_inconsistency{ + "More than one crate with the same ID", id()}; + } + }; + return valid; +} + +std::string el_crate_impl::name() +{ + boost::optional<std::string> name; + storage_->music_db << "SELECT title FROM Crate WHERE id = ?" << id() >> + [&](std::string title) { + if (!name) + { + name = std::move(title); + } + else + { + throw crate_database_inconsistency{ + "More than one crate with the same ID", id()}; + } + }; + if (!name) + { + throw crate_deleted{id()}; + } + return *name; +} + +boost::optional<crate> el_crate_impl::parent() +{ + boost::optional<crate> parent; + storage_->music_db + << "SELECT crateParentId FROM CrateParentList WHERE crateOriginId " + "= ? AND crateParentId <> crateOriginId" + << id() >> + [&](int64_t parent_id) { + if (!parent) + { + parent = + crate{std::make_shared<el_crate_impl>(storage_, parent_id)}; + } + else + { + throw crate_database_inconsistency{ + "More than one parent crate for the same crate", id()}; + } + }; + return parent; +} + +void el_crate_impl::remove_track(track tr) +{ + storage_->music_db + << "DELETE FROM CrateTrackList WHERE crateId = ? AND trackId = ?" + << id() << tr.id(); +} + +void el_crate_impl::set_name(boost::string_view name) +{ + storage_->music_db << "BEGIN"; + + // obtain parent's `path` + std::string parent_path; + storage_->music_db + << "SELECT path FROM Crate c JOIN CrateParentList cpl ON c.id = " + "cpl.crateParentId WHERE cpl.crateOriginId = ? AND " + "cpl.crateOriginId <> cpl.crateParentId" + << id() >> + [&](std::string path) { + if (parent_path.empty()) + { + parent_path = std::move(path); + } + else + { + throw crate_database_inconsistency{ + "More than one parent crate for the same crate", id()}; + } + }; + + // update name and path + std::string path = std::move(parent_path) + name.data() + ';'; + storage_->music_db << "UPDATE Crate SET title = ?, path = ? WHERE id = ?" + << name.data() << path << id(); + + // call the lambda in order to update the path of direct children + for (crate cr : children()) + { + update_path(storage_->music_db, cr, path); + } + + storage_->music_db << "COMMIT"; +} + +void el_crate_impl::set_parent(boost::optional<crate> parent) +{ + storage_->music_db << "BEGIN"; + + storage_->music_db << "DELETE FROM CrateParentList WHERE crateOriginId = ?" + << id(); + + storage_->music_db << "INSERT INTO CrateParentList (crateOriginId, " + "crateParentId) VALUES (?, ?)" + << id() << (parent ? parent->id() : id()); + + storage_->music_db << "DELETE FROM CrateHierarchy WHERE crateIdChild = ?" + << id(); + + if (parent) + { + storage_->music_db + << "INSERT INTO CrateHierarchy (crateId, crateIdChild) SELECT " + "crateId, ? FROM CrateHierarchy WHERE crateIdChild = ? UNION " + "SELECT ? AS crateId, ? AS crateIdChild" + << id() << parent->id() << parent->id() << id(); + } + + storage_->music_db << "COMMIT"; +} + +std::vector<track> el_crate_impl::tracks() +{ + std::vector<track> results; + storage_->music_db << "SELECT trackId FROM CrateTrackList WHERE crateId = ?" + << id() >> + [&](int64_t track_id) { + results.emplace_back( + std::make_shared<el_track_impl>(storage_, track_id)); + }; + return results; +} + +} // namespace enginelibrary +} // namespace djinterop
\ No newline at end of file diff --git a/src/djinterop/enginelibrary/el_crate_impl.hpp b/src/djinterop/enginelibrary/el_crate_impl.hpp new file mode 100644 index 0000000..7381f0a --- /dev/null +++ b/src/djinterop/enginelibrary/el_crate_impl.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include <djinterop/impl/crate_impl.hpp> + +namespace djinterop +{ +class track; + +namespace enginelibrary +{ +class el_storage; + +class el_crate_impl : public djinterop::crate_impl +{ +public: + el_crate_impl(std::shared_ptr<el_storage> storage, int64_t id); + + void add_track(track tr) override; + std::vector<crate> children() override; + void clear_tracks() override; + database db() override; + std::vector<crate> descendants() override; + bool is_valid() override; + std::string name() override; + boost::optional<crate> parent() override; + void remove_track(track tr) override; + void set_name(boost::string_view name) override; + void set_parent(boost::optional<crate> parent) override; + std::vector<track> tracks() override; + +private: + std::shared_ptr<el_storage> storage_; +}; + +} // namespace enginelibrary +} // namespace djinterop diff --git a/src/djinterop/enginelibrary/el_database_impl.cpp b/src/djinterop/enginelibrary/el_database_impl.cpp new file mode 100644 index 0000000..ab7eaef --- /dev/null +++ b/src/djinterop/enginelibrary/el_database_impl.cpp @@ -0,0 +1,281 @@ +#include <djinterop/djinterop.hpp> +#include <djinterop/enginelibrary/el_crate_impl.hpp> +#include <djinterop/enginelibrary/el_database_impl.hpp> +#include <djinterop/enginelibrary/el_storage.hpp> +#include <djinterop/enginelibrary/el_track_impl.hpp> +#include <djinterop/enginelibrary/schema.hpp> +#include <djinterop/impl/util.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +using djinterop::crate; +using djinterop::track; + +el_database_impl::el_database_impl(std::string directory) + : storage_{std::make_shared<el_storage>(std::move(directory))} +{ +} + +el_database_impl::el_database_impl(std::shared_ptr<el_storage> storage) + : storage_{std::move(storage)} +{ +} + +boost::optional<crate> el_database_impl::crate_by_id(int64_t id) +{ + boost::optional<crate> cr; + storage_->music_db << "SELECT COUNT(*) FROM Crate WHERE id = ?" << id >> + [&](int64_t count) { + if (count == 1) + { + cr = crate{std::make_shared<el_crate_impl>(storage_, id)}; + } + else if (count > 1) + { + throw crate_database_inconsistency{ + "More than one crate with the same ID", id}; + } + }; + return cr; +} + +std::vector<crate> el_database_impl::crates() +{ + std::vector<crate> results; + storage_->music_db << "SELECT id FROM Crate ORDER BY id" >> + [&](int64_t id) { + results.push_back( + crate{std::make_shared<el_crate_impl>(storage_, id)}); + }; + return results; +} + +std::vector<crate> el_database_impl::crates_by_name(boost::string_view name) +{ + std::vector<crate> results; + storage_->music_db << "SELECT id FROM Crate WHERE title = ? ORDER BY id" + << name.data() >> + [&](int64_t id) { + results.push_back( + crate{std::make_shared<el_crate_impl>(storage_, id)}); + }; + return results; +} + +crate el_database_impl::create_crate(boost::string_view name) +{ + storage_->music_db << "BEGIN"; + + storage_->music_db << "INSERT INTO Crate (title, path) VALUES (?, ?)" + << name.data() << std::string{name} + ';'; + + int64_t id = storage_->music_db.last_insert_rowid(); + + storage_->music_db << "INSERT INTO CrateParentList (crateOriginId, " + "crateParentId) VALUES (?, ?)" + << id << id; + + storage_->music_db << "COMMIT"; + + return crate{std::make_shared<el_crate_impl>(storage_, id)}; +} + +track el_database_impl::create_track(boost::string_view relative_path) +{ + // TODO (haslersn): Should it be allowed to create two tracks with the same + // `relative_path`? + + auto filename = get_filename(relative_path); + + storage_->music_db << "BEGIN"; + + // Insert a new entry in the track table + storage_->music_db << "INSERT INTO Track (path, filename, trackType, " + "isExternalTrack, idAlbumArt) VALUES (?,?,?,?,?)" + << relative_path.data() // + << std::string{filename} // + << 1 // trackType + << 0 // isExternalTrack + << 1; // idAlbumArt + + auto id = storage_->music_db.last_insert_rowid(); + + if (version() >= version_1_7_1) + { + storage_->music_db << "UPDATE Track SET pdbImportKey = 0 WHERE id = ?" + << id; + } + + { + auto extension = get_file_extension(filename); + auto metadata_str_inserter = + storage_->music_db + << "REPLACE INTO MetaData (id, type, text) VALUES (?, ?, ?)"; + for (int64_t type : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16}) + { + boost::optional<std::string> text; + switch (type) + { + case 10: + // duration in MM:SS + // TODO (haslersn) + break; + case 13: + // extension + if (extension) + { + text = extension->to_string(); + } + break; + case 15: + case 16: + // Always 1 to our knowledge + text = "1"; + break; + } + metadata_str_inserter << id << type << text; + metadata_str_inserter++; + } + } + + { + auto metadata_int_inserter = storage_->music_db + << "REPLACE INTO MetaDataInteger (id, " + "type, value) VALUES (?, ?, ?)"; + for (int64_t type = 1; type <= 11 /* 12 */; ++type) + { + boost::optional<int64_t> value; + switch (type) + { + case 5: value = 0; break; + case 11: + // case 12: + value = 1; + break; + } + metadata_int_inserter << id << type << value; + metadata_int_inserter++; + } + } + + storage_->music_db << "COMMIT"; + + return track{std::make_shared<el_track_impl>(storage_, id)}; +} + +std::string el_database_impl::directory() +{ + return storage_->directory; +} + +bool el_database_impl::is_supported() +{ + return djinterop::enginelibrary::is_supported(version()); +} + +void el_database_impl::verify() +{ + // Verify music schema + verify_music_schema(storage_->music_db); + + // Verify performance schema + verify_performance_schema(storage_->perfdata_db); +} + +std::string el_database_impl::music_db_path() +{ + return storage_->music_db_path; +} + +std::string el_database_impl::perfdata_db_path() +{ + return storage_->perfdata_db_path; +} + +void el_database_impl::remove_crate(crate cr) +{ + storage_->music_db << "DELETE FROM Crate WHERE id = ?" << cr.id(); +} + +void el_database_impl::remove_track(track tr) +{ |