Replaced UUID storage from str to array of bytes

This commit is contained in:
emeric
2026-06-13 16:54:21 +02:00
parent 309c39a572
commit af018a9166
43 changed files with 392 additions and 140 deletions
+1
View File
@@ -2,6 +2,7 @@
add_executable(bench-core add_executable(bench-core
Core.cpp Core.cpp
TraceLoggerBench.cpp TraceLoggerBench.cpp
UUIDBench.cpp
) )
target_link_libraries(bench-core PRIVATE target_link_libraries(bench-core PRIVATE
+55
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <benchmark/benchmark.h>
#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
+59 -38
View File
@@ -19,72 +19,93 @@
#include "core/UUID.hpp" #include "core/UUID.hpp"
#include <cassert> #include <array>
#include <iomanip> #include <charconv>
#include <regex> #include <cstddef>
#include <sstream> #include <cstdint>
#include "core/Random.hpp" #include "core/Random.hpp"
#include "core/String.hpp"
namespace lms::core namespace lms::core
{ {
namespace stringUtils namespace stringUtils
{ {
template<> template<>
std::optional<UUID> std::optional<UUID> readAs(std::string_view str)
readAs(std::string_view str)
{ {
return UUID::fromString(str); return UUID::fromString(str);
} }
} // namespace stringUtils } // namespace stringUtils
namespace namespace
{ {
bool stringIsUUID(std::string_view str) // Each entry is the str index of the high hex char for that UUID byte
{ constexpr std::array<std::size_t, 16> byteOffsets{
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})" }; 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<std::byte, 16>& 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<std::byte>(byte);
}
return true;
} }
} // namespace } // namespace
UUID::UUID(std::string_view str) UUID::UUID(std::array<std::byte, 16> bytes) noexcept
: _value{ stringUtils::stringToLower(str) } : _bytes{ bytes }
{ {
} }
std::optional<UUID> UUID::fromString(std::string_view str) std::optional<UUID> UUID::fromString(std::string_view str)
{ {
if (!stringIsUUID(str)) std::array<std::byte, 16> bytes{};
if (!parseUUID(str, bytes))
return std::nullopt; return std::nullopt;
return UUID{ bytes };
}
return UUID{ str }; UUID UUID::fromBytes(std::span<const std::byte, 16> bytes) noexcept
{
std::array<std::byte, 16> 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<unsigned char>(_bytes[i]) };
s[byteOffsets[i]] = hex[b >> 4];
s[byteOffsets[i] + 1] = hex[b & 0x0F];
}
return s;
} }
UUID UUID::generate() UUID UUID::generate()
{ {
// Form is "123e4567-e89b-12d3-a456-426614174000" std::uniform_int_distribution<std::uint8_t> dist{ 0, 255 };
// TODO: store 128 bits and only convert to string when necessary auto& rng{ random::getRandGenerator() };
std::array<std::byte, binarySize> bytes{};
std::ostringstream oss; for (auto& b : bytes)
b = static_cast<std::byte>(dist(rng));
auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) { return UUID{ bytes };
for (std::size_t i{}; i < byteCount; ++i)
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(random::getRandom<std::uint8_t>(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();
} }
} // namespace lms::core } // namespace lms::core
+15 -7
View File
@@ -19,8 +19,11 @@
#pragma once #pragma once
#include <array>
#include <cstddef>
#include <functional> #include <functional>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -31,24 +34,28 @@ namespace lms::core
class UUID class UUID
{ {
public: public:
static constexpr std::size_t binarySize{ 16 };
UUID() noexcept = default;
static std::optional<UUID> fromString(std::string_view str); static std::optional<UUID> fromString(std::string_view str);
static UUID fromBytes(std::span<const std::byte, binarySize> bytes) noexcept;
static UUID generate(); static UUID generate();
std::string_view getAsString() const { return _value; } std::string toString() const;
std::span<const std::byte, binarySize> bytes() const noexcept { return _bytes; }
auto operator<=>(const UUID&) const = default; auto operator<=>(const UUID&) const = default;
private: private:
UUID(std::string_view value); explicit UUID(std::array<std::byte, binarySize> bytes) noexcept;
std::string _value; std::array<std::byte, binarySize> _bytes{};
}; };
} // namespace lms::core } // namespace lms::core
namespace lms::core::stringUtils namespace lms::core::stringUtils
{ {
template<> template<>
std::optional<UUID> std::optional<UUID> readAs(std::string_view str);
readAs(std::string_view str);
} }
namespace std namespace std
@@ -56,9 +63,10 @@ namespace std
template<> template<>
struct hash<lms::core::UUID> struct hash<lms::core::UUID>
{ {
size_t operator()(const lms::core::UUID& str) const size_t operator()(const lms::core::UUID& uuid) const noexcept
{ {
return hash<std::string_view>{}(str.getAsString()); const auto& b{ uuid.bytes() };
return hash<string_view>{}({ static_cast<const char*>(static_cast<const void*>(b.data())), b.size() });
} }
}; };
} // namespace std } // namespace std
+61 -4
View File
@@ -17,10 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <atomic> #include <unordered_set>
#include <chrono>
#include <thread>
#include <vector>
#include <gtest/gtest.h> #include <gtest/gtest.h>
@@ -28,6 +25,19 @@
namespace lms::core 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) TEST(UUID, caseInsensitive)
{ {
const std::optional<UUID> uuid1{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") }; const std::optional<UUID> uuid1{ UUID::fromString("3f51c839-bee2-4e9d-a7b7-0693e45178fc") };
@@ -37,4 +47,51 @@ namespace lms::core
EXPECT_TRUE(uuid1 >= uuid2); EXPECT_TRUE(uuid1 >= uuid2);
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{ UUID::fromString(str) };
ASSERT_TRUE(uuid);
EXPECT_EQ(uuid->toString(), str);
}
TEST(UUID, toString_lowercase)
{
const std::optional<UUID> 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{ 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{ 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 } // namespace lms::core
+40 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{ {
namespace namespace
{ {
static constexpr Version LMS_DATABASE_VERSION{ 106 }; static constexpr Version LMS_DATABASE_VERSION{ 107 };
} }
VersionInfo::VersionInfo() 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 '')"); 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) bool doDbMigration(Session& session)
{ {
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1818,6 +1856,7 @@ FROM track)");
{ 103, migrateFromV103 }, { 103, migrateFromV103 },
{ 104, migrateFromV104 }, { 104, migrateFromV104 },
{ 105, migrateFromV105 }, { 105, migrateFromV105 },
{ 106, migrateFromV106 },
}; };
bool migrationPerformed{}; bool migrationPerformed{};
+1
View File
@@ -66,6 +66,7 @@
#include "traits/ImageHashTypeTraits.hpp" #include "traits/ImageHashTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp" #include "traits/PathTraits.hpp"
#include "traits/UUIDTraits.hpp"
namespace lms::db namespace lms::db
{ {
+3 -13
View File
@@ -35,6 +35,7 @@
#include "objects/detail/Types.hpp" #include "objects/detail/Types.hpp"
#include "traits/IdTypeTraits.hpp" #include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp" #include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Artist) DBO_INSTANTIATE_TEMPLATES(lms::db::Artist)
@@ -214,7 +215,7 @@ namespace lms::db
} // namespace } // namespace
Artist::Artist(const std::string& name, const std::optional<core::UUID>& mbid) Artist::Artist(const std::string& name, const std::optional<core::UUID>& mbid)
: _mbid{ mbid ? mbid->getAsString() : "" } : _mbid{ mbid }
{ {
setName(name); setName(name);
} }
@@ -277,7 +278,7 @@ namespace lms::db
Artist::pointer Artist::find(Session& session, const core::UUID& mbid) Artist::pointer Artist::find(Session& session, const core::UUID& mbid)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.mbid = ?").bind(std::string{ mbid.getAsString() })); return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.mbid = ?").bind(mbid));
} }
Artist::pointer Artist::find(Session& session, ArtistId id) 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); utils::executeCommand(*session.getDboSession(), "UPDATE artist SET preferred_artwork_id = NULL WHERE id = ?", artistId);
} }
std::optional<core::UUID> Artist::getMBID() const
{
return core::UUID::fromString(_mbid);
}
bool Artist::hasMBID() const
{
// TODO optim this
return getMBID().has_value();
}
ObjectPtr<Artwork> Artist::getPreferredArtwork() const ObjectPtr<Artwork> Artist::getPreferredArtwork() const
{ {
return ObjectPtr<Artwork>{ _preferredArtwork }; return ObjectPtr<Artwork>{ _preferredArtwork };
@@ -112,13 +112,13 @@ namespace lms::db
query.where("a_i.mbid_matched = FALSE"); query.where("a_i.mbid_matched = FALSE");
if (!allowArtistMBIDFallback) if (!allowArtistMBIDFallback)
{ {
query.where("a.mbid <> ''"); query.where("a.mbid IS NOT NULL");
} }
else else
{ {
query.where(R"( 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)) (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 = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); 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); utils::applyRange(query, range);
+4 -3
View File
@@ -46,6 +46,7 @@
#include "traits/IdTypeTraits.hpp" #include "traits/IdTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp"
#include "traits/StringViewTraits.hpp" #include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Country) DBO_INSTANTIATE_TEMPLATES(lms::db::Country)
DBO_INSTANTIATE_TEMPLATES(lms::db::Label) DBO_INSTANTIATE_TEMPLATES(lms::db::Label)
@@ -218,7 +219,7 @@ namespace lms::db
query.where("t.codec = ?").bind(detail::getDbCodec(params.filters.codec.value())); query.where("t.codec = ?").bind(detail::getDbCodec(params.filters.codec.value()));
if (params.releaseGroupMBID) if (params.releaseGroupMBID)
query.where("group_mbid = ?").bind(params.releaseGroupMBID->getAsString()); query.where("group_mbid = ?").bind(*params.releaseGroupMBID);
switch (params.sortMethod) switch (params.sortMethod)
{ {
@@ -454,7 +455,7 @@ namespace lms::db
Release::Release(const std::string& name, const std::optional<core::UUID>& MBID) Release::Release(const std::string& name, const std::optional<core::UUID>& MBID)
: _name{ std::string(name, 0, _maxNameLength) } : _name{ std::string(name, 0, _maxNameLength) }
, _MBID{ MBID ? MBID->getAsString() : "" } , _MBID{ MBID }
{ {
} }
@@ -467,7 +468,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r from release r").where("r.mbid = ?").bind(mbid.getAsString())); return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r from release r").where("r.mbid = ?").bind(mbid));
} }
Release::pointer Release::find(Session& session, ReleaseId id) Release::pointer Release::find(Session& session, ReleaseId id)
@@ -123,13 +123,13 @@ namespace lms::db
query.where("r_a_l.artist_mbid_matched = FALSE"); query.where("r_a_l.artist_mbid_matched = FALSE");
if (!allowArtistMBIDFallback) if (!allowArtistMBIDFallback)
{ {
query.where("a.mbid <> ''"); query.where("a.mbid IS NOT NULL");
} }
else else
{ {
query.where(R"( 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)) (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 = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); 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); utils::applyRange(query, range);
+4 -3
View File
@@ -46,6 +46,7 @@
#include "traits/PartialDateTimeTraits.hpp" #include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp" #include "traits/PathTraits.hpp"
#include "traits/StringViewTraits.hpp" #include "traits/StringViewTraits.hpp"
#include "traits/UUIDTraits.hpp"
DBO_INSTANTIATE_TEMPLATES(lms::db::Track) DBO_INSTANTIATE_TEMPLATES(lms::db::Track)
@@ -392,21 +393,21 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.mbid = ?").bind(mbid.getAsString())); return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.mbid = ?").bind(mbid));
} }
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid) std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid.getAsString())); return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid));
} }
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range) RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackId>("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<TrackId>("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<TrackId>(query, range); return utils::execRangeQuery<TrackId>(query, range);
} }
@@ -165,13 +165,13 @@ namespace lms::db
query.where("t_a_l.artist_mbid_matched = FALSE"); query.where("t_a_l.artist_mbid_matched = FALSE");
if (!allowArtistMBIDFallback) if (!allowArtistMBIDFallback)
{ {
query.where("a.mbid <> ''"); query.where("a.mbid IS NOT NULL");
} }
else else
{ {
query.where(R"( 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)) (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 = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))"); 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); utils::applyRange(query, range);
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstddef>
#include <cstring>
#include <span>
#include <vector>
#include <Wt/Dbo/StdSqlTraits.h>
#include "core/UUID.hpp"
namespace Wt::Dbo
{
template<>
struct sql_value_traits<lms::core::UUID>
{
static constexpr bool specialized{ true };
using UnderlyingType = std::vector<unsigned char>;
static std::string type(SqlConnection* conn, int size)
{
return sql_value_traits<UnderlyingType, void>::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<UnderlyingType>::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<UnderlyingType>::read(buf, statement, column, size) || buf.size() != binarySize)
return false;
v = lms::core::UUID::fromBytes(std::span<const std::byte, binarySize>{ reinterpret_cast<const std::byte*>(buf.data()), binarySize });
return true;
}
};
} // namespace Wt::Dbo
@@ -141,8 +141,8 @@ namespace lms::db
// Accessors // Accessors
const std::string& getName() const { return _name; } const std::string& getName() const { return _name; }
const std::string& getSortName() const { return _sortName; } const std::string& getSortName() const { return _sortName; }
std::optional<core::UUID> getMBID() const; std::optional<core::UUID> getMBID() const { return _mbid; }
bool hasMBID() const; bool hasMBID() const { return _mbid.has_value(); }
ObjectPtr<Artwork> getPreferredArtwork() const; ObjectPtr<Artwork> getPreferredArtwork() const;
ArtworkId getPreferredArtworkId() const; ArtworkId getPreferredArtworkId() const;
@@ -152,7 +152,7 @@ namespace lms::db
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::span<const ClusterTypeId> clusterTypeIds, std::size_t size) const; std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::span<const ClusterTypeId> clusterTypeIds, std::size_t size) const;
void setName(std::string_view name); void setName(std::string_view name);
void setMBID(const std::optional<core::UUID>& mbid) { _mbid = mbid ? mbid->getAsString() : ""; } void setMBID(const std::optional<core::UUID>& mbid) { _mbid = mbid; }
void setSortName(std::string_view sortName); void setSortName(std::string_view sortName);
void setPreferredArtwork(ObjectPtr<Artwork> artwork); void setPreferredArtwork(ObjectPtr<Artwork> artwork);
@@ -174,7 +174,7 @@ namespace lms::db
std::string _name; std::string _name;
std::string _sortName; std::string _sortName;
std::string _mbid; // Musicbrainz Identifier std::optional<core::UUID> _mbid;
Wt::Dbo::ptr<Artwork> _preferredArtwork; Wt::Dbo::ptr<Artwork> _preferredArtwork;
}; };
@@ -298,8 +298,8 @@ namespace lms::db
// Accessors // Accessors
std::string_view getName() const { return _name; } std::string_view getName() const { return _name; }
std::string_view getSortName() const { return _sortName; } std::string_view getSortName() const { return _sortName; }
std::optional<core::UUID> getMBID() const { return core::UUID::fromString(_MBID); } std::optional<core::UUID> getMBID() const { return _MBID; }
std::optional<core::UUID> getGroupMBID() const { return core::UUID::fromString(_groupMBID); } std::optional<core::UUID> getGroupMBID() const { return _groupMBID; }
std::optional<std::size_t> getTotalDisc() const { return _totalDisc; } // the number of discs this release should have if complete std::optional<std::size_t> getTotalDisc() const { return _totalDisc; } // the number of discs this release should have if complete
std::chrono::milliseconds getDuration() const; std::chrono::milliseconds getDuration() const;
Wt::WDateTime getAddedTime() const; Wt::WDateTime getAddedTime() const;
@@ -325,8 +325,8 @@ namespace lms::db
// Setters // Setters
void setName(std::string_view name) { _name = name; } void setName(std::string_view name) { _name = name; }
void setSortName(std::string_view sortName) { _sortName = sortName; } void setSortName(std::string_view sortName) { _sortName = sortName; }
void setMBID(const std::optional<core::UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } void setMBID(const std::optional<core::UUID>& mbid) { _MBID = mbid; }
void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; } void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid; }
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; } void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
void clearArtistLinks(); void clearArtistLinks();
@@ -382,8 +382,8 @@ namespace lms::db
std::string _name; std::string _name;
std::string _sortName; std::string _sortName;
std::string _MBID; std::optional<core::UUID> _MBID;
std::string _groupMBID; std::optional<core::UUID> _groupMBID;
std::optional<int> _totalDisc{}; std::optional<int> _totalDisc{};
std::string _artistDisplayName; std::string _artistDisplayName;
bool _isCompilation{}; // See https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#compilation-itunes-5 bool _isCompilation{}; // See https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#compilation-itunes-5
@@ -258,8 +258,8 @@ namespace lms::db
void setName(std::string_view name); void setName(std::string_view name);
void setDate(const core::PartialDateTime& date) { _date = date; } void setDate(const core::PartialDateTime& date) { _date = date; }
void setOriginalDate(const core::PartialDateTime& date) { _originalDate = date; } void setOriginalDate(const core::PartialDateTime& date) { _originalDate = date; }
void setTrackMBID(const std::optional<core::UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } void setTrackMBID(const std::optional<core::UUID>& MBID) { _trackMBID = MBID; }
void setRecordingMBID(const std::optional<core::UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } void setRecordingMBID(const std::optional<core::UUID>& MBID) { _recordingMBID = MBID; }
void setCopyright(std::string_view copyright); void setCopyright(std::string_view copyright);
void setCopyrightURL(std::string_view copyrightURL); void setCopyrightURL(std::string_view copyrightURL);
void setAdvisory(Advisory advisory) { _advisory = advisory; } void setAdvisory(Advisory advisory) { _advisory = advisory; }
@@ -308,8 +308,8 @@ namespace lms::db
std::optional<int> getOriginalYear() const; std::optional<int> getOriginalYear() const;
const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; } const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; }
bool hasLyrics() const; bool hasLyrics() const;
std::optional<core::UUID> getTrackMBID() const { return core::UUID::fromString(_trackMBID); } std::optional<core::UUID> getTrackMBID() const { return _trackMBID; }
std::optional<core::UUID> getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); } std::optional<core::UUID> getRecordingMBID() const { return _recordingMBID; }
std::string_view getCopyright() const; std::string_view getCopyright() const;
std::string_view getCopyrightURL() const; std::string_view getCopyrightURL() const;
std::string_view getArtistDisplayName() const { return _artistDisplayName; } std::string_view getArtistDisplayName() const { return _artistDisplayName; }
@@ -413,8 +413,8 @@ namespace lms::db
std::string _name; std::string _name;
core::PartialDateTime _date; core::PartialDateTime _date;
core::PartialDateTime _originalDate; core::PartialDateTime _originalDate;
std::string _trackMBID; std::optional<core::UUID> _trackMBID;
std::string _recordingMBID; std::optional<core::UUID> _recordingMBID;
std::string _copyright; std::string _copyright;
std::string _copyrightURL; std::string _copyrightURL;
std::string _artistDisplayName; std::string _artistDisplayName;
+18 -4
View File
@@ -20,6 +20,7 @@
#include "Common.hpp" #include "Common.hpp"
#include "core/String.hpp" #include "core/String.hpp"
#include "core/UUID.hpp"
#include "database/objects/Artist.hpp" #include "database/objects/Artist.hpp"
#include "database/objects/ArtistInfo.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);)" }; CREATE INDEX starred_track_user_scrobbler_idx ON starred_track(user_id,scrobbler);)" };
const std::string_view createDummyData{ R"( 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 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'); (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 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'); (2, 'Release Y', 'mbid_release_y');
-- Inserting tracks without any associated artists or releases (Orphan Tracks) -- Inserting tracks without any associated artists or releases (Orphan Tracks)
@@ -381,6 +382,19 @@ VALUES
EXPECT_FALSE(TrackLyrics::find(session, TrackLyricsId{})); EXPECT_FALSE(TrackLyrics::find(session, TrackLyricsId{}));
EXPECT_FALSE(UIState::find(session, UIStateId{})); EXPECT_FALSE(UIState::find(session, UIStateId{}));
EXPECT_FALSE(User::find(session, UserId{})); 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 } // namespace lms::db::tests
@@ -25,7 +25,7 @@ namespace lms::feedback::listenBrainz
{ {
std::ostream& operator<<(std::ostream& os, const Feedback& feedback) std::ostream& operator<<(std::ostream& os, const Feedback& feedback)
{ {
os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast<int>(feedback.score); os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.toString() << "', score = " << static_cast<int>(feedback.score);
return os; return os;
} }
} // namespace lms::feedback::listenBrainz } // namespace lms::feedback::listenBrainz
@@ -127,7 +127,7 @@ namespace lms::feedback::listenBrainz
request.message.addHeader("Authorization", "Token " + listenBrainzToken); request.message.addHeader("Authorization", "Token " + listenBrainzToken);
Wt::Json::Object root; 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<int>(type) }; root["score"] = Wt::Json::Value{ static_cast<int>(type) };
request.message.addBodyText(Wt::Json::serialize(root)); request.message.addBodyText(Wt::Json::serialize(root));
@@ -78,7 +78,7 @@ namespace lms::podcast::utils
std::string generateRandomFileName() std::string generateRandomFileName()
{ {
return std::string{ core::UUID::generate().getAsString() }; return core::UUID::generate().toString();
} }
void removeFile(const std::filesystem::path& filePath) void removeFile(const std::filesystem::path& filePath)
@@ -620,7 +620,7 @@ namespace lms::recommendation
db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) { db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) {
const auto mbid{ artist->getMBID() }; const auto mbid{ artist->getMBID() };
// skip "Various Artists" to avoid false artist matches // 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; return;
std::unordered_set<db::TrackId> artistTrackIds; std::unordered_set<db::TrackId> artistTrackIds;
@@ -144,7 +144,7 @@ namespace lms::recommendation
db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) { db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) {
const auto mbid{ artist->getMBID() }; const auto mbid{ artist->getMBID() };
// skip "Various Artists" to avoid false artist matches // 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; return;
std::unordered_set<db::TrackId> artistTrackIds; std::unordered_set<db::TrackId> artistTrackIds;
@@ -46,7 +46,7 @@ namespace lms::scanner
{ {
os << "'" << artist->getName() << "'"; os << "'" << artist->getName() << "'";
if (const auto mbid{ artist->getMBID() }) if (const auto mbid{ artist->getMBID() })
os << " [" << mbid->getAsString() << "]"; os << " [" << mbid->toString() << "]";
return os; return os;
} }
@@ -94,7 +94,7 @@ namespace lms::scanner
db::Image::pointer image; db::Image::pointer image;
// Find anywhere, since it is supposed to be unique! // 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) if (!image)
image = foundImg; image = foundImg;
}); });
@@ -90,7 +90,7 @@ namespace lms::scanner
if (mbid) if (mbid)
{ {
// Find anywhere, since it is suppoed to be unique! // 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) if (!artwork)
artwork = db::Artwork::find(session, image->getId()); artwork = db::Artwork::find(session, image->getId());
}); });
@@ -50,7 +50,7 @@ namespace lms::scanner
const Track::pointer track{ Track::find(session, trackId) }; const Track::pointer track{ Track::find(session, trackId) };
if (auto trackMBID{ track->getTrackMBID() }) 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.stats.duplicates.emplace_back(ScanDuplicate{ track->getId(), DuplicateReason::SameTrackMBID });
context.currentStepStats.processedElems++; context.currentStepStats.processedElems++;
_progressCallback(context.currentStepStats); _progressCallback(context.currentStepStats);
@@ -31,9 +31,9 @@ namespace lms::scrobbling::listenBrainz
if (listen.trackNumber) if (listen.trackNumber)
os << ", trackNumber = " << *listen.trackNumber; os << ", trackNumber = " << *listen.trackNumber;
if (listen.trackMBID) if (listen.trackMBID)
os << ", trackMBID = '" << listen.trackMBID->getAsString() << "'"; os << ", trackMBID = '" << listen.trackMBID->toString() << "'";
if (listen.recordingMBID) if (listen.recordingMBID)
os << ", recordingMBID = '" << listen.recordingMBID->getAsString() << "'"; os << ", recordingMBID = '" << listen.recordingMBID->toString() << "'";
return os; return os;
} }
@@ -89,9 +89,9 @@ namespace lms::scrobbling::listenBrainz
if (const auto release{ track->getRelease() }) if (const auto release{ track->getRelease() })
{ {
if (auto MBID{ release->getMBID() }) 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() }) 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) for (const Artist& artist : artists)
{ {
if (artist.mbid) 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()) if (!artistMBIDs.empty())
@@ -107,10 +107,10 @@ namespace lms::scrobbling::listenBrainz
} }
if (auto MBID{ track->getTrackMBID() }) 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() }) 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<std::size_t> trackNumber{ track->getTrackNumber() }) if (const std::optional<std::size_t> trackNumber{ track->getTrackNumber() })
additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast<long long int>(*trackNumber) }; additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast<long long int>(*trackNumber) };
@@ -60,18 +60,18 @@ namespace lms::scrobbling::listenBrainz::tests
EXPECT_EQ(result.listens[0].releaseName, "Petal"); EXPECT_EQ(result.listens[0].releaseName, "Petal");
EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); EXPECT_EQ(result.listens[0].artistName, "Broke For Free");
ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); 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()); 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[0].trackNumber, 5);
EXPECT_EQ(result.listens[1].trackName, "Melt"); EXPECT_EQ(result.listens[1].trackName, "Melt");
EXPECT_EQ(result.listens[1].releaseName, "Petal"); EXPECT_EQ(result.listens[1].releaseName, "Petal");
EXPECT_EQ(result.listens[1].artistName, "Broke For Free"); EXPECT_EQ(result.listens[1].artistName, "Broke For Free");
ASSERT_TRUE(result.listens[1].recordingMBID.has_value()); 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()); 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); 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].releaseName, "Petal");
EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); EXPECT_EQ(result.listens[0].artistName, "Broke For Free");
ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); 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()); 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[0].trackNumber, 5);
} }
@@ -561,10 +561,10 @@ namespace lms::api::subsonic
switch (context.getResponseFormat()) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
artistInfoNode.setAttribute("musicBrainzId", artistMBID->getAsString()); artistInfoNode.setAttribute("musicBrainzId", artistMBID->toString());
break; break;
case ResponseFormat::xml: case ResponseFormat::xml:
artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString()); artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->toString());
break; break;
} }
} }
@@ -108,7 +108,7 @@ namespace lms::api::subsonic
const core::UUID uuid{ getTranscodeDecisionTracker().add(audioFileId, transcodeRes.targetStreamInfo) }; const core::UUID uuid{ getTranscodeDecisionTracker().add(audioFileId, transcodeRes.targetStreamInfo) };
transcodeNode.addChild("transcodeStream", createStreamDetails(transcodeRes.targetStreamInfo)); transcodeNode.addChild("transcodeStream", createStreamDetails(transcodeRes.targetStreamInfo));
transcodeNode.setAttribute("transcodeParams", uuid.getAsString()); transcodeNode.setAttribute("transcodeParams", uuid.toString());
}, },
[&](const detail::FailureResult& failureRes) { [&](const detail::FailureResult& failureRes) {
transcodeNode.setAttribute("canDirectPlay", false); transcodeNode.setAttribute("canDirectPlay", false);
+1 -1
View File
@@ -165,7 +165,7 @@ namespace lms::api::subsonic
{ {
std::optional<core::UUID> mbid{ release->getMBID() }; std::optional<core::UUID> 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) { auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) {
@@ -34,10 +34,10 @@ namespace lms::api::subsonic
switch (context.getResponseFormat()) switch (context.getResponseFormat())
{ {
case ResponseFormat::json: case ResponseFormat::json:
albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString()); albumInfo.setAttribute("musicBrainzId", releaseMBID->toString());
break; break;
case ResponseFormat::xml: case ResponseFormat::xml:
albumInfo.createChild("musicBrainzId").setValue(releaseMBID->getAsString()); albumInfo.createChild("musicBrainzId").setValue(releaseMBID->toString());
break; break;
} }
} }
+1 -1
View File
@@ -102,7 +102,7 @@ namespace lms::api::subsonic
{ {
std::optional<core::UUID> mbid{ artist->getMBID() }; std::optional<core::UUID> mbid{ artist->getMBID() };
artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); artistNode.setAttribute("musicBrainzId", mbid ? mbid->toString() : "");
} }
artistNode.setAttribute("sortName", artist->getSortName()); artistNode.setAttribute("sortName", artist->getSortName());
+1 -1
View File
@@ -192,7 +192,7 @@ namespace lms::api::subsonic
{ {
std::optional<core::UUID> mbid{ track->getRecordingMBID() }; std::optional<core::UUID> mbid{ track->getRecordingMBID() };
trackResponse.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); trackResponse.setAttribute("musicBrainzId", mbid ? mbid->toString() : "");
} }
{ {
+2 -2
View File
@@ -59,7 +59,7 @@ namespace lms::ui::utils
Wt::WLink createArtistLink(const db::Artist::pointer& artist) Wt::WLink createArtistLink(const db::Artist::pointer& artist)
{ {
if (const auto mbid{ artist->getMBID() }) 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 else
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() }; 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) Wt::WLink createReleaseLink(db::Release::pointer release)
{ {
if (const auto mbid{ release->getMBID() }) 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() }; return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() };
} }
+1 -1
View File
@@ -150,7 +150,7 @@ namespace lms::ui
response.out() << track->getAbsoluteFilePath().string(); response.out() << track->getAbsoluteFilePath().string();
if (auto mbid{ track->getTrackMBID() }) if (auto mbid{ track->getTrackMBID() })
response.out() << " (Track MBID " << mbid->getAsString() << ")"; response.out() << " (Track MBID " << mbid->toString() << ")";
response.out() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << '\n'; response.out() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << '\n';
} }
+1 -1
View File
@@ -107,7 +107,7 @@ namespace lms::ui
user.modify()->setType(db::UserType::DEMO); 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 // For demo user, we create the subsonic API auth token now as we have no other mean to create it later
core::Service<auth::IAuthTokenService>::get()->createAuthToken("subsonic", user->getId(), core::UUID::generate().getAsString()); core::Service<auth::IAuthTokenService>::get()->createAuthToken("subsonic", user->getId(), core::UUID::generate().toString());
} }
if (_authPasswordService) if (_authPasswordService)
+1 -1
View File
@@ -393,7 +393,7 @@ namespace lms::ui
if (mbid) if (mbid)
{ {
setCondition("if-has-mbid", true); 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());
} }
} }
+1 -1
View File
@@ -554,7 +554,7 @@ namespace lms::ui
if (mbid) if (mbid)
{ {
setCondition("if-has-mbid", true); 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());
} }
} }
+1 -1
View File
@@ -225,7 +225,7 @@ namespace lms::ui
} }
auto doGenerate{ [subsonicTokenPtr, updateKeyButtonStates] { 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 transaction{ LmsApp->getDbSession().createWriteTransaction() };
auto& authService{ *core::Service<auth::IAuthTokenService>::get() }; auto& authService{ *core::Service<auth::IAuthTokenService>::get() };
+4 -4
View File
@@ -78,14 +78,14 @@ namespace lms
db::Cluster::pointer generateCluster(db::Session& session, db::ClusterType::pointer clusterType) 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<db::Cluster>(clusterType, clusterName); return session.create<db::Cluster>(clusterType, clusterName);
} }
db::Artist::pointer generateArtist(db::Session& session) db::Artist::pointer generateArtist(db::Session& session)
{ {
const core::UUID artistMBID{ core::UUID::generate() }; 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<db::Artist>(artistName, artistMBID); return session.create<db::Artist>(artistName, artistMBID);
} }
@@ -94,7 +94,7 @@ namespace lms
using namespace db; using namespace db;
const core::UUID releaseMBID{ core::UUID::generate() }; 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<Release>(releaseName, releaseMBID) }; Release::pointer release{ context.session.create<Release>(releaseName, releaseMBID) };
Medium::pointer medium{ context.session.create<Medium>(release) }; Medium::pointer medium{ context.session.create<Medium>(release) };
medium.modify()->setTrackCount(params.trackCountPerRelease); medium.modify()->setTrackCount(params.trackCountPerRelease);
@@ -113,7 +113,7 @@ namespace lms
{ {
Track::pointer track{ context.session.create<Track>() }; Track::pointer track{ context.session.create<Track>() };
track.modify()->setName("Track-" + std::string{ core::UUID::generate().getAsString() }); track.modify()->setName("Track-" + core::UUID::generate().toString());
track.modify()->setMedium(medium); track.modify()->setMedium(medium);
track.modify()->setTrackNumber(i); track.modify()->setTrackNumber(i);
track.modify()->setDuration(std::chrono::seconds{ core::random::getRandom(30, 300) }); track.modify()->setDuration(std::chrono::seconds{ core::random::getRandom(30, 300) });