diff --git a/src/libs/core/bench/CMakeLists.txt b/src/libs/core/bench/CMakeLists.txt index 4ab4c319..7c07b875 100644 --- a/src/libs/core/bench/CMakeLists.txt +++ b/src/libs/core/bench/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(bench-core Core.cpp TraceLoggerBench.cpp + UUIDBench.cpp ) target_link_libraries(bench-core PRIVATE diff --git a/src/libs/core/bench/UUIDBench.cpp b/src/libs/core/bench/UUIDBench.cpp new file mode 100644 index 00000000..1f22f93b --- /dev/null +++ b/src/libs/core/bench/UUIDBench.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include + +#include "core/UUID.hpp" + +namespace lms::core::benchs +{ + static void BM_UUID_fromString(benchmark::State& state) + { + for (auto _ : state) + benchmark::DoNotOptimize(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc")); + } + + static void BM_UUID_fromString_invalid(benchmark::State& state) + { + for (auto _ : state) + benchmark::DoNotOptimize(UUID::fromString("not-a-valid-uuid-string-at-all-xx")); + } + + static void BM_UUID_toString(benchmark::State& state) + { + const UUID uuid{ *UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") }; + for (auto _ : state) + benchmark::DoNotOptimize(uuid.toString()); + } + + static void BM_UUID_generate(benchmark::State& state) + { + for (auto _ : state) + benchmark::DoNotOptimize(UUID::generate()); + } + + BENCHMARK(BM_UUID_fromString); + BENCHMARK(BM_UUID_fromString_invalid); + BENCHMARK(BM_UUID_toString); + BENCHMARK(BM_UUID_generate); +} // namespace lms::core::benchs diff --git a/src/libs/core/impl/UUID.cpp b/src/libs/core/impl/UUID.cpp index 756bcb1a..aafe5e4a 100644 --- a/src/libs/core/impl/UUID.cpp +++ b/src/libs/core/impl/UUID.cpp @@ -19,72 +19,93 @@ #include "core/UUID.hpp" -#include -#include -#include -#include +#include +#include +#include +#include #include "core/Random.hpp" -#include "core/String.hpp" namespace lms::core { namespace stringUtils { template<> - std::optional - readAs(std::string_view str) + std::optional readAs(std::string_view str) { return UUID::fromString(str); } } // namespace stringUtils + namespace { - bool stringIsUUID(std::string_view str) - { - static const std::regex re{ R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})" }; + // Each entry is the str index of the high hex char for that UUID byte + constexpr std::array byteOffsets{ + 0, 2, 4, 6, // group 1 (4 bytes, positions 0-7) + 9, 11, // group 2 (2 bytes, positions 9-12) + 14, 16, // group 3 (2 bytes, positions 14-17) + 19, 21, // group 4 (2 bytes, positions 19-22) + 24, 26, 28, 30, 32, 34 // group 5 (6 bytes, positions 24-35) + }; - return std::regex_match(std::cbegin(str), std::cend(str), re); + bool parseUUID(std::string_view str, std::array& out) + { + if (str.size() != 36 || str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-') + return false; + + for (std::size_t i{}; i < 16; ++i) + { + unsigned int byte{}; + const char* begin{ str.data() + byteOffsets[i] }; + const auto [ptr, ec]{ std::from_chars(begin, begin + 2, byte, 16) }; + if (ec != std::errc{} || ptr != begin + 2) + return false; + out[i] = static_cast(byte); + } + return true; } } // namespace - UUID::UUID(std::string_view str) - : _value{ stringUtils::stringToLower(str) } + UUID::UUID(std::array bytes) noexcept + : _bytes{ bytes } { } std::optional UUID::fromString(std::string_view str) { - if (!stringIsUUID(str)) + std::array bytes{}; + if (!parseUUID(str, bytes)) return std::nullopt; + return UUID{ bytes }; + } - return UUID{ str }; + UUID UUID::fromBytes(std::span bytes) noexcept + { + std::array arr{}; + std::copy(bytes.begin(), bytes.end(), arr.begin()); + return UUID{ arr }; + } + + std::string UUID::toString() const + { + static constexpr char hex[]{ "0123456789abcdef" }; + std::string s(36, '-'); + for (std::size_t i{}; i < 16; ++i) + { + const auto b{ std::to_integer(_bytes[i]) }; + s[byteOffsets[i]] = hex[b >> 4]; + s[byteOffsets[i] + 1] = hex[b & 0x0F]; + } + return s; } UUID UUID::generate() { - // Form is "123e4567-e89b-12d3-a456-426614174000" - // TODO: store 128 bits and only convert to string when necessary - - std::ostringstream oss; - - auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) { - for (std::size_t i{}; i < byteCount; ++i) - os << std::hex << std::setfill('0') << std::setw(2) << static_cast(random::getRandom(0, 255)); - } }; - - concatRandomBytes(oss, 4); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 6); - - const auto uuid{ fromString(oss.str()) }; - assert(uuid); - return uuid.value(); + std::uniform_int_distribution dist{ 0, 255 }; + auto& rng{ random::getRandGenerator() }; + std::array bytes{}; + for (auto& b : bytes) + b = static_cast(dist(rng)); + return UUID{ bytes }; } -} // namespace lms::core \ No newline at end of file +} // namespace lms::core diff --git a/src/libs/core/include/core/UUID.hpp b/src/libs/core/include/core/UUID.hpp index 422bba75..c7367ebf 100644 --- a/src/libs/core/include/core/UUID.hpp +++ b/src/libs/core/include/core/UUID.hpp @@ -19,8 +19,11 @@ #pragma once +#include +#include #include #include +#include #include #include @@ -31,24 +34,28 @@ namespace lms::core class UUID { public: + static constexpr std::size_t binarySize{ 16 }; + + UUID() noexcept = default; static std::optional fromString(std::string_view str); + static UUID fromBytes(std::span bytes) noexcept; static UUID generate(); - std::string_view getAsString() const { return _value; } + std::string toString() const; + std::span bytes() const noexcept { return _bytes; } auto operator<=>(const UUID&) const = default; private: - UUID(std::string_view value); - std::string _value; + explicit UUID(std::array bytes) noexcept; + std::array _bytes{}; }; } // namespace lms::core namespace lms::core::stringUtils { template<> - std::optional - readAs(std::string_view str); + std::optional readAs(std::string_view str); } namespace std @@ -56,9 +63,10 @@ namespace std template<> struct hash { - size_t operator()(const lms::core::UUID& str) const + size_t operator()(const lms::core::UUID& uuid) const noexcept { - return hash{}(str.getAsString()); + const auto& b{ uuid.bytes() }; + return hash{}({ static_cast(static_cast(b.data())), b.size() }); } }; } // namespace std diff --git a/src/libs/core/test/UUID.cpp b/src/libs/core/test/UUID.cpp index 9dad6f09..18d8b4d6 100644 --- a/src/libs/core/test/UUID.cpp +++ b/src/libs/core/test/UUID.cpp @@ -17,10 +17,7 @@ * along with LMS. If not, see . */ -#include -#include -#include -#include +#include #include @@ -28,6 +25,19 @@ namespace lms::core { + TEST(UUID, fromString_invalid) + { + EXPECT_FALSE(UUID::fromString("")); + EXPECT_FALSE(UUID::fromString("not-a-uuid")); + EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178f")); // too short + EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fcc")); // too long + EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7_0693e45178fc")); // wrong separator position 23 + EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178gz")); // invalid hex char 'g','z' + EXPECT_FALSE(UUID::fromString("3f51c839Xbee2-4e9d-a7b7-0693e45178fc")); // dash replaced at position 8 + EXPECT_FALSE(UUID::fromString("3f51c839-bee2X4e9d-a7b7-0693e45178fc")); // dash replaced at position 13 + EXPECT_FALSE(UUID::fromString("3f51c839-bee2-4e9dXa7b7-0693e45178fc")); // dash replaced at position 18 + } + TEST(UUID, caseInsensitive) { const std::optional uuid1{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") }; @@ -37,4 +47,51 @@ namespace lms::core EXPECT_TRUE(uuid1 >= uuid2); EXPECT_TRUE(uuid1 <= uuid2); } + + TEST(UUID, toString_roundTrip) + { + const std::string str{ "3f51c839-bee2-4e9d-a7b7-0693e45178fc" }; + const std::optional uuid{ UUID::fromString(str) }; + + ASSERT_TRUE(uuid); + EXPECT_EQ(uuid->toString(), str); + } + + TEST(UUID, toString_lowercase) + { + const std::optional uuid{ UUID::fromString("3F51C839-BEE2-4E9D-A7B7-0693E45178FC") }; + + ASSERT_TRUE(uuid); + EXPECT_EQ(uuid->toString(), "3f51c839-bee2-4e9d-a7b7-0693e45178fc"); + } + + TEST(UUID, fromBytes_roundTrip) + { + const std::optional uuid{ UUID::fromString("550e8400-e29b-41d4-a716-446655440000") }; + ASSERT_TRUE(uuid); + + const UUID fromB{ UUID::fromBytes(uuid->bytes()) }; + EXPECT_EQ(fromB, *uuid); + EXPECT_EQ(fromB.toString(), "550e8400-e29b-41d4-a716-446655440000"); + } + + TEST(UUID, bytes_size) + { + const std::optional uuid{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") }; + ASSERT_TRUE(uuid); + EXPECT_EQ(uuid->bytes().size(), 16U); + } + + TEST(UUID, generate_validString) + { + const UUID uuid{ UUID::generate() }; + const std::string s{ uuid.toString() }; + + ASSERT_EQ(s.size(), 36U); + EXPECT_EQ(s[8], '-'); + EXPECT_EQ(s[13], '-'); + EXPECT_EQ(s[18], '-'); + EXPECT_EQ(s[23], '-'); + EXPECT_TRUE(UUID::fromString(s).has_value()); + } } // namespace lms::core \ No newline at end of file diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index e7b2e54a..93e4f5f8 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -35,7 +35,7 @@ namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 106 }; + static constexpr Version LMS_DATABASE_VERSION{ 107 }; } VersionInfo::VersionInfo() @@ -1736,6 +1736,44 @@ FROM track)"); utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE "user" ADD COLUMN "lastfm_session_key" TEXT NOT NULL DEFAULT '')"); } + void migrateFromV106(Session& session) + { + dropIndexes(session); + + // Convert the 5 MBID TEXT columns to BLOB (16 raw bytes) + // unhex() returns NULL for non-hex input, so malformed values become NULL + + // artist.mbid + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist ADD COLUMN mbid_new BLOB)"); + utils::executeCommand(*session.getDboSession(), R"(UPDATE artist SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist DROP COLUMN mbid)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE artist RENAME COLUMN mbid_new TO mbid)"); + + // release.mbid + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release ADD COLUMN mbid_new BLOB)"); + utils::executeCommand(*session.getDboSession(), R"(UPDATE release SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release DROP COLUMN mbid)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release RENAME COLUMN mbid_new TO mbid)"); + + // release.group_mbid + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release ADD COLUMN group_mbid_new BLOB)"); + utils::executeCommand(*session.getDboSession(), R"(UPDATE release SET group_mbid_new = CASE WHEN group_mbid != '' THEN unhex(replace(group_mbid, '-', '')) ELSE NULL END)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release DROP COLUMN group_mbid)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE release RENAME COLUMN group_mbid_new TO group_mbid)"); + + // track.mbid + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track ADD COLUMN mbid_new BLOB)"); + utils::executeCommand(*session.getDboSession(), R"(UPDATE track SET mbid_new = CASE WHEN mbid != '' THEN unhex(replace(mbid, '-', '')) ELSE NULL END)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track DROP COLUMN mbid)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track RENAME COLUMN mbid_new TO mbid)"); + + // track.recording_mbid + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track ADD COLUMN recording_mbid_new BLOB)"); + utils::executeCommand(*session.getDboSession(), R"(UPDATE track SET recording_mbid_new = CASE WHEN recording_mbid != '' THEN unhex(replace(recording_mbid, '-', '')) ELSE NULL END)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track DROP COLUMN recording_mbid)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE track RENAME COLUMN recording_mbid_new TO recording_mbid)"); + } + bool doDbMigration(Session& session) { constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -1818,6 +1856,7 @@ FROM track)"); { 103, migrateFromV103 }, { 104, migrateFromV104 }, { 105, migrateFromV105 }, + { 106, migrateFromV106 }, }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index f1c0371f..df51253e 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -66,6 +66,7 @@ #include "traits/ImageHashTypeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp" #include "traits/PathTraits.hpp" +#include "traits/UUIDTraits.hpp" namespace lms::db { diff --git a/src/libs/database/impl/objects/Artist.cpp b/src/libs/database/impl/objects/Artist.cpp index 6e7cf657..d064e7fc 100644 --- a/src/libs/database/impl/objects/Artist.cpp +++ b/src/libs/database/impl/objects/Artist.cpp @@ -35,6 +35,7 @@ #include "objects/detail/Types.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +#include "traits/UUIDTraits.hpp" DBO_INSTANTIATE_TEMPLATES(lms::db::Artist) @@ -214,7 +215,7 @@ namespace lms::db } // namespace Artist::Artist(const std::string& name, const std::optional& mbid) - : _mbid{ mbid ? mbid->getAsString() : "" } + : _mbid{ mbid } { setName(name); } @@ -277,7 +278,7 @@ namespace lms::db Artist::pointer Artist::find(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT a FROM artist a").where("a.mbid = ?").bind(std::string{ mbid.getAsString() })); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT a FROM artist a").where("a.mbid = ?").bind(mbid)); } Artist::pointer Artist::find(Session& session, ArtistId id) @@ -386,17 +387,6 @@ AND NOT EXISTS ( utils::executeCommand(*session.getDboSession(), "UPDATE artist SET preferred_artwork_id = NULL WHERE id = ?", artistId); } - std::optional Artist::getMBID() const - { - return core::UUID::fromString(_mbid); - } - - bool Artist::hasMBID() const - { - // TODO optim this - return getMBID().has_value(); - } - ObjectPtr Artist::getPreferredArtwork() const { return ObjectPtr{ _preferredArtwork }; diff --git a/src/libs/database/impl/objects/ArtistInfo.cpp b/src/libs/database/impl/objects/ArtistInfo.cpp index b7acd5d4..736acbb2 100644 --- a/src/libs/database/impl/objects/ArtistInfo.cpp +++ b/src/libs/database/impl/objects/ArtistInfo.cpp @@ -112,13 +112,13 @@ namespace lms::db query.where("a_i.mbid_matched = FALSE"); if (!allowArtistMBIDFallback) { - query.where("a.mbid <> ''"); + query.where("a.mbid IS NOT NULL"); } else { query.where(R"( - (a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid)) - OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); + (a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid)) + OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))"); } utils::applyRange(query, range); diff --git a/src/libs/database/impl/objects/Release.cpp b/src/libs/database/impl/objects/Release.cpp index 1747fe75..8117f978 100644 --- a/src/libs/database/impl/objects/Release.cpp +++ b/src/libs/database/impl/objects/Release.cpp @@ -46,6 +46,7 @@ #include "traits/IdTypeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp" #include "traits/StringViewTraits.hpp" +#include "traits/UUIDTraits.hpp" DBO_INSTANTIATE_TEMPLATES(lms::db::Country) DBO_INSTANTIATE_TEMPLATES(lms::db::Label) @@ -218,7 +219,7 @@ namespace lms::db query.where("t.codec = ?").bind(detail::getDbCodec(params.filters.codec.value())); if (params.releaseGroupMBID) - query.where("group_mbid = ?").bind(params.releaseGroupMBID->getAsString()); + query.where("group_mbid = ?").bind(*params.releaseGroupMBID); switch (params.sortMethod) { @@ -454,7 +455,7 @@ namespace lms::db Release::Release(const std::string& name, const std::optional& MBID) : _name{ std::string(name, 0, _maxNameLength) } - , _MBID{ MBID ? MBID->getAsString() : "" } + , _MBID{ MBID } { } @@ -467,7 +468,7 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r from release r").where("r.mbid = ?").bind(mbid.getAsString())); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r from release r").where("r.mbid = ?").bind(mbid)); } Release::pointer Release::find(Session& session, ReleaseId id) diff --git a/src/libs/database/impl/objects/ReleaseArtistLink.cpp b/src/libs/database/impl/objects/ReleaseArtistLink.cpp index 6c508c4c..c6eeeb7a 100644 --- a/src/libs/database/impl/objects/ReleaseArtistLink.cpp +++ b/src/libs/database/impl/objects/ReleaseArtistLink.cpp @@ -123,13 +123,13 @@ namespace lms::db query.where("r_a_l.artist_mbid_matched = FALSE"); if (!allowArtistMBIDFallback) { - query.where("a.mbid <> ''"); + query.where("a.mbid IS NOT NULL"); } else { query.where(R"( - (a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid)) - OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); + (a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid)) + OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))"); } utils::applyRange(query, range); diff --git a/src/libs/database/impl/objects/Track.cpp b/src/libs/database/impl/objects/Track.cpp index 00aa93d5..ff8ad52d 100644 --- a/src/libs/database/impl/objects/Track.cpp +++ b/src/libs/database/impl/objects/Track.cpp @@ -46,6 +46,7 @@ #include "traits/PartialDateTimeTraits.hpp" #include "traits/PathTraits.hpp" #include "traits/StringViewTraits.hpp" +#include "traits/UUIDTraits.hpp" DBO_INSTANTIATE_TEMPLATES(lms::db::Track) @@ -392,21 +393,21 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.mbid = ?").bind(mbid.getAsString())); + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.mbid = ?").bind(mbid)); } std::vector Track::findByRecordingMBID(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid.getAsString())); + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid)); } RangeResults Track::findIdsTrackMBIDDuplicates(Session& session, std::optional range) { session.checkReadTransaction(); - auto query{ session.getDboSession()->query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.mbid") }; + auto query{ session.getDboSession()->query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid IS NOT NULL GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.mbid") }; return utils::execRangeQuery(query, range); } diff --git a/src/libs/database/impl/objects/TrackArtistLink.cpp b/src/libs/database/impl/objects/TrackArtistLink.cpp index 3366d6bc..8ad07317 100644 --- a/src/libs/database/impl/objects/TrackArtistLink.cpp +++ b/src/libs/database/impl/objects/TrackArtistLink.cpp @@ -165,13 +165,13 @@ namespace lms::db query.where("t_a_l.artist_mbid_matched = FALSE"); if (!allowArtistMBIDFallback) { - query.where("a.mbid <> ''"); + query.where("a.mbid IS NOT NULL"); } else { query.where(R"( - (a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid)) - OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); + (a.mbid IS NOT NULL AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL AND a2.mbid <> a.mbid)) + OR (a.mbid IS NULL AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid IS NOT NULL) = 1))"); } utils::applyRange(query, range); diff --git a/src/libs/database/impl/traits/UUIDTraits.hpp b/src/libs/database/impl/traits/UUIDTraits.hpp new file mode 100644 index 00000000..25d7b510 --- /dev/null +++ b/src/libs/database/impl/traits/UUIDTraits.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2025 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include "core/UUID.hpp" + +namespace Wt::Dbo +{ + template<> + struct sql_value_traits + { + static constexpr bool specialized{ true }; + using UnderlyingType = std::vector; + + static std::string type(SqlConnection* conn, int size) + { + return sql_value_traits::type(conn, size); + } + + static void bind(const lms::core::UUID& v, SqlStatement* statement, int column, int size) + { + constexpr auto binarySize{ lms::core::UUID::binarySize }; + const auto bytes{ v.bytes() }; + UnderlyingType blob(binarySize); + std::memcpy(blob.data(), bytes.data(), binarySize); + sql_value_traits::bind(blob, statement, column, size); + } + + static bool read(lms::core::UUID& v, SqlStatement* statement, int column, int size) + { + constexpr auto binarySize{ lms::core::UUID::binarySize }; + UnderlyingType buf; + if (!sql_value_traits::read(buf, statement, column, size) || buf.size() != binarySize) + return false; + + v = lms::core::UUID::fromBytes(std::span{ reinterpret_cast(buf.data()), binarySize }); + return true; + } + }; +} // namespace Wt::Dbo diff --git a/src/libs/database/include/database/objects/Artist.hpp b/src/libs/database/include/database/objects/Artist.hpp index 1d3adbda..8786c15f 100644 --- a/src/libs/database/include/database/objects/Artist.hpp +++ b/src/libs/database/include/database/objects/Artist.hpp @@ -141,8 +141,8 @@ namespace lms::db // Accessors const std::string& getName() const { return _name; } const std::string& getSortName() const { return _sortName; } - std::optional getMBID() const; - bool hasMBID() const; + std::optional getMBID() const { return _mbid; } + bool hasMBID() const { return _mbid.has_value(); } ObjectPtr getPreferredArtwork() const; ArtworkId getPreferredArtworkId() const; @@ -152,7 +152,7 @@ namespace lms::db std::vector>> getClusterGroups(std::span clusterTypeIds, std::size_t size) const; void setName(std::string_view name); - void setMBID(const std::optional& mbid) { _mbid = mbid ? mbid->getAsString() : ""; } + void setMBID(const std::optional& mbid) { _mbid = mbid; } void setSortName(std::string_view sortName); void setPreferredArtwork(ObjectPtr artwork); @@ -174,7 +174,7 @@ namespace lms::db std::string _name; std::string _sortName; - std::string _mbid; // Musicbrainz Identifier + std::optional _mbid; Wt::Dbo::ptr _preferredArtwork; }; diff --git a/src/libs/database/include/database/objects/Release.hpp b/src/libs/database/include/database/objects/Release.hpp index 53eec131..95a0d1ae 100644 --- a/src/libs/database/include/database/objects/Release.hpp +++ b/src/libs/database/include/database/objects/Release.hpp @@ -298,8 +298,8 @@ namespace lms::db // Accessors std::string_view getName() const { return _name; } std::string_view getSortName() const { return _sortName; } - std::optional getMBID() const { return core::UUID::fromString(_MBID); } - std::optional getGroupMBID() const { return core::UUID::fromString(_groupMBID); } + std::optional getMBID() const { return _MBID; } + std::optional getGroupMBID() const { return _groupMBID; } std::optional getTotalDisc() const { return _totalDisc; } // the number of discs this release should have if complete std::chrono::milliseconds getDuration() const; Wt::WDateTime getAddedTime() const; @@ -325,8 +325,8 @@ namespace lms::db // Setters void setName(std::string_view name) { _name = name; } void setSortName(std::string_view sortName) { _sortName = sortName; } - void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } - void setGroupMBID(const std::optional& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; } + void setMBID(const std::optional& mbid) { _MBID = mbid; } + void setGroupMBID(const std::optional& mbid) { _groupMBID = mbid; } void setTotalDisc(std::optional totalDisc) { _totalDisc = totalDisc; } void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } void clearArtistLinks(); @@ -382,8 +382,8 @@ namespace lms::db std::string _name; std::string _sortName; - std::string _MBID; - std::string _groupMBID; + std::optional _MBID; + std::optional _groupMBID; std::optional _totalDisc{}; std::string _artistDisplayName; bool _isCompilation{}; // See https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#compilation-itunes-5 diff --git a/src/libs/database/include/database/objects/Track.hpp b/src/libs/database/include/database/objects/Track.hpp index cc06c373..9be95a2a 100644 --- a/src/libs/database/include/database/objects/Track.hpp +++ b/src/libs/database/include/database/objects/Track.hpp @@ -258,8 +258,8 @@ namespace lms::db void setName(std::string_view name); void setDate(const core::PartialDateTime& date) { _date = date; } void setOriginalDate(const core::PartialDateTime& date) { _originalDate = date; } - void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } - void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } + void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID; } + void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID; } void setCopyright(std::string_view copyright); void setCopyrightURL(std::string_view copyrightURL); void setAdvisory(Advisory advisory) { _advisory = advisory; } @@ -308,8 +308,8 @@ namespace lms::db std::optional getOriginalYear() const; const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; } bool hasLyrics() const; - std::optional getTrackMBID() const { return core::UUID::fromString(_trackMBID); } - std::optional getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); } + std::optional getTrackMBID() const { return _trackMBID; } + std::optional getRecordingMBID() const { return _recordingMBID; } std::string_view getCopyright() const; std::string_view getCopyrightURL() const; std::string_view getArtistDisplayName() const { return _artistDisplayName; } @@ -413,8 +413,8 @@ namespace lms::db std::string _name; core::PartialDateTime _date; core::PartialDateTime _originalDate; - std::string _trackMBID; - std::string _recordingMBID; + std::optional _trackMBID; + std::optional _recordingMBID; std::string _copyright; std::string _copyrightURL; std::string _artistDisplayName; diff --git a/src/libs/database/test/Migration.cpp b/src/libs/database/test/Migration.cpp index 522e9097..82c4b00c 100644 --- a/src/libs/database/test/Migration.cpp +++ b/src/libs/database/test/Migration.cpp @@ -20,6 +20,7 @@ #include "Common.hpp" #include "core/String.hpp" +#include "core/UUID.hpp" #include "database/objects/Artist.hpp" #include "database/objects/ArtistInfo.hpp" @@ -289,14 +290,14 @@ CREATE INDEX starred_release_user_scrobbler_idx ON starred_release(user_id,scrob CREATE INDEX starred_track_user_scrobbler_idx ON starred_track(user_id,scrobbler);)" }; const std::string_view createDummyData{ R"( --- Inserting artists +-- Inserting artists (Artist A has a valid UUID MBID to verify round-trip through migration) INSERT INTO artist (version, name, sort_name, mbid) VALUES -(1, 'Artist A', 'Artist A', 'mbid_artist_a'), +(1, 'Artist A', 'Artist A', '550e8400-e29b-41d4-a716-446655440000'), (2, 'Artist B', 'Artist B', 'mbid_artist_b'); --- Inserting releases +-- Inserting releases (Release X has a valid UUID MBID to verify round-trip through migration) INSERT INTO release (version, name, mbid) VALUES -(1, 'Release X', 'mbid_release_x'), +(1, 'Release X', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'), (2, 'Release Y', 'mbid_release_y'); -- Inserting tracks without any associated artists or releases (Orphan Tracks) @@ -381,6 +382,19 @@ VALUES EXPECT_FALSE(TrackLyrics::find(session, TrackLyricsId{})); EXPECT_FALSE(UIState::find(session, UIStateId{})); EXPECT_FALSE(User::find(session, UserId{})); + + // Verify UUID MBID round-trip through V107 migration (TEXT → BLOB) + const auto artistMBID{ core::UUID::fromString("550e8400-e29b-41d4-a716-446655440000") }; + ASSERT_TRUE(artistMBID); + const auto artist{ Artist::find(session, *artistMBID) }; + ASSERT_TRUE(artist); + EXPECT_EQ(artist->getMBID(), artistMBID); + + const auto releaseMBID{ core::UUID::fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") }; + ASSERT_TRUE(releaseMBID); + const auto release{ Release::find(session, *releaseMBID) }; + ASSERT_TRUE(release); + EXPECT_EQ(release->getMBID(), releaseMBID); } } } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp index 5d231141..89380df8 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp @@ -25,7 +25,7 @@ namespace lms::feedback::listenBrainz { std::ostream& operator<<(std::ostream& os, const Feedback& feedback) { - os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast(feedback.score); + os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.toString() << "', score = " << static_cast(feedback.score); return os; } } // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp index 0ba352a2..2818ae13 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp @@ -127,7 +127,7 @@ namespace lms::feedback::listenBrainz request.message.addHeader("Authorization", "Token " + listenBrainzToken); Wt::Json::Object root; - root["recording_mbid"] = Wt::Json::Value{ std::string{ recordingMBID->getAsString() } }; + root["recording_mbid"] = Wt::Json::Value{ recordingMBID->toString() }; root["score"] = Wt::Json::Value{ static_cast(type) }; request.message.addBodyText(Wt::Json::serialize(root)); diff --git a/src/libs/services/podcast/impl/steps/Utils.cpp b/src/libs/services/podcast/impl/steps/Utils.cpp index f60adaaa..cdf9ae42 100644 --- a/src/libs/services/podcast/impl/steps/Utils.cpp +++ b/src/libs/services/podcast/impl/steps/Utils.cpp @@ -78,7 +78,7 @@ namespace lms::podcast::utils std::string generateRandomFileName() { - return std::string{ core::UUID::generate().getAsString() }; + return core::UUID::generate().toString(); } void removeFile(const std::filesystem::path& filePath) diff --git a/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp b/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp index 92a28801..c3c869c7 100644 --- a/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp +++ b/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp @@ -620,7 +620,7 @@ namespace lms::recommendation db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) { const auto mbid{ artist->getMBID() }; // skip "Various Artists" to avoid false artist matches - if (mbid && mbid->getAsString() == "89ad4ac3-39f7-470e-963a-56509c546377") + if (mbid && mbid->toString() == "89ad4ac3-39f7-470e-963a-56509c546377") return; std::unordered_set artistTrackIds; diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp index 86540518..5e4476a8 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp @@ -144,7 +144,7 @@ namespace lms::recommendation db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) { const auto mbid{ artist->getMBID() }; // skip "Various Artists" to avoid false artist matches - if (mbid && mbid->getAsString() == "89ad4ac3-39f7-470e-963a-56509c546377") + if (mbid && mbid->toString() == "89ad4ac3-39f7-470e-963a-56509c546377") return; std::unordered_set artistTrackIds; diff --git a/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp b/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp index 2a8ff7c7..83d036b6 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp @@ -46,7 +46,7 @@ namespace lms::scanner { os << "'" << artist->getName() << "'"; if (const auto mbid{ artist->getMBID() }) - os << " [" << mbid->getAsString() << "]"; + os << " [" << mbid->toString() << "]"; return os; } diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp index c5d9bf13..fec80a73 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp @@ -94,7 +94,7 @@ namespace lms::scanner db::Image::pointer image; // Find anywhere, since it is supposed to be unique! - db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid.getAsString()), [&](const db::Image::pointer foundImg) { + db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid.toString()), [&](const db::Image::pointer foundImg) { if (!image) image = foundImg; }); diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp index 341366a7..749d041d 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp @@ -90,7 +90,7 @@ namespace lms::scanner if (mbid) { // Find anywhere, since it is suppoed to be unique! - db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid->getAsString()), [&](const db::Image::pointer& image) { + db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid->toString()), [&](const db::Image::pointer& image) { if (!artwork) artwork = db::Artwork::find(session, image->getId()); }); diff --git a/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp b/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp index 3b236cd7..8c3aafae 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp @@ -50,7 +50,7 @@ namespace lms::scanner const Track::pointer track{ Track::find(session, trackId) }; if (auto trackMBID{ track->getTrackMBID() }) { - LMS_LOG(DBUPDATER, INFO, "Found duplicated track MBID [" << trackMBID->getAsString() << "], file: " << track->getAbsoluteFilePath().string() << " - " << track->getName()); + LMS_LOG(DBUPDATER, INFO, "Found duplicated track MBID [" << trackMBID->toString() << "], file: " << track->getAbsoluteFilePath().string() << " - " << track->getName()); context.stats.duplicates.emplace_back(ScanDuplicate{ track->getId(), DuplicateReason::SameTrackMBID }); context.currentStepStats.processedElems++; _progressCallback(context.currentStepStats); diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp index 322ae5f0..d1092c54 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp @@ -31,9 +31,9 @@ namespace lms::scrobbling::listenBrainz if (listen.trackNumber) os << ", trackNumber = " << *listen.trackNumber; if (listen.trackMBID) - os << ", trackMBID = '" << listen.trackMBID->getAsString() << "'"; + os << ", trackMBID = '" << listen.trackMBID->toString() << "'"; if (listen.recordingMBID) - os << ", recordingMBID = '" << listen.recordingMBID->getAsString() << "'"; + os << ", recordingMBID = '" << listen.recordingMBID->toString() << "'"; return os; } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index 62da3092..ac271a80 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -89,9 +89,9 @@ namespace lms::scrobbling::listenBrainz if (const auto release{ track->getRelease() }) { if (auto MBID{ release->getMBID() }) - additionalInfo["release_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; + additionalInfo["release_mbid"] = Wt::Json::Value{ MBID->toString() }; if (auto groupMBID{ release->getGroupMBID() }) - additionalInfo["release_group_mbid"] = Wt::Json::Value{ std::string{ groupMBID->getAsString() } }; + additionalInfo["release_group_mbid"] = Wt::Json::Value{ groupMBID->toString() }; } { @@ -99,7 +99,7 @@ namespace lms::scrobbling::listenBrainz for (const Artist& artist : artists) { if (artist.mbid) - artistMBIDs.push_back(Wt::Json::Value{ std::string{ artist.mbid->getAsString() } }); + artistMBIDs.push_back(Wt::Json::Value{ artist.mbid->toString() }); } if (!artistMBIDs.empty()) @@ -107,10 +107,10 @@ namespace lms::scrobbling::listenBrainz } if (auto MBID{ track->getTrackMBID() }) - additionalInfo["track_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; + additionalInfo["track_mbid"] = Wt::Json::Value{ MBID->toString() }; if (auto MBID{ track->getRecordingMBID() }) - additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; + additionalInfo["recording_mbid"] = Wt::Json::Value{ MBID->toString() }; if (const std::optional trackNumber{ track->getTrackNumber() }) additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast(*trackNumber) }; diff --git a/src/libs/services/scrobbling/test/Listenbrainz.cpp b/src/libs/services/scrobbling/test/Listenbrainz.cpp index 3b585d0d..8be3a0bc 100644 --- a/src/libs/services/scrobbling/test/Listenbrainz.cpp +++ b/src/libs/services/scrobbling/test/Listenbrainz.cpp @@ -60,18 +60,18 @@ namespace lms::scrobbling::listenBrainz::tests EXPECT_EQ(result.listens[0].releaseName, "Petal"); EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); - EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); + EXPECT_EQ(result.listens[0].recordingMBID->toString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); - EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[0].releaseMBID->toString(), "44915500-fbb9-4060-98ce-59a57a429edc"); EXPECT_EQ(result.listens[0].trackNumber, 5); EXPECT_EQ(result.listens[1].trackName, "Melt"); EXPECT_EQ(result.listens[1].releaseName, "Petal"); EXPECT_EQ(result.listens[1].artistName, "Broke For Free"); ASSERT_TRUE(result.listens[1].recordingMBID.has_value()); - EXPECT_EQ(result.listens[1].recordingMBID->getAsString(), "d89d042c-8cc1-4526-9080-5bab728ee15f"); + EXPECT_EQ(result.listens[1].recordingMBID->toString(), "d89d042c-8cc1-4526-9080-5bab728ee15f"); ASSERT_TRUE(result.listens[1].releaseMBID.has_value()); - EXPECT_EQ(result.listens[1].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[1].releaseMBID->toString(), "44915500-fbb9-4060-98ce-59a57a429edc"); EXPECT_EQ(result.listens[1].trackNumber, 4); } @@ -84,9 +84,9 @@ namespace lms::scrobbling::listenBrainz::tests EXPECT_EQ(result.listens[0].releaseName, "Petal"); EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); - EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); + EXPECT_EQ(result.listens[0].recordingMBID->toString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); - EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[0].releaseMBID->toString(), "44915500-fbb9-4060-98ce-59a57a429edc"); EXPECT_EQ(result.listens[0].trackNumber, 5); } diff --git a/src/libs/subsonic/impl/endpoints/Browsing.cpp b/src/libs/subsonic/impl/endpoints/Browsing.cpp index 5092e535..98c55b5e 100644 --- a/src/libs/subsonic/impl/endpoints/Browsing.cpp +++ b/src/libs/subsonic/impl/endpoints/Browsing.cpp @@ -561,10 +561,10 @@ namespace lms::api::subsonic switch (context.getResponseFormat()) { case ResponseFormat::json: - artistInfoNode.setAttribute("musicBrainzId", artistMBID->getAsString()); + artistInfoNode.setAttribute("musicBrainzId", artistMBID->toString()); break; case ResponseFormat::xml: - artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString()); + artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->toString()); break; } } diff --git a/src/libs/subsonic/impl/endpoints/Transcoding.cpp b/src/libs/subsonic/impl/endpoints/Transcoding.cpp index b9112e5b..2ef4bc08 100644 --- a/src/libs/subsonic/impl/endpoints/Transcoding.cpp +++ b/src/libs/subsonic/impl/endpoints/Transcoding.cpp @@ -108,7 +108,7 @@ namespace lms::api::subsonic const core::UUID uuid{ getTranscodeDecisionTracker().add(audioFileId, transcodeRes.targetStreamInfo) }; transcodeNode.addChild("transcodeStream", createStreamDetails(transcodeRes.targetStreamInfo)); - transcodeNode.setAttribute("transcodeParams", uuid.getAsString()); + transcodeNode.setAttribute("transcodeParams", uuid.toString()); }, [&](const detail::FailureResult& failureRes) { transcodeNode.setAttribute("canDirectPlay", false); diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 3baf3bd2..7d7fa1d6 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -165,7 +165,7 @@ namespace lms::api::subsonic { std::optional mbid{ release->getMBID() }; - albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); + albumNode.setAttribute("musicBrainzId", mbid ? mbid->toString() : ""); } auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) { diff --git a/src/libs/subsonic/impl/responses/AlbumInfo.cpp b/src/libs/subsonic/impl/responses/AlbumInfo.cpp index e58b07c7..3831d324 100644 --- a/src/libs/subsonic/impl/responses/AlbumInfo.cpp +++ b/src/libs/subsonic/impl/responses/AlbumInfo.cpp @@ -34,10 +34,10 @@ namespace lms::api::subsonic switch (context.getResponseFormat()) { case ResponseFormat::json: - albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString()); + albumInfo.setAttribute("musicBrainzId", releaseMBID->toString()); break; case ResponseFormat::xml: - albumInfo.createChild("musicBrainzId").setValue(releaseMBID->getAsString()); + albumInfo.createChild("musicBrainzId").setValue(releaseMBID->toString()); break; } } diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 577d97c7..297e6ad0 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -102,7 +102,7 @@ namespace lms::api::subsonic { std::optional mbid{ artist->getMBID() }; - artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); + artistNode.setAttribute("musicBrainzId", mbid ? mbid->toString() : ""); } artistNode.setAttribute("sortName", artist->getSortName()); diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index c20a0de0..fb8c7281 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -192,7 +192,7 @@ namespace lms::api::subsonic { std::optional mbid{ track->getRecordingMBID() }; - trackResponse.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); + trackResponse.setAttribute("musicBrainzId", mbid ? mbid->toString() : ""); } { diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index a5425d9e..4ac236a5 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -59,7 +59,7 @@ namespace lms::ui::utils Wt::WLink createArtistLink(const db::Artist::pointer& artist) { if (const auto mbid{ artist->getMBID() }) - return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string{ mbid->getAsString() } }; + return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + mbid->toString() }; else return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() }; } @@ -229,7 +229,7 @@ namespace lms::ui::utils Wt::WLink createReleaseLink(db::Release::pointer release) { if (const auto mbid{ release->getMBID() }) - return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string{ mbid->getAsString() } }; + return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + mbid->toString() }; return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() }; } diff --git a/src/lms/ui/admin/ScannerReportResource.cpp b/src/lms/ui/admin/ScannerReportResource.cpp index 8a1c369a..acc4bfbd 100644 --- a/src/lms/ui/admin/ScannerReportResource.cpp +++ b/src/lms/ui/admin/ScannerReportResource.cpp @@ -150,7 +150,7 @@ namespace lms::ui response.out() << track->getAbsoluteFilePath().string(); if (auto mbid{ track->getTrackMBID() }) - response.out() << " (Track MBID " << mbid->getAsString() << ")"; + response.out() << " (Track MBID " << mbid->toString() << ")"; response.out() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << '\n'; } diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 9af91ce6..74cbed9f 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -107,7 +107,7 @@ namespace lms::ui user.modify()->setType(db::UserType::DEMO); // For demo user, we create the subsonic API auth token now as we have no other mean to create it later - core::Service::get()->createAuthToken("subsonic", user->getId(), core::UUID::generate().getAsString()); + core::Service::get()->createAuthToken("subsonic", user->getId(), core::UUID::generate().toString()); } if (_authPasswordService) diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index e1bea7e0..8883fbd7 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -393,7 +393,7 @@ namespace lms::ui if (mbid) { setCondition("if-has-mbid", true); - bindString("mbid-link", std::string{ "https://musicbrainz.org/artist/" } + std::string{ mbid->getAsString() }); + bindString("mbid-link", "https://musicbrainz.org/artist/" + mbid->toString()); } } diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 9f1455d6..d11ed45c 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -554,7 +554,7 @@ namespace lms::ui if (mbid) { setCondition("if-has-mbid", true); - bindString("mbid-link", std::string{ "https://musicbrainz.org/release/" } + std::string{ mbid->getAsString() }); + bindString("mbid-link", "https://musicbrainz.org/release/" + mbid->toString()); } } diff --git a/src/lms/ui/settings/SubsonicSettingsView.cpp b/src/lms/ui/settings/SubsonicSettingsView.cpp index 97ad929c..27cc51e5 100644 --- a/src/lms/ui/settings/SubsonicSettingsView.cpp +++ b/src/lms/ui/settings/SubsonicSettingsView.cpp @@ -225,7 +225,7 @@ namespace lms::ui } auto doGenerate{ [subsonicTokenPtr, updateKeyButtonStates] { - const std::string newToken{ core::UUID::generate().getAsString() }; + const std::string newToken{ core::UUID::generate().toString() }; { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto& authService{ *core::Service::get() }; diff --git a/src/tools/db-generator/LmsDbGenerator.cpp b/src/tools/db-generator/LmsDbGenerator.cpp index f1c0a5bf..0d2a8054 100644 --- a/src/tools/db-generator/LmsDbGenerator.cpp +++ b/src/tools/db-generator/LmsDbGenerator.cpp @@ -78,14 +78,14 @@ namespace lms db::Cluster::pointer generateCluster(db::Session& session, db::ClusterType::pointer clusterType) { - const std::string clusterName{ std::string{ clusterType->getName() } + "-" + std::string{ core::UUID::generate().getAsString() } }; + const std::string clusterName{ std::string{ clusterType->getName() } + "-" + core::UUID::generate().toString() }; return session.create(clusterType, clusterName); } db::Artist::pointer generateArtist(db::Session& session) { const core::UUID artistMBID{ core::UUID::generate() }; - const std::string artistName{ "Artist-" + std::string{ core::UUID::generate().getAsString() } }; + const std::string artistName{ "Artist-" + core::UUID::generate().toString() }; return session.create(artistName, artistMBID); } @@ -94,7 +94,7 @@ namespace lms using namespace db; const core::UUID releaseMBID{ core::UUID::generate() }; - const std::string releaseName{ "Release-" + std::string{ core::UUID::generate().getAsString() } }; + const std::string releaseName{ "Release-" + core::UUID::generate().toString() }; Release::pointer release{ context.session.create(releaseName, releaseMBID) }; Medium::pointer medium{ context.session.create(release) }; medium.modify()->setTrackCount(params.trackCountPerRelease); @@ -113,7 +113,7 @@ namespace lms { Track::pointer track{ context.session.create() }; - track.modify()->setName("Track-" + std::string{ core::UUID::generate().getAsString() }); + track.modify()->setName("Track-" + core::UUID::generate().toString()); track.modify()->setMedium(medium); track.modify()->setTrackNumber(i); track.modify()->setDuration(std::chrono::seconds{ core::random::getRandom(30, 300) });