From 19b0856dacdd32424d6af28a65f91ee2e9bd79db Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 7 Jan 2024 15:49:30 +0100 Subject: [PATCH 01/12] Added google benchmark support + small test --- CMakeLists.txt | 10 ++ src/libs/som/CMakeLists.txt | 4 + src/libs/som/bench/CMakeLists.txt | 9 ++ src/libs/som/bench/SomBench.cpp | 54 +++++++++ src/libs/som/include/som/Matrix.hpp | 164 +++++++++++++-------------- src/libs/som/include/som/Network.hpp | 108 +++++++++--------- 6 files changed, 211 insertions(+), 138 deletions(-) create mode 100644 src/libs/som/bench/CMakeLists.txt create mode 100644 src/libs/som/bench/SomBench.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a48f53d8..80856a51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat) pkg_check_modules(Archive REQUIRED IMPORTED_TARGET libarchive) find_package(PAM) find_package(STB) +find_package(benchmark) # WT if (NOT Wt_FOUND) @@ -70,6 +71,15 @@ elseif (IMAGE_LIBRARY STREQUAL STB AND NOT STB_FOUND) endif () message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}") +# Benchmark +if (benchmark_FOUND) + set(BUILD_BENCHMARKS ON) + message(STATUS "Google Benchmark found. Building benchmarks") +else () + set(BUILD_BENCHMARKS OFF) + message(STATUS "Google Benchmark not found. Not building benchmarks") +endif() + add_subdirectory(src) install(DIRECTORY approot DESTINATION share/lms) diff --git a/src/libs/som/CMakeLists.txt b/src/libs/som/CMakeLists.txt index 938e835a..87a2d53f 100644 --- a/src/libs/som/CMakeLists.txt +++ b/src/libs/som/CMakeLists.txt @@ -22,3 +22,7 @@ install(TARGETS lmssom DESTINATION lib) if(BUILD_TESTING) add_subdirectory(test) endif() + +if (BUILD_BENCHMARKS) + add_subdirectory(bench) +endif() diff --git a/src/libs/som/bench/CMakeLists.txt b/src/libs/som/bench/CMakeLists.txt new file mode 100644 index 00000000..ccbb5bf8 --- /dev/null +++ b/src/libs/som/bench/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_executable(bench-som + SomBench.cpp + ) + +target_link_libraries(bench-som PRIVATE + lmssom + benchmark + ) diff --git a/src/libs/som/bench/SomBench.cpp b/src/libs/som/bench/SomBench.cpp new file mode 100644 index 00000000..6e1829bb --- /dev/null +++ b/src/libs/som/bench/SomBench.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 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 + +#include "som/Network.hpp" + +using namespace SOM; + +// Benchmark function +static void BM_Matrix(benchmark::State& state) +{ + std::minstd_rand randomEngine{ 42 }; + std::uniform_int_distribution distrib{ 0, 1000 }; + + Matrix matrix{ static_cast(state.range(0)), static_cast(state.range(0)) }; + + for (Coordinate x {}; x < matrix.getWidth(); ++x ) + { + for (Coordinate y {}; y < matrix.getHeight(); ++y ) + matrix.get({ x, y }) = distrib(randomEngine); + } + + for (auto _ : state) + { + // Code inside this loop is measured repeatedly + const Position pos{ matrix.getPositionMinElement([](int a, int b) { return a < b; }) }; + benchmark::DoNotOptimize(pos); + } + + // Perform cleanup here if needed +} + +// Register the benchmark with custom range +BENCHMARK(BM_Matrix)->Arg(3)->Arg(6)->Arg(12)->Arg(24); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/som/include/som/Matrix.hpp b/src/libs/som/include/som/Matrix.hpp index 4a855a72..ceb0ed70 100644 --- a/src/libs/som/include/som/Matrix.hpp +++ b/src/libs/som/include/som/Matrix.hpp @@ -26,108 +26,104 @@ namespace SOM { + using Coordinate = unsigned; -using Coordinate = unsigned; -using Norm = InputVector::value_type; + struct Position + { + Coordinate x; + Coordinate y; -struct Position -{ - Coordinate x; - Coordinate y; + bool operator<(const Position& other) const + { + if (x == other.x) + return y < other.y; + else + return x < other.x; + } - bool operator<(const Position& other) const - { - if (x == other.x) - return y < other.y; - else - return x < other.x; - } + bool operator==(const Position& other) const + { + return x == other.x && y == other.y; + } + }; - bool operator==(const Position& other) const - { - return x == other.x && y == other.y; - } -}; + template + class Matrix + { + public: + Matrix() = default; -template -class Matrix -{ - public: - Matrix() = default; + Matrix(Coordinate width, Coordinate height) + : _width{ width } + , _height{ height } + { + _values.resize(static_cast(_width) * static_cast(_height)); + } - Matrix(Coordinate width, Coordinate height) - : _width {width} - , _height {height} - { - _values.resize(static_cast(_width) * static_cast(_height)); - } + template + Matrix(Coordinate width, Coordinate height, CtrArgs&& ... args) + : _width{ width } + , _height{ height } + { + _values.resize(static_cast(_width) * static_cast(_height), T{ std::forward(args)... }); + } - template - Matrix(Coordinate width, Coordinate height, CtArgs... args) - : _width {width} - , _height {height} - { - _values.resize(static_cast(_width) * static_cast(_height), T{args...}); - } + void clear() + { + _values.clear(); + } - void clear() - { - std::vector values(static_cast(_width) * static_cast(_height)); - _values.swap(values); - } + Coordinate getHeight() const { return _height; } + Coordinate getWidth() const { return _width; } - Coordinate getHeight() const { return _height; } - Coordinate getWidth() const { return _width; } + T& get(const Position& position) + { + assert(position.x < _width); + assert(position.y < _height); + return _values[position.x + _width * position.y]; + } - T& get(const Position& position) - { - assert(position.x < _width); - assert(position.y < _height); - return _values[position.x + _width*position.y]; - } + const T& get(const Position& position) const + { + assert(position.x < _width); + assert(position.y < _height); + return _values[position.x + _width * position.y]; + } - const T& get(const Position& position) const - { - assert(position.x < _width); - assert(position.y < _height); - return _values[position.x + _width*position.y]; - } + T& operator[](const Position& position) { return get(position); } + const T& operator[](const Position& position) const { return get(position); } - T& operator[](const Position& position) { return get(position); } - const T& operator[](const Position& position) const { return get(position); } + template + Position getPositionMinElement(Func func) const + { + assert(!_values.empty()); - template - Position getPositionMinElement(Func func) const - { - assert(!_values.empty()); + const auto it{ std::min_element(_values.begin(), _values.end(), std::move(func)) }; + const auto index{ static_cast(std::distance(_values.begin(), it)) }; - auto it {std::min_element(_values.begin(), _values.end(), std::move(func))}; - auto index {static_cast(std::distance(_values.begin(), it))}; + return Position{ index % _height, index / _height }; + } - return {index % _height, index / _height}; - } - - private: - Coordinate _width {}; - Coordinate _height {}; - std::vector _values; -}; + private: + Coordinate _width{}; + Coordinate _height{}; + std::vector _values; + }; } // ns SOM -namespace std { - -template<> -class hash +namespace std { - public: - size_t operator()(const SOM::Position& s) const - { - size_t h1 = std::hash()(s.x); - size_t h2 = std::hash()(s.y); - return h1 ^ (h2 << 1); - } -}; - + template<> + class hash + { + public: + size_t operator()(const SOM::Position& s) const + { + size_t h1 = std::hash()(s.x); + size_t h2 = std::hash()(s.y); + return h1 ^ (h2 << 1); + } + }; } // ns std diff --git a/src/libs/som/include/som/Network.hpp b/src/libs/som/include/som/Network.hpp index b46abb09..bd0cc032 100644 --- a/src/libs/som/include/som/Network.hpp +++ b/src/libs/som/include/som/Network.hpp @@ -30,78 +30,78 @@ namespace SOM { -using LearningFactor = InputVector::value_type; + using LearningFactor = InputVector::value_type; + using Norm = InputVector::value_type; -void checkSameDimensions(const InputVector& a, const InputVector& b); -void checkSameDimensions(const InputVector& a, std::size_t inputDimCount); -std::ostream& operator<<(std::ostream& os, const InputVector& a); + void checkSameDimensions(const InputVector& a, const InputVector& b); + void checkSameDimensions(const InputVector& a, std::size_t inputDimCount); + std::ostream& operator<<(std::ostream& os, const InputVector& a); + class Network + { + public: + // Init a network with random values + Network(Coordinate width, Coordinate height, std::size_t inputDimCount); -class Network -{ - public: - // Init a network with random values - Network(Coordinate width, Coordinate height, std::size_t inputDimCount); + Coordinate getWidth() const { return _refVectors.getWidth(); } + Coordinate getHeight() const { return _refVectors.getHeight(); } + std::size_t getInputDimCount() const { return _inputDimCount; } + const InputVector& getDataWeights() const { return _weights; } - Coordinate getWidth() const { return _refVectors.getWidth(); } - Coordinate getHeight() const { return _refVectors.getHeight(); } - std::size_t getInputDimCount() const { return _inputDimCount; } - const InputVector& getDataWeights() const { return _weights; } + // Set weight for each dimension (default is 1 for each weight) + void setDataWeights(const InputVector& weights); - // Set weight for each dimension (default is 1 for each weight) - void setDataWeights(const InputVector& weights); + // use this to manually construct a network without training + void setRefVector(const Position& position, const InputVector& data); - // use this to manually construct a network without training - void setRefVector(const Position& position, const InputVector& data); + // data must be normalized + struct CurrentIteration + { + std::size_t idIteration; + std::size_t iterationCount; + }; + using ProgressCallback = std::function; + using RequestStopCallback = std::function; + void train(const std::vector& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{}); - // data must be normalized - struct CurrentIteration - { - std::size_t idIteration; - std::size_t iterationCount; - }; - using ProgressCallback = std::function; - using RequestStopCallback = std::function; - void train(const std::vector& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{}); + const InputVector& getRefVector(const Position& position) const; + Position getClosestRefVectorPosition(const InputVector& data) const; + std::optional getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const; - const InputVector& getRefVector(const Position& position) const; - Position getClosestRefVectorPosition(const InputVector& data) const; - std::optional getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const; + std::optional getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const; - std::optional getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const; + InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const; - InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const; + InputVector::Distance computeRefVectorsDistanceMean() const; + InputVector::Distance computeRefVectorsDistanceMedian() const; - InputVector::Distance computeRefVectorsDistanceMean() const; - InputVector::Distance computeRefVectorsDistanceMedian() const; + void dump(std::ostream& os) const; - void dump(std::ostream& os) const; + // For each ref vector, update formula is: + // i is the current iteration + // refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector) - // For each ref vector, update formula is: - // i is the current iteration - // refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector) + using DistanceFunc = std::function; + void setDistanceFunc(DistanceFunc distanceFunc); + DistanceFunc getDistanceFunc() { return _distanceFunc; } - using DistanceFunc = std::function; - void setDistanceFunc(DistanceFunc distanceFunc); - DistanceFunc getDistanceFunc() { return _distanceFunc; } + using LearningFactorFunc = std::function; + void setLearningFactorFunc(LearningFactorFunc learningFactorFunc); - using LearningFactorFunc = std::function; - void setLearningFactorFunc(LearningFactorFunc learningFactorFunc); + using NeighbourhoodFunc = std::function; + void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc); - using NeighbourhoodFunc = std::function; - void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc); + private: - private: + void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration); - void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration); + std::size_t _inputDimCount{}; + InputVector _weights; // weight for each dimension + Matrix _refVectors; - std::size_t _inputDimCount {}; - InputVector _weights; // weight for each dimension - Matrix _refVectors; - - DistanceFunc _distanceFunc; - LearningFactorFunc _learningFactorFunc; - NeighbourhoodFunc _neighbourhoodFunc; -}; + DistanceFunc _distanceFunc; + LearningFactorFunc _learningFactorFunc; + NeighbourhoodFunc _neighbourhoodFunc; + }; } // namespace SOM From 6cfa4e9de66a3b6b30efbda435867b438421b8ca Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 1 Feb 2024 22:34:36 +0100 Subject: [PATCH 02/12] Switched to C++20 --- CMakeLists.txt | 2 +- cmake/modules/FindFilesystem.cmake | 3 - .../av/impl/TranscodingResourceHandler.cpp | 2 +- .../listenbrainz/FeedbacksSynchronizer.cpp | 6 +- .../services/scanner/impl/ScannerService.cpp | 6 +- .../impl/listenbrainz/ListensSynchronizer.cpp | 348 +++++++++--------- src/libs/utils/impl/http/SendQueue.cpp | 2 +- src/lms/ui/Auth.cpp | 2 +- src/lms/ui/LmsApplication.cpp | 6 +- src/lms/ui/ModalManager.cpp | 2 +- src/lms/ui/NotificationContainer.cpp | 2 +- src/lms/ui/PlayQueue.cpp | 22 +- src/lms/ui/admin/InitWizardView.cpp | 2 +- src/lms/ui/admin/MediaLibrariesView.cpp | 14 +- src/lms/ui/admin/MediaLibraryModal.cpp | 4 +- src/lms/ui/admin/UsersView.cpp | 4 +- src/lms/ui/explore/ArtistView.cpp | 14 +- src/lms/ui/explore/ArtistsView.cpp | 2 +- src/lms/ui/explore/Filters.cpp | 207 +++++------ src/lms/ui/explore/Filters.hpp | 27 +- src/lms/ui/explore/ReleaseView.cpp | 30 +- src/lms/ui/explore/ReleasesView.cpp | 8 +- src/lms/ui/explore/SearchView.cpp | 4 +- src/lms/ui/explore/TrackListView.cpp | 12 +- src/lms/ui/explore/TrackListsView.cpp | 2 +- src/lms/ui/explore/TracksView.cpp | 10 +- 26 files changed, 365 insertions(+), 378 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a48f53d8..1f53fce2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(lms) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) if (UNIX) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") diff --git a/cmake/modules/FindFilesystem.cmake b/cmake/modules/FindFilesystem.cmake index 221130f8..bdccdb95 100755 --- a/cmake/modules/FindFilesystem.cmake +++ b/cmake/modules/FindFilesystem.cmake @@ -110,9 +110,6 @@ cmake_push_check_state() set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY}) -# All of our tests required C++17 or later -set(CMAKE_CXX_STANDARD 17) - # Normalize and check the component list we were given set(want_components ${Filesystem_FIND_COMPONENTS}) if(Filesystem_FIND_COMPONENTS STREQUAL "") diff --git a/src/libs/av/impl/TranscodingResourceHandler.cpp b/src/libs/av/impl/TranscodingResourceHandler.cpp index 4fbbb518..8de8f705 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.cpp +++ b/src/libs/av/impl/TranscodingResourceHandler.cpp @@ -68,7 +68,7 @@ namespace Av::Transcoding { Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; continuation->waitForMoreData(); - _transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead) + _transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) { LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back"); diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp index 68842f6f..949cc04d 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp @@ -131,9 +131,9 @@ namespace Feedback::ListenBrainz request.message.addBodyText(Wt::Json::serialize(root)); request.message.addHeader("Content-Type", "application/json"); - request.onSuccessFunc = [=](std::string_view /*msgBody*/) + request.onSuccessFunc = [this, type, starredTrackId](std::string_view /*msgBody*/) { - _strand.dispatch([=] + _strand.dispatch([this, type, starredTrackId] { onFeedbackSent(type, starredTrackId); }); @@ -404,7 +404,7 @@ namespace Feedback::ListenBrainz } }); }; - request.onFailureFunc = [=, &context] + request.onFailureFunc = [this, &context] { onSyncEnded(context); }; diff --git a/src/libs/services/scanner/impl/ScannerService.cpp b/src/libs/services/scanner/impl/ScannerService.cpp index a11c05a2..c37bc7c3 100644 --- a/src/libs/services/scanner/impl/ScannerService.cpp +++ b/src/libs/services/scanner/impl/ScannerService.cpp @@ -139,7 +139,7 @@ namespace Scanner void ScannerService::requestImmediateScan(bool force) { abortScan(); - _ioService.post([=]() + _ioService.post([this, force] { if (_abortScan) return; @@ -156,7 +156,7 @@ namespace Scanner void ScannerService::requestReload() { abortScan(); - _ioService.post([=]() + _ioService.post([this]() { if (_abortScan) return; @@ -234,7 +234,7 @@ namespace Scanner void ScannerService::scheduleScan(bool force, const Wt::WDateTime& dateTime) { - auto cb{ [=](boost::system::error_code ec) + auto cb{ [this, force](boost::system::error_code ec) { if (ec) return; diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index a964686d..dc617454 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -40,175 +40,173 @@ #include "Utils.hpp" -namespace -{ - using namespace Scrobbling::ListenBrainz; - - std::optional listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint) - { - auto transaction{ session.createReadTransaction() }; - - const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) }; - if (!track) - return std::nullopt; - - auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) }; - if (artists.empty()) - artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist }); - - if (artists.empty()) - { - LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist"); - return std::nullopt; - } - - Wt::Json::Object additionalInfo; - additionalInfo["listening_from"] = "LMS"; - additionalInfo["duration_ms"] = std::chrono::duration_cast(track->getDuration()).count(); - if (track->getRelease()) - { - if (auto MBID{ track->getRelease()->getMBID() }) - additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; - } - - { - Wt::Json::Array artistMBIDs; - for (const Database::Artist::pointer& artist : artists) - { - if (auto MBID{ artist->getMBID() }) - artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} }); - } - - if (!artistMBIDs.empty()) - additionalInfo["artist_mbids"] = std::move(artistMBIDs); - } - - if (auto MBID{ track->getTrackMBID() }) - additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; - - if (auto MBID{ track->getRecordingMBID() }) - additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; - - if (const std::optional trackNumber{ track->getTrackNumber() }) - additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast(*trackNumber) }; - - Wt::Json::Object trackMetadata; - trackMetadata["additional_info"] = std::move(additionalInfo); - trackMetadata["artist_name"] = Wt::Json::Value{ artists.front()->getName() }; - trackMetadata["track_name"] = Wt::Json::Value{ track->getName() }; - if (track->getRelease()) - trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() }; - - Wt::Json::Object payload; - payload["track_metadata"] = std::move(trackMetadata); - if (timePoint.isValid()) - payload["listened_at"] = Wt::Json::Value{ static_cast(timePoint.toTime_t()) }; - - return payload; - } - - std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType) - { - std::string res; - - std::optional payload{ listenToJsonPayload(session, listen, timePoint) }; - if (!payload) - return res; - - Wt::Json::Object root; - root["listen_type"] = Wt::Json::Value{ std::string {listenType} }; - root["payload"] = Wt::Json::Array{ std::move(*payload) }; - - res = Wt::Json::serialize(root); - return res; - } - - std::optional parseListenCount(std::string_view msgBody) - { - try - { - Wt::Json::Object root; - Wt::Json::parse(std::string{ msgBody }, root); - - const Wt::Json::Object& payload{ static_cast(root.get("payload")) }; - return static_cast(payload.get("count")); - } - catch (const Wt::WException& e) - { - LOG(ERROR, "Cannot parse listen count response: " << e.what()); - return std::nullopt; - } - } - - Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen) - { - using namespace Database; - - auto transaction{ session.createReadTransaction() }; - - // first try to match using track MBID, and then fallback on possibly ambiguous info - if (listen.trackMBID) - { - const auto tracks{ Track::findByMBID(session, *listen.trackMBID) }; - // if duplicated files, do not record it (let the user correct its database) - if (tracks.size() == 1) - { - LOG(DEBUG, "Matched listen '" << listen << "' using track MBID"); - return tracks.front()->getId(); - } - else if (tracks.size() > 1) - { - LOG(DEBUG, "Too many matches for listen '" << listen << "' using track MBID!"); - return {}; - } - } - - if (listen.recordingMBID) - { - const auto tracks{ Track::findByRecordingMBID(session, *listen.recordingMBID) }; - // if duplicated files, do not record it (let the user correct its database) - if (tracks.size() == 1) - { - LOG(DEBUG, "Matched listen '" << listen << "' using recording MBID"); - return tracks.front()->getId(); - } - else if (tracks.size() > 1) - { - LOG(DEBUG, "Too many matches for listen '" << listen << "' using recording MBID!"); - return {}; - } - } - - assert(!listen.trackName.empty() && !listen.artistName.empty()); - - // TODO check release MBID? - Track::FindParameters params; - params.setName(listen.trackName); - params.setReleaseName(listen.releaseName); - params.setArtistName(listen.artistName); - if (listen.trackNumber) - params.setTrackNumber(*listen.trackNumber); - - const auto tracks{ Track::findIds(session, params) }; - // conservative behavior: in case of multiple matches: reject - if (tracks.results.size() == 1) - { - LOG(DEBUG, "Matched listen '" << listen << "' using metadata"); - return tracks.results.front(); - } - else if (tracks.results.size() > 1) - { - LOG(DEBUG, "Too many matches for listen '" << listen << "' using metadata"); - return {}; - } - - LOG(DEBUG, "No match for listen '" << listen << "'"); - return {}; - } -} - namespace Scrobbling::ListenBrainz { + namespace + { + std::optional listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint) + { + auto transaction{ session.createReadTransaction() }; + + const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) }; + if (!track) + return std::nullopt; + + auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) }; + if (artists.empty()) + artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist }); + + if (artists.empty()) + { + LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist"); + return std::nullopt; + } + + Wt::Json::Object additionalInfo; + additionalInfo["listening_from"] = "LMS"; + additionalInfo["duration_ms"] = std::chrono::duration_cast(track->getDuration()).count(); + if (track->getRelease()) + { + if (auto MBID{ track->getRelease()->getMBID() }) + additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + } + + { + Wt::Json::Array artistMBIDs; + for (const Database::Artist::pointer& artist : artists) + { + if (auto MBID{ artist->getMBID() }) + artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} }); + } + + if (!artistMBIDs.empty()) + additionalInfo["artist_mbids"] = std::move(artistMBIDs); + } + + if (auto MBID{ track->getTrackMBID() }) + additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + + if (auto MBID{ track->getRecordingMBID() }) + additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + + if (const std::optional trackNumber{ track->getTrackNumber() }) + additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast(*trackNumber) }; + + Wt::Json::Object trackMetadata; + trackMetadata["additional_info"] = std::move(additionalInfo); + trackMetadata["artist_name"] = Wt::Json::Value{ artists.front()->getName() }; + trackMetadata["track_name"] = Wt::Json::Value{ track->getName() }; + if (track->getRelease()) + trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() }; + + Wt::Json::Object payload; + payload["track_metadata"] = std::move(trackMetadata); + if (timePoint.isValid()) + payload["listened_at"] = Wt::Json::Value{ static_cast(timePoint.toTime_t()) }; + + return payload; + } + + std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType) + { + std::string res; + + std::optional payload{ listenToJsonPayload(session, listen, timePoint) }; + if (!payload) + return res; + + Wt::Json::Object root; + root["listen_type"] = Wt::Json::Value{ std::string {listenType} }; + root["payload"] = Wt::Json::Array{ std::move(*payload) }; + + res = Wt::Json::serialize(root); + return res; + } + + std::optional parseListenCount(std::string_view msgBody) + { + try + { + Wt::Json::Object root; + Wt::Json::parse(std::string{ msgBody }, root); + + const Wt::Json::Object& payload{ static_cast(root.get("payload")) }; + return static_cast(payload.get("count")); + } + catch (const Wt::WException& e) + { + LOG(ERROR, "Cannot parse listen count response: " << e.what()); + return std::nullopt; + } + } + + Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen) + { + using namespace Database; + + auto transaction{ session.createReadTransaction() }; + + // first try to match using track MBID, and then fallback on possibly ambiguous info + if (listen.trackMBID) + { + const auto tracks{ Track::findByMBID(session, *listen.trackMBID) }; + // if duplicated files, do not record it (let the user correct its database) + if (tracks.size() == 1) + { + LOG(DEBUG, "Matched listen '" << listen << "' using track MBID"); + return tracks.front()->getId(); + } + else if (tracks.size() > 1) + { + LOG(DEBUG, "Too many matches for listen '" << listen << "' using track MBID!"); + return {}; + } + } + + if (listen.recordingMBID) + { + const auto tracks{ Track::findByRecordingMBID(session, *listen.recordingMBID) }; + // if duplicated files, do not record it (let the user correct its database) + if (tracks.size() == 1) + { + LOG(DEBUG, "Matched listen '" << listen << "' using recording MBID"); + return tracks.front()->getId(); + } + else if (tracks.size() > 1) + { + LOG(DEBUG, "Too many matches for listen '" << listen << "' using recording MBID!"); + return {}; + } + } + + assert(!listen.trackName.empty() && !listen.artistName.empty()); + + // TODO check release MBID? + Track::FindParameters params; + params.setName(listen.trackName); + params.setReleaseName(listen.releaseName); + params.setArtistName(listen.artistName); + if (listen.trackNumber) + params.setTrackNumber(*listen.trackNumber); + + const auto tracks{ Track::findIds(session, params) }; + // conservative behavior: in case of multiple matches: reject + if (tracks.results.size() == 1) + { + LOG(DEBUG, "Matched listen '" << listen << "' using metadata"); + return tracks.results.front(); + } + else if (tracks.results.size() > 1) + { + LOG(DEBUG, "Too many matches for listen '" << listen << "' using metadata"); + return {}; + } + + LOG(DEBUG, "No match for listen '" << listen << "'"); + return {}; + } + } + ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client) : _ioContext{ ioContext } , _db{ db } @@ -244,13 +242,13 @@ namespace Scrobbling::ListenBrainz saveListen(timedListen, Database::SyncState::PendingAdd); request.priority = Http::ClientRequestParameters::Priority::Normal; - request.onSuccessFunc = [=](std::string_view) + request.onSuccessFunc = [this, timedListen](std::string_view) { - _strand.dispatch([=] + _strand.dispatch([this, timedListen] { if (saveListen(timedListen, Database::SyncState::Synchronized)) { - UserContext& context{ getUserContext(listen.userId) }; + UserContext& context{ getUserContext(timedListen.userId) }; if (context.listenCount) (*context.listenCount)++; } @@ -484,11 +482,11 @@ namespace Scrobbling::ListenBrainz Http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count"; request.priority = Http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [=, &context](std::string_view msgBody) + request.onSuccessFunc = [this, &context](std::string_view msgBody) { - _strand.dispatch([=, &context] + const auto listenCount{ parseListenCount(msgBody) }; + _strand.dispatch([this, listenCount, &context] { - const auto listenCount = parseListenCount(msgBody); if (listenCount) LOG(DEBUG, "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount); @@ -520,7 +518,7 @@ namespace Scrobbling::ListenBrainz Http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t()); request.priority = Http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [=, &context](std::string_view msgBody) + request.onSuccessFunc = [this, &context](std::string_view msgBody) { processGetListensResponse(msgBody, context); if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid()) @@ -531,7 +529,7 @@ namespace Scrobbling::ListenBrainz enqueGetListens(context); }; - request.onFailureFunc = [=, &context] + request.onFailureFunc = [this, &context] { onSyncEnded(context); }; diff --git a/src/libs/utils/impl/http/SendQueue.cpp b/src/libs/utils/impl/http/SendQueue.cpp index 91a4a01a..c4e77df0 100644 --- a/src/libs/utils/impl/http/SendQueue.cpp +++ b/src/libs/utils/impl/http/SendQueue.cpp @@ -66,7 +66,7 @@ namespace Http { _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) { - _strand.dispatch([=, msg = std::move(msg)] + _strand.dispatch([this, ec, msg = std::move(msg)] { onClientDone(ec, msg); }); diff --git a/src/lms/ui/Auth.cpp b/src/lms/ui/Auth.cpp index 53259a8c..afeda877 100644 --- a/src/lms/ui/Auth.cpp +++ b/src/lms/ui/Auth.cpp @@ -171,7 +171,7 @@ Auth::Auth() { auto model {std::make_shared()}; - auto processAuth = [=]() + auto processAuth = [this, model] { updateModel(model.get()); diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 367cc647..9c5be7a4 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -448,12 +448,12 @@ namespace UserInterface _playQueue = mainStack->addWidget(std::move(playQueue)); mainStack->addNew(); - searchEdit->enterPressed().connect([=] + searchEdit->enterPressed().connect([this] { setInternalPath("/search", true); }); - searchEdit->textInput().connect([=] + searchEdit->textInput().connect([this, explore, searchEdit] { setInternalPath("/search", true); explore->search(searchEdit->text()); @@ -518,7 +518,7 @@ namespace UserInterface const bool isAdmin{ getUserType() == Database::UserType::ADMIN }; if (isAdmin) { - _scannerEvents.scanComplete.connect([=](const Scanner::ScanStats& stats) + _scannerEvents.scanComplete.connect([this](const Scanner::ScanStats& stats) { notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), diff --git a/src/lms/ui/ModalManager.cpp b/src/lms/ui/ModalManager.cpp index 1de4bbb5..26741613 100644 --- a/src/lms/ui/ModalManager.cpp +++ b/src/lms/ui/ModalManager.cpp @@ -25,7 +25,7 @@ namespace UserInterface ModalManager::ModalManager() : _closed{ this, "closed" } { - _closed.connect([=](const std::string& id) + _closed.connect([this](const std::string& id) { LMS_LOG(UI, DEBUG, "Received closed for id '" << id << "'"); for (int i{}; i < count(); ++i) diff --git a/src/lms/ui/NotificationContainer.cpp b/src/lms/ui/NotificationContainer.cpp index 27628e22..d7a96564 100644 --- a/src/lms/ui/NotificationContainer.cpp +++ b/src/lms/ui/NotificationContainer.cpp @@ -80,7 +80,7 @@ namespace UserInterface { NotificationWidget* notification{ addNew(type, category, message, duration) }; - notification->closed.connect([=] + notification->closed.connect([this, notification] { removeWidget(notification); }); diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 1a7496e4..803e2501 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -126,13 +126,13 @@ namespace UserInterface addFunction("tr", &Wt::WTemplate::Functions::tr); Wt::WPushButton* clearBtn{ bindNew("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML) }; - clearBtn->clicked().connect([=] + clearBtn->clicked().connect([this] { clearTracks(); }); Wt::WPushButton* saveBtn{ bindNew("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML) }; - saveBtn->clicked().connect([=] + saveBtn->clicked().connect([this] { saveAsTrackList(); }); @@ -145,7 +145,7 @@ namespace UserInterface }); Wt::WPushButton* shuffleBtn{ bindNew("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML) }; - shuffleBtn->clicked().connect([=] + shuffleBtn->clicked().connect([this] { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -163,7 +163,7 @@ namespace UserInterface }); _repeatBtn = bindNew("repeat-btn"); - _repeatBtn->clicked().connect([=] + _repeatBtn->clicked().connect([this] { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -177,7 +177,7 @@ namespace UserInterface } _radioBtn = bindNew("radio-btn"); - _radioBtn->clicked().connect([=] + _radioBtn->clicked().connect([this] { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -203,7 +203,7 @@ namespace UserInterface _nbTracks = bindNew("track-count"); _duration = bindNew("duration"); - LmsApp->getMediaPlayer().settingsLoaded.connect([=] + LmsApp->getMediaPlayer().settingsLoaded.connect([this] { if (_mediaPlayerSettingsLoaded) return; @@ -220,7 +220,7 @@ namespace UserInterface loadTrack(trackPos, false); }); - LmsApp->preQuit().connect([=] + LmsApp->preQuit().connect([this] { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -537,7 +537,7 @@ namespace UserInterface entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain); Wt::WPushButton* playBtn{ entry->bindNew("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; - playBtn->clicked().connect([=] + playBtn->clicked().connect([this, entry] { const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; if (pos) @@ -546,7 +546,7 @@ namespace UserInterface Wt::WPushButton* delBtn{ entry->bindNew("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML) }; delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([=] + delBtn->clicked().connect([this, tracklistEntryId, entry] { // Remove the entry n both the widget tree and the playqueue { @@ -572,7 +572,7 @@ namespace UserInterface entry->bindNew("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); entry->bindNew("play", Wt::WString::tr("Lms.Explore.play")) - ->clicked().connect([=] + ->clicked().connect([this, entry] { const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; if (pos) @@ -734,7 +734,7 @@ namespace UserInterface replaceTrackList->updateView(replaceTrackListModel.get()); auto* saveBtn{ modal->bindNew("save-btn", Wt::WString::tr("Lms.save")) }; - saveBtn->clicked().connect([=] + saveBtn->clicked().connect([=, this] { bool success{}; switch (contentStack->currentIndex()) diff --git a/src/lms/ui/admin/InitWizardView.cpp b/src/lms/ui/admin/InitWizardView.cpp index d3d2c77f..36320651 100644 --- a/src/lms/ui/admin/InitWizardView.cpp +++ b/src/lms/ui/admin/InitWizardView.cpp @@ -131,7 +131,7 @@ InitWizardView::InitWizardView() Wt::WPushButton* saveButton = bindNew("create-btn", Wt::WString::tr("Lms.create")); - saveButton->clicked().connect([=] + saveButton->clicked().connect([=, this] { updateModel(model.get()); diff --git a/src/lms/ui/admin/MediaLibrariesView.cpp b/src/lms/ui/admin/MediaLibrariesView.cpp index bec83e13..304a2f54 100644 --- a/src/lms/ui/admin/MediaLibrariesView.cpp +++ b/src/lms/ui/admin/MediaLibrariesView.cpp @@ -44,7 +44,7 @@ namespace UserInterface auto mediaLibraryModal{ std::make_unique(Database::MediaLibraryId{}) }; MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; - mediaLibraryModalPtr->saved().connect(this, [=](Database::MediaLibraryId newMediaLibraryId) + mediaLibraryModalPtr->saved().connect(this, [this, mediaLibraryModalPtr](Database::MediaLibraryId newMediaLibraryId) { Wt::WTemplate* entry{ addEntry() }; updateEntry(newMediaLibraryId, entry); @@ -53,7 +53,7 @@ namespace UserInterface LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); - mediaLibraryModalPtr->cancelled().connect(this, [=] + mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] { LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); @@ -95,7 +95,7 @@ namespace UserInterface Wt::WWidget* modalPtr{ modal.get() }; auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; - delBtn->clicked().connect([=] + delBtn->clicked().connect([=, this] { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -138,12 +138,12 @@ namespace UserInterface Wt::WPushButton* editBtn{ entry->bindNew("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML) }; editBtn->setToolTip(Wt::WString::tr("Lms.edit")); - editBtn->clicked().connect([=] + editBtn->clicked().connect([this, mediaLibraryId, entry] { auto mediaLibraryModal{ std::make_unique(mediaLibraryId) }; MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; - mediaLibraryModalPtr->saved().connect(this, [=](Database::MediaLibraryId newMediaLibraryId) + mediaLibraryModalPtr->saved().connect(this, [=, this](Database::MediaLibraryId newMediaLibraryId) { updateEntry(newMediaLibraryId, entry); @@ -154,7 +154,7 @@ namespace UserInterface LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); - mediaLibraryModalPtr->cancelled().connect(this, [=] + mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] { LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); @@ -164,7 +164,7 @@ namespace UserInterface Wt::WPushButton* delBtn{ entry->bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) }; delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([=] + delBtn->clicked().connect([this, mediaLibraryId, entry] { showDeleteLibraryModal(mediaLibraryId, entry); }); diff --git a/src/lms/ui/admin/MediaLibraryModal.cpp b/src/lms/ui/admin/MediaLibraryModal.cpp index e80d73e7..1262bc0a 100644 --- a/src/lms/ui/admin/MediaLibraryModal.cpp +++ b/src/lms/ui/admin/MediaLibraryModal.cpp @@ -189,7 +189,7 @@ namespace UserInterface setFormWidget(MediaLibraryModel::DirectoryField, std::make_unique()); Wt::WPushButton* saveBtn{ bindNew("save-btn", Wt::WString::tr(mediaLibraryId.isValid() ? "Lms.save" : "Lms.create")) }; - saveBtn->clicked().connect(this, [=] + saveBtn->clicked().connect(this, [this, mediaLibraryId, model] { updateModel(model.get()); @@ -205,7 +205,7 @@ namespace UserInterface }); Wt::WPushButton* cancelBtn{ bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; - cancelBtn->clicked().connect(this, [=] {cancelled().emit();}); + cancelBtn->clicked().connect(this, [this] { cancelled().emit(); }); updateView(model.get()); } diff --git a/src/lms/ui/admin/UsersView.cpp b/src/lms/ui/admin/UsersView.cpp index b6753701..125aac00 100644 --- a/src/lms/ui/admin/UsersView.cpp +++ b/src/lms/ui/admin/UsersView.cpp @@ -101,14 +101,14 @@ namespace UserInterface Wt::WPushButton* delBtn = entry->bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML); delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([=] + delBtn->clicked().connect([this, userId, entry] { auto modal{ std::make_unique(Wt::WString::tr("Lms.Admin.Users.template.delete-user")) }; modal->addFunction("tr", &Wt::WTemplate::Functions::tr); Wt::WWidget* modalPtr{ modal.get() }; auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; - delBtn->clicked().connect([=] + delBtn->clicked().connect([=, this] { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 736d5c6d..aadd0744 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -139,7 +139,7 @@ namespace UserInterface { const Database::ClusterId clusterId = cluster->getId(); Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; - entry->clicked().connect([=] + entry->clicked().connect([this, clusterId] { _filters.add(clusterId); }); @@ -150,23 +150,23 @@ namespace UserInterface bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain); bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::Play, { _artistId }); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _artistId }); }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _artistId }); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _artistId }); }); @@ -174,10 +174,10 @@ namespace UserInterface ->setLink(Wt::WLink{ std::make_unique(_artistId) }); { - auto isStarred{ [=] { return Service::get()->isStarred(LmsApp->getUserId(), _artistId); } }; + auto isStarred{ [this] { return Service::get()->isStarred(LmsApp->getUserId(), _artistId); } }; Wt::WPushButton* starBtn{ bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=] + starBtn->clicked().connect([=, this] { if (isStarred()) { diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 9129baf0..bcc2e2ed 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -44,7 +44,7 @@ Artists::Artists(Filters& filters) auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ArtistCollector::Mode mode) { auto *menuItem {bindNew(var, title)}; - menuItem->clicked().connect([=] + menuItem->clicked().connect([=, this] { refreshView(mode); _currentActiveItem->removeStyleClass("active"); diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp index 823bb8f3..d9d787d2 100644 --- a/src/lms/ui/explore/Filters.cpp +++ b/src/lms/ui/explore/Filters.cpp @@ -31,142 +31,135 @@ #include "Utils.hpp" #include "ModalManager.hpp" -namespace UserInterface { - -using namespace Database; - -void -Filters::showDialog() +namespace UserInterface { - auto dialog {std::make_unique(Wt::WString::tr("Lms.Explore.template.add-filter"))}; - Wt::WWidget* dialogPtr {dialog.get()}; - dialog->addFunction("tr", &Wt::WTemplate::Functions::tr); - dialog->addFunction("id", &Wt::WTemplate::Functions::id); + using namespace Database; - Wt::WComboBox* typeCombo {dialog->bindNew("type")}; - Wt::WComboBox* valueCombo {dialog->bindNew("value")}; + void Filters::showDialog() + { + auto dialog{ std::make_unique(Wt::WString::tr("Lms.Explore.template.add-filter")) }; + Wt::WWidget* dialogPtr{ dialog.get() }; + dialog->addFunction("tr", &Wt::WTemplate::Functions::tr); + dialog->addFunction("id", &Wt::WTemplate::Functions::id); - Wt::WPushButton* addBtn {dialog->bindNew("add-btn", Wt::WString::tr("Lms.Explore.add-filter"))}; - addBtn->clicked().connect([=] - { - const std::string type {typeCombo->valueText().toUTF8()}; - const std::string value {valueCombo->valueText().toUTF8()}; + Wt::WComboBox* typeCombo{ dialog->bindNew("type") }; + Wt::WComboBox* valueCombo{ dialog->bindNew("value") }; - // TODO use a model to store the cluster.id() values - ClusterId clusterId {}; + Wt::WPushButton* addBtn{ dialog->bindNew("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) }; + addBtn->clicked().connect([this, typeCombo, valueCombo, dialogPtr] + { + const std::string type{ typeCombo->valueText().toUTF8() }; + const std::string value{ valueCombo->valueText().toUTF8() }; - { - auto transaction {LmsApp->getDbSession().createReadTransaction()}; + // TODO use a model to store the cluster.id() values + ClusterId clusterId{}; - ClusterType::pointer clusterType {ClusterType::find(LmsApp->getDbSession(), type)}; - if (!clusterType) - return; + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - Cluster::pointer cluster {clusterType->getCluster(value)}; - if (!cluster) - return; + ClusterType::pointer clusterType{ ClusterType::find(LmsApp->getDbSession(), type) }; + if (!clusterType) + return; - clusterId = cluster->getId(); - } + Cluster::pointer cluster{ clusterType->getCluster(value) }; + if (!cluster) + return; - add(clusterId); - LmsApp->getModalManager().dispose(dialogPtr); - }); + clusterId = cluster->getId(); + } - Wt::WPushButton* cancelBtn {dialog->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel"))}; - cancelBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(dialogPtr); - }); + add(clusterId); + LmsApp->getModalManager().dispose(dialogPtr); + }); + Wt::WPushButton* cancelBtn{ dialog->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; + cancelBtn->clicked().connect([=] + { + LmsApp->getModalManager().dispose(dialogPtr); + }); - // Populate data - { - auto transaction {LmsApp->getDbSession().createReadTransaction()}; + // Populate data + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - const auto clusterTypesIds {ClusterType::findUsed(LmsApp->getDbSession())}; - for (const ClusterTypeId clusterTypeId : clusterTypesIds.results) - { - const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypeId)}; - typeCombo->addItem(Wt::WString::fromUTF8(std::string{ clusterType->getName() })); - } + const auto clusterTypesIds{ ClusterType::findUsed(LmsApp->getDbSession()) }; + for (const ClusterTypeId clusterTypeId : clusterTypesIds.results) + { + const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypeId) }; + typeCombo->addItem(Wt::WString::fromUTF8(std::string{ clusterType->getName() })); + } - if (!clusterTypesIds.results.empty()) - { - const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypesIds.results.front())}; + if (!clusterTypesIds.results.empty()) + { + const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypesIds.results.front()) }; - for (const Cluster::pointer& cluster : clusterType->getClusters()) - { - if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end()) - valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() })); - } - } - } + for (const Cluster::pointer& cluster : clusterType->getClusters()) + { + if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end()) + valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() })); + } + } + } - typeCombo->changed().connect([=] - { - const std::string name {typeCombo->valueText().toUTF8()}; + typeCombo->changed().connect([this, typeCombo, valueCombo] + { + const std::string name{ typeCombo->valueText().toUTF8() }; - valueCombo->clear(); + valueCombo->clear(); - auto transaction {LmsApp->getDbSession().createReadTransaction()}; + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - auto clusterType {ClusterType::find(LmsApp->getDbSession(), name)}; - for (const Cluster::pointer& cluster : clusterType->getClusters()) - { - if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end()) - valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() })); - } - }); + auto clusterType{ ClusterType::find(LmsApp->getDbSession(), name) }; + for (const Cluster::pointer& cluster : clusterType->getClusters()) + { + if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end()) + valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() })); + } + }); - LmsApp->getModalManager().show(std::move(dialog)); -} + LmsApp->getModalManager().show(std::move(dialog)); + } -void -Filters::add(ClusterId clusterId) -{ - if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds)) - return; + void Filters::add(ClusterId clusterId) + { + if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds)) + return; - Wt::WInteractWidget* filter {}; + Wt::WInteractWidget* filter{}; - { - auto cluster {Utils::createCluster(clusterId, true)}; - if (!cluster) - return; + { + auto cluster{ Utils::createCluster(clusterId, true) }; + if (!cluster) + return; - filter = _filters->addWidget(std::move(cluster)); - } + filter = _filters->addWidget(std::move(cluster)); + } - _clusterIds.push_back(clusterId); + _clusterIds.push_back(clusterId); - filter->clicked().connect([=] - { - _filters->removeWidget(filter); - _clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](ClusterId id) { return id == clusterId; }), std::end(_clusterIds)); - _sigUpdated.emit(); - }); + filter->clicked().connect([this, filter, clusterId] + { + _filters->removeWidget(filter); + _clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](ClusterId id) { return id == clusterId; }), std::end(_clusterIds)); + _sigUpdated.emit(); + }); - LmsApp->notifyMsg(Notification::Type::Info, - Wt::WString::tr("Lms.Explore.filters"), - Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2}); + LmsApp->notifyMsg(Notification::Type::Info, + Wt::WString::tr("Lms.Explore.filters"), + Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds{ 2 }); - _sigUpdated.emit(); -} - -Filters::Filters() -: Wt::WTemplate {Wt::WString::tr("Lms.Explore.template.filters")} -{ - addFunction("tr", &Functions::tr); - - // Filters - Wt::WPushButton *addFilterBtn = bindNew("add-filter", Wt::WText::tr("Lms.Explore.add-filter")); - addFilterBtn->clicked().connect(this, &Filters::showDialog); - - _filters = bindNew("clusters"); -} + _sigUpdated.emit(); + } + Filters::Filters() + : Wt::WTemplate{ Wt::WString::tr("Lms.Explore.template.filters") } + { + addFunction("tr", &Functions::tr); + // Filters + Wt::WPushButton* addFilterBtn = bindNew("add-filter", Wt::WText::tr("Lms.Explore.add-filter")); + addFilterBtn->clicked().connect(this, &Filters::showDialog); + _filters = bindNew("clusters"); + } } // namespace UserInterface - diff --git a/src/lms/ui/explore/Filters.hpp b/src/lms/ui/explore/Filters.hpp index b045ee85..7f891c17 100644 --- a/src/lms/ui/explore/Filters.hpp +++ b/src/lms/ui/explore/Filters.hpp @@ -30,21 +30,22 @@ namespace UserInterface { - class Filters : public Wt::WTemplate - { - public: - Filters(); + class Filters : public Wt::WTemplate + { + public: + Filters(); - void add(Database::ClusterId clusterId); - const std::vector& getClusterIds() const { return _clusterIds; } - Wt::Signal<>& updated() { return _sigUpdated; } + const std::vector& getClusterIds() const { return _clusterIds; } + void add(Database::ClusterId clusterId); - private: - void showDialog(); + Wt::Signal<>& updated() { return _sigUpdated; } - Wt::WContainerWidget *_filters; - Wt::Signal<> _sigUpdated; - std::vector _clusterIds; - }; + private: + void showDialog(); + + Wt::WContainerWidget* _filters; + Wt::Signal<> _sigUpdated; + std::vector _clusterIds; + }; } // namespace UserInterface diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index a4c64593..82dea623 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -271,7 +271,7 @@ namespace UserInterface { const ClusterId clusterId{ cluster->getId() }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; - entry->clicked().connect([=] + entry->clicked().connect([this, clusterId] { _filters.add(clusterId); }); @@ -280,25 +280,25 @@ namespace UserInterface } bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId }); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId }); }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId }); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId }); }); @@ -307,16 +307,16 @@ namespace UserInterface ->setLink(Wt::WLink{ std::make_unique(_releaseId) }); bindNew("release-info", Wt::WString::tr("Lms.Explore.release-info")) - ->clicked().connect([=] + ->clicked().connect([this] { showReleaseInfoModal(_releaseId); }); { - auto isStarred{ [=] { return Service::get()->isStarred(LmsApp->getUserId(), _releaseId); } }; + auto isStarred{ [this] { return Service::get()->isStarred(LmsApp->getUserId(), _releaseId); } }; Wt::WPushButton* starBtn{ bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=] + starBtn->clicked().connect([=, this] { if (isStarred()) { @@ -410,7 +410,7 @@ namespace UserInterface } Wt::WPushButton* playBtn{ entry->bindNew("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; - playBtn->clicked().connect([=] + playBtn->clicked().connect([this, trackId] { _playQueueController.playTrackInRelease(trackId); }); @@ -418,17 +418,17 @@ namespace UserInterface { entry->bindNew("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); entry->bindNew("play", Wt::WString::tr("Lms.Explore.play")) - ->clicked().connect([=] + ->clicked().connect([this, trackId] { _playQueueController.playTrackInRelease(trackId); }); entry->bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next")) - ->clicked().connect([=] + ->clicked().connect([this, trackId] { _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId }); }); entry->bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last")) - ->clicked().connect([=] + ->clicked().connect([this, trackId] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId }); }); @@ -436,10 +436,8 @@ namespace UserInterface auto isStarred{ [=] { return Service::get()->isStarred(LmsApp->getUserId(), trackId); } }; Wt::WPushButton* starBtn{ entry->bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=] + starBtn->clicked().connect([=, this] { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - if (isStarred()) { Service::get()->unstar(LmsApp->getUserId(), trackId); @@ -456,7 +454,7 @@ namespace UserInterface ->setLink(Wt::WLink{ std::make_unique(trackId) }); entry->bindNew("track-info", Wt::WString::tr("Lms.Explore.track-info")) - ->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); }); + ->clicked().connect([this, trackId] { TrackListHelpers::showTrackInfoModal(trackId, _filters); }); } entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain); diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index 36f64644..655d0e28 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -47,7 +47,7 @@ namespace UserInterface auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode) { auto* menuItem {bindNew(var, title)}; - menuItem->clicked().connect([=] + menuItem->clicked().connect([this, mode, menuItem] { refreshView(mode); _currentActiveItem->removeStyleClass("active"); @@ -76,17 +76,17 @@ namespace UserInterface }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases()); }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllReleases()); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases()); }); diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index 179868b8..ad680421 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -77,7 +77,7 @@ namespace UserInterface auto bindMenuItem{ [this](std::size_t index, const std::string& var, const Wt::WString& title) { Wt::WPushButton* menuItem {bindNew(var, title)}; - menuItem->clicked().connect([=] + menuItem->clicked().connect([this, menuItem, index] { _stack->setCurrentIndex(index); _currentActiveItem->removeStyleClass("active"); @@ -96,7 +96,7 @@ namespace UserInterface bindMenuItem(1, "artists", Wt::WString::tr("Lms.Explore.artists")); bindMenuItem(2, "tracks", Wt::WString::tr("Lms.Explore.tracks")); - filters.updated().connect([=] + filters.updated().connect([this] { refreshView(); }); diff --git a/src/lms/ui/explore/TrackListView.cpp b/src/lms/ui/explore/TrackListView.cpp index 8e178133..e03ad8c1 100644 --- a/src/lms/ui/explore/TrackListView.cpp +++ b/src/lms/ui/explore/TrackListView.cpp @@ -108,7 +108,7 @@ namespace UserInterface { const ClusterId clusterId{ cluster->getId() }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; - entry->clicked().connect([=] + entry->clicked().connect([this, clusterId] { _filters.add(clusterId); }); @@ -117,19 +117,19 @@ namespace UserInterface } bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([=] + ->clicked().connect([this, trackListId] { _playQueueController.processCommand(PlayQueueController::Command::Play, *trackListId); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this, trackListId] { _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, *trackListId); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this, trackListId] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, *trackListId); }); @@ -138,14 +138,14 @@ namespace UserInterface ->setLink(Wt::WLink{ std::make_unique(*trackListId) }); bindNew("delete", Wt::WString::tr("Lms.delete")) - ->clicked().connect([=] + ->clicked().connect([this, trackListId] { auto modal{ std::make_unique(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) }; modal->addFunction("tr", &Wt::WTemplate::Functions::tr); Wt::WWidget* modalPtr{ modal.get() }; auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; - delBtn->clicked().connect([=] + delBtn->clicked().connect([this, trackListId, modalPtr] { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; diff --git a/src/lms/ui/explore/TrackListsView.cpp b/src/lms/ui/explore/TrackListsView.cpp index 3c8e6f2c..45334747 100644 --- a/src/lms/ui/explore/TrackListsView.cpp +++ b/src/lms/ui/explore/TrackListsView.cpp @@ -44,7 +44,7 @@ namespace UserInterface auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, Mode mode) { auto *menuItem {bindNew(var, title)}; - menuItem->clicked().connect([=] + menuItem->clicked().connect([this, mode, menuItem] { _mode = mode; refreshView(); diff --git a/src/lms/ui/explore/TracksView.cpp b/src/lms/ui/explore/TracksView.cpp index 4256f360..806a19bb 100644 --- a/src/lms/ui/explore/TracksView.cpp +++ b/src/lms/ui/explore/TracksView.cpp @@ -47,7 +47,7 @@ namespace UserInterface auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, TrackCollector::Mode mode) { auto* menuItem {bindNew(var, title)}; - menuItem->clicked().connect([=] + menuItem->clicked().connect([this, mode, menuItem] { refreshView(mode); _currentActiveItem->removeStyleClass("active"); @@ -70,23 +70,23 @@ namespace UserInterface bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), TrackCollector::Mode::All); bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks()); }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks()); }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllTracks()); }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([=] + ->clicked().connect([this] { _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks()); }); From d3aa11714ad707cbe4c5e533cc9391bd4f95935c Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 2 Feb 2024 21:49:02 +0100 Subject: [PATCH 03/12] Fixed badly formatted logs --- src/libs/services/feedback/impl/listenbrainz/Utils.hpp | 6 +++--- src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp index ea8f5eaa..df295817 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp @@ -27,11 +27,11 @@ namespace Database { - class Session; + class Session; } namespace Feedback::ListenBrainz::Utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); - std::string parseValidateToken(std::string_view msgBody); + std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); + std::string parseValidateToken(std::string_view msgBody); } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp index 1b475477..640afff7 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp @@ -23,15 +23,15 @@ #include "utils/ILogger.hpp" #include "utils/UUID.hpp" -#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, message << "[listenbrainz] ") +#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[listenbrainz] " << message) namespace Database { - class Session; + class Session; } namespace Scrobbling::ListenBrainz::Utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); - std::string parseValidateToken(std::string_view msgBody); + std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); + std::string parseValidateToken(std::string_view msgBody); } From 5db91fc5b5fd05b58130cdf93de84f738d312243 Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 2 Feb 2024 22:58:37 +0100 Subject: [PATCH 04/12] Limit to the first 4 digits when parsing years badly encoded as YYYYMMDD --- src/libs/metadata/impl/TagLibParser.cpp | 8 +++--- src/libs/metadata/impl/Utils.cpp | 32 +++++++++++++++++++++ src/libs/metadata/impl/Utils.hpp | 2 ++ src/libs/metadata/test/Utils.cpp | 37 +++++++++++++++++++++++++ src/libs/utils/test/String.cpp | 3 ++ 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index b821bd66..2bfe2f1d 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -336,19 +336,19 @@ namespace MetaData if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid()) track.date = date; else if (!track.year) - track.year = StringUtils::readAs(value); + track.year = Utils::parseYear(value); } else if (tag == "YEAR") - track.year = StringUtils::readAs(value); + track.year = Utils::parseYear(value); else if (tag == "ORIGINALDATE") { if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid()) track.originalDate = date; else if (!track.originalYear) - track.originalYear = StringUtils::readAs(value); + track.originalYear = Utils::parseYear(value); } else if (tag == "ORIGINALYEAR") - track.originalYear = StringUtils::readAs(value); + track.originalYear = Utils::parseYear(value); else if (tag == "METADATA_BLOCK_PICTURE") track.hasCover = true; else if (tag == "COPYRIGHT") diff --git a/src/libs/metadata/impl/Utils.cpp b/src/libs/metadata/impl/Utils.cpp index a0354cbe..b8a0877b 100644 --- a/src/libs/metadata/impl/Utils.cpp +++ b/src/libs/metadata/impl/Utils.cpp @@ -64,6 +64,38 @@ namespace MetaData::Utils return {}; } + std::optional parseYear(std::string_view yearStr) + { + // limit to first 4 digit, accept leading '-' + if (yearStr.empty()) + return std::nullopt; + + int sign; + if (yearStr.front() == '-') + { + sign = -1; + yearStr.remove_prefix(1); + } + else + { + sign = 1; + } + + if (yearStr.empty() || !std::isdigit(yearStr.front())) + return std::nullopt; + + int result{}; + for (std::size_t i{}; i < yearStr.size() && i < 4; ++i) + { + if (!std::isdigit(yearStr[i])) { + break; + } + result = result * 10 + (yearStr[i] - '0'); + } + + return result * sign; + } + std::string_view readStyleToString(ParserReadStyle readStyle) { switch (readStyle) diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp index e1de14b8..d566cf7d 100644 --- a/src/libs/metadata/impl/Utils.hpp +++ b/src/libs/metadata/impl/Utils.hpp @@ -20,6 +20,7 @@ #pragma once +#include #include #include @@ -28,6 +29,7 @@ namespace MetaData::Utils { Wt::WDate parseDate(std::string_view dateStr); + std::optional parseYear(std::string_view yearStr); std::string_view readStyleToString(ParserReadStyle readStyle); struct PerformerArtist diff --git a/src/libs/metadata/test/Utils.cpp b/src/libs/metadata/test/Utils.cpp index 50b1ff3a..71e4aa44 100644 --- a/src/libs/metadata/test/Utils.cpp +++ b/src/libs/metadata/test/Utils.cpp @@ -76,6 +76,43 @@ TEST(MetaData, parseDate) } } +TEST(MetaData, parseYear) +{ + using namespace MetaData::Utils; + + struct TestCase + { + std::string str; + std::optional result; + } testCases[] + { + { "1995-05-09", 1995 }, + { "1995", 1995 }, + { "-0", 0 }, + { "0", 0 }, + { "00", 0 }, + { "05", 5 }, + { "050", 50 }, + { "00005", 0 }, + { "-50", -50 }, + { "-", std::nullopt }, + { "", std::nullopt }, + { "a", std::nullopt }, + { "1a", 1 }, + { "12a", 12 }, + { "123a", 123 }, + { "1234a", 1234 }, + { "19951123", 1995 }, + { "199511", 1995 }, + }; + + for (const TestCase& testCase : testCases) + { + const std::optional parsed{ parseYear(testCase.str) }; + EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'"; + } +} + TEST(MetaData, extractPerformerAndRole) { using namespace MetaData::Utils; diff --git a/src/libs/utils/test/String.cpp b/src/libs/utils/test/String.cpp index fff9c076..0a8769d1 100644 --- a/src/libs/utils/test/String.cpp +++ b/src/libs/utils/test/String.cpp @@ -173,9 +173,12 @@ TEST(StringUtils, readAs_int) EXPECT_EQ(StringUtils::readAs("-1"), -1); EXPECT_EQ(StringUtils::readAs(""), std::nullopt); EXPECT_EQ(StringUtils::readAs("a"), std::nullopt); + EXPECT_EQ(StringUtils::readAs("-"), std::nullopt); EXPECT_EQ(StringUtils::readAs("1024-1"), 1024); EXPECT_EQ(StringUtils::readAs("1024-"), 1024); EXPECT_EQ(StringUtils::readAs("1024/5"), 1024); + EXPECT_EQ(StringUtils::readAs("1024a"), 1024); + EXPECT_EQ(StringUtils::readAs("a1024a"), std::nullopt); } TEST(StringUtils, capitalize) From 2214d75419bd7f868d1b5231dd92d60979a98fa1 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Feb 2024 00:08:01 +0100 Subject: [PATCH 05/12] Made benchmarks an option --- CMakeLists.txt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80856a51..a0108fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,6 @@ pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat) pkg_check_modules(Archive REQUIRED IMPORTED_TARGET libarchive) find_package(PAM) find_package(STB) -find_package(benchmark) # WT if (NOT Wt_FOUND) @@ -72,12 +71,10 @@ endif () message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}") # Benchmark -if (benchmark_FOUND) - set(BUILD_BENCHMARKS ON) - message(STATUS "Google Benchmark found. Building benchmarks") -else () - set(BUILD_BENCHMARKS OFF) - message(STATUS "Google Benchmark not found. Not building benchmarks") +option(BUILD_BENCHMARKS "Build benchmarks" OFF) +if (BUILD_BENCHMARKS) + find_package(benchmark REQUIRED) + message(STATUS "Building benchmarks") endif() add_subdirectory(src) From 676600e92da6e8f9d405fa8961a08e0cd0a13b44 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 3 Feb 2024 14:51:39 +0100 Subject: [PATCH 06/12] Build benchmarks on arch/alpine --- Dockerfile-build-alpine | 3 ++- Dockerfile-build-arch | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile-build-alpine b/Dockerfile-build-alpine index aef34b78..02c18010 100644 --- a/Dockerfile-build-alpine +++ b/Dockerfile-build-alpine @@ -15,6 +15,7 @@ ARG LMS_BUILD_PACKAGES=" \ gcc \ g++ \ musl-dev \ + benchmark-dev \ boost-dev \ ffmpeg-dev \ libarchive-dev \ @@ -35,7 +36,7 @@ ARG LMS_BUILD_TYPE="Release" RUN \ DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \ xx-info is-cross && export BUILD_TESTS=OFF || export BUILD_TESTS=ON && \ - PKG_CONFIG_PATH=/$(xx-info)/usr/lib/pkgconfig cmake /tmp/lms/ -DCMAKE_INCLUDE_PATH=${PREFIX}/include -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} $(xx-clang --print-cmake-defines) -DCMAKE_PREFIX_PATH=/$(xx-info)/usr/lib/cmake -DBUILD_TESTING=${BUILD_TESTS} && \ + PKG_CONFIG_PATH=/$(xx-info)/usr/lib/pkgconfig cmake /tmp/lms/ -DCMAKE_INCLUDE_PATH=${PREFIX}/include -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} $(xx-clang --print-cmake-defines) -DCMAKE_PREFIX_PATH=/$(xx-info)/usr/lib/cmake -DBUILD_TESTING=${BUILD_TESTS} -DBUILD_BENCHMARKS=ON && \ VERBOSE=1 make -j$(nproc) && \ xx-verify src/lms/lms && \ (xx-info is-cross || make test) diff --git a/Dockerfile-build-arch b/Dockerfile-build-arch index 14eeac08..82cc3ee0 100644 --- a/Dockerfile-build-arch +++ b/Dockerfile-build-arch @@ -1,6 +1,7 @@ FROM archlinux:latest ARG BUILD_PACKAGES="\ + benchmark \ clang \ cmake \ boost \ @@ -22,6 +23,6 @@ COPY . /tmp/lms/ ARG LMS_BUILD_TYPE="Release" RUN \ DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \ - cmake /tmp/lms/ -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=/usr && \ + cmake /tmp/lms/ -DCMAKE_BUILD_TYPE=${LMS_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_BENCHMARKS=ON && \ VERBOSE=1 make -j$(nproc) && \ make test From 14d65dc7c2aba78d0ccd68000bcd148dfbcb2fd9 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Feb 2024 15:01:43 +0100 Subject: [PATCH 07/12] Added media library params to the db generator and subsonic api bench --- src/tools/db-generator/LmsDbGenerator.cpp | 17 ++++++++++++-- src/tools/subsonic-api-bench/search3.sh | 28 ++++++++++++++++++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/tools/db-generator/LmsDbGenerator.cpp b/src/tools/db-generator/LmsDbGenerator.cpp index 93e7a9e0..8f264c22 100644 --- a/src/tools/db-generator/LmsDbGenerator.cpp +++ b/src/tools/db-generator/LmsDbGenerator.cpp @@ -31,6 +31,7 @@ #include "database/Db.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" +#include "database/MediaLibrary.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" @@ -45,6 +46,7 @@ namespace { struct GeneratorParameters { + std::size_t mediaLibraryCount{ 1 }; std::size_t releaseCountPerBatch{ 1000 }; std::size_t releaseCount{ 100 }; std::size_t trackCountPerRelease{ 10 }; @@ -59,6 +61,7 @@ namespace struct GenerationContext { Database::Session& session; + std::vector mediaLibraries; std::vector genres; std::vector moods; GenerationContext(Database::Session& _session) : session{ _session } {} @@ -99,13 +102,17 @@ namespace track.modify()->setTrackMBID(UUID::generate()); track.modify()->setRecordingMBID(UUID::generate()); track.modify()->setTotalTrack(params.trackCountPerRelease); + if (!context.mediaLibraries.empty()) + track.modify()->setMediaLibrary(*Random::pickRandom(context.mediaLibraries)); TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::Artist); TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist); std::vector> clusters; - clusters.push_back(*Random::pickRandom(context.genres)); - clusters.push_back(*Random::pickRandom(context.moods)); + if (!context.genres.empty()) + clusters.push_back(*Random::pickRandom(context.genres)); + if (!context.moods.empty()) + clusters.push_back(*Random::pickRandom(context.moods)); track.modify()->setClusters(clusters); } } @@ -128,6 +135,10 @@ namespace { auto transaction{ context.session.createWriteTransaction() }; + // create some random media libraries + for (std::size_t i{}; i < params.mediaLibraryCount; ++i) + context.mediaLibraries.push_back(context.session.create()); + // create some random genres/moods { Database::ClusterType::pointer genre{ Database::ClusterType::find(context.session, "GENRE") }; @@ -163,6 +174,7 @@ int main(int argc, char* argv[]) po::options_description options{ "Options" }; options.add_options() ("conf,c", po::value()->default_value("/etc/lms.conf"), "lms config file") + ("media-library-count", po::value()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use") ("release-count-per-batch", po::value()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction") ("release-count", po::value()->default_value(defaultParams.releaseCount), "Number of releases to generate") ("track-count-per-release", po::value()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release") @@ -187,6 +199,7 @@ int main(int argc, char* argv[]) po::notify(vm); GeneratorParameters genParams; + genParams.mediaLibraryCount = vm["media-library-count"].as(); genParams.releaseCountPerBatch = vm["release-count-per-batch"].as(); genParams.releaseCount = vm["release-count"].as(); genParams.trackCountPerRelease = vm["track-count-per-release"].as(); diff --git a/src/tools/subsonic-api-bench/search3.sh b/src/tools/subsonic-api-bench/search3.sh index 400a59c2..8a72e923 100755 --- a/src/tools/subsonic-api-bench/search3.sh +++ b/src/tools/subsonic-api-bench/search3.sh @@ -1,7 +1,7 @@ #!/bin/bash -if [ "$#" -ne 6 ]; then - echo "Usage: $0 " +if [ "$#" -lt 6 ] || [ "$#" -gt 7 ]; then + echo "Usage: $0 [musicFolderId]" exit 1 fi @@ -19,25 +19,41 @@ album_count="$4" song_count="$5" batch_size="$6" +music_folder_id="" +if [ "$#" -eq 7 ]; then + music_folder_id="$7" +fi + +append_music_folder() { + local url="$1" + if [ -n "$music_folder_id" ]; then + url="$url&musicFolderId=$music_folder_id" + fi + echo "$url" +} + start_time=$(date +%s.%3N) # artists +echo "Fetching $artist_count artists..." for ((i = 0; i < artist_count; i += $batch_size)); do - wget -q -O - "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=$batch_size&artistOffset=$i&albumCount=0&songCount=0" > /dev/null + wget -q -O - "$(append_music_folder "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=$batch_size&artistOffset=$i&albumCount=0&songCount=0")" > /dev/null done # albums +echo "Fetching $album_count albums..." for ((i = 0; i < album_count; i += $batch_size)); do - wget -q -O - "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=0&albumCount=$batch_size&albumOffset=$i&songCount=0" > /dev/null + wget -q -O - "$(append_music_folder "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=0&albumCount=$batch_size&albumOffset=$i&songCount=0")" > /dev/null done # songs +echo "Fetching $song_count songs..." for ((i = 0; i < song_count; i += $batch_size)); do - wget -q -O - "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=0&albumCount=0&songCount=$batch_size&songOffset=$i" > /dev/null + wget -q -O - "$(append_music_folder "$base_url/rest/search3.view?u=$user&p=$user_password&v=1.13.0&c=benchmark&f=json&query=&artistCount=0&albumCount=0&songCount=$batch_size&songOffset=$i")" > /dev/null done end_time=$(date +%s.%3N) elapsed_time=$(echo "$end_time - $start_time" | bc) -echo "Fetch time: $elapsed_time seconds" \ No newline at end of file +echo "Fetch time: $elapsed_time seconds" From c242b4c01e07ff51744f2d72c0af8c59ff541f70 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 4 Feb 2024 20:35:46 +0100 Subject: [PATCH 08/12] Fixed bad migrations due to NULL constraints. fixes #413 --- src/libs/database/impl/Migration.cpp | 37 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 405cf5a9..b3adbb49 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -182,8 +182,8 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( static void migrateFromV40(Session& session) { // add artist_display_name in Release and Track - session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT"); - session.getDboSession().execute("ALTER TABLE track ADD artist_display_name TEXT"); + session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT NOT NULL DEFAULT ''"); + session.getDboSession().execute("ALTER TABLE track ADD artist_display_name TEXT NOT NULL DEFAULT ''"); // Just increment the scan version of the settings to make the next scheduled scan rescan everything session.getDboSession().execute("UPDATE scan_settings SET scan_version = scan_version + 1"); @@ -237,7 +237,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( static void migrateFromV44(Session& session) { // add bitrate - session.getDboSession().execute("ALTER TABLE track ADD bitrate INTEGER"); + session.getDboSession().execute("ALTER TABLE track ADD bitrate INTEGER NOT NULL DEFAULT 0"); // Just increment the scan version of the settings to make the next scheduled scan rescan everything session.getDboSession().execute("UPDATE scan_settings SET scan_version = scan_version + 1"); @@ -365,7 +365,36 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( ))"); // Migrate data, with the new media_library_id field set to 1 - session.getDboSession().execute("INSERT INTO track_backup SELECT id, version, scan_version, track_number, disc_number, total_track, disc_subtitle, name, duration, bitrate, date, year, original_date, original_year, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url, track_replay_gain, release_replay_gain, artist_display_name, release_id, 1 FROM track"); + session.getDboSession().execute(R"(INSERT INTO track_backup +SELECT + id, + version, + scan_version, + track_number, + disc_number, + total_track, + disc_subtitle, + name, + duration, + COALESCE(bitrate, 0), + date, + year, + original_date, + original_year, + file_path, + file_last_write, + file_added, + has_cover, + mbid, + recording_mbid, + copyright, + copyright_url, + track_replay_gain, + release_replay_gain, + COALESCE(artist_display_name, ""), + release_id, + 1 + FROM track)"); session.getDboSession().execute("DROP TABLE track"); session.getDboSession().execute("ALTER TABLE track_backup RENAME TO track"); } From 01538166eda83838ad537ecbd425756e708ae0c4 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Feb 2024 11:21:15 +0100 Subject: [PATCH 09/12] Fixed bad MediaLibrary::getCount() --- src/libs/database/impl/MediaLibrary.cpp | 2 +- src/libs/database/include/database/MediaLibrary.hpp | 4 +++- src/libs/database/test/Common.cpp | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/database/impl/MediaLibrary.cpp b/src/libs/database/impl/MediaLibrary.cpp index 2538caad..3732bb26 100644 --- a/src/libs/database/impl/MediaLibrary.cpp +++ b/src/libs/database/impl/MediaLibrary.cpp @@ -43,7 +43,7 @@ namespace Database { session.checkReadTransaction(); - return session.getDboSession().query("SELECT COUNT(*) FROM cluster"); + return session.getDboSession().query("SELECT COUNT(*) FROM media_library"); } MediaLibrary::pointer MediaLibrary::find(Session& session, MediaLibraryId id) diff --git a/src/libs/database/include/database/MediaLibrary.hpp b/src/libs/database/include/database/MediaLibrary.hpp index ca3582ec..790a5719 100644 --- a/src/libs/database/include/database/MediaLibrary.hpp +++ b/src/libs/database/include/database/MediaLibrary.hpp @@ -17,6 +17,8 @@ * along with LMS. If not, see . */ +#pragma once + #include #include #include @@ -38,7 +40,7 @@ namespace Database MediaLibrary() = default; // find - std::size_t getCount(Session& session); + static std::size_t getCount(Session& session); static pointer find(Session& session, MediaLibraryId id); static pointer find(Session& session, std::string_view name); static pointer find(Session& session, const std::filesystem::path& path); diff --git a/src/libs/database/test/Common.cpp b/src/libs/database/test/Common.cpp index 80fcc170..2015cacb 100644 --- a/src/libs/database/test/Common.cpp +++ b/src/libs/database/test/Common.cpp @@ -23,6 +23,7 @@ #include "database/Cluster.hpp" #include "database/Db.hpp" #include "database/Listen.hpp" +#include "database/MediaLibrary.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/StarredArtist.hpp" @@ -77,6 +78,7 @@ void DatabaseFixture::testDatabaseEmpty() EXPECT_EQ(Cluster::getCount(session), 0); EXPECT_EQ(ClusterType::getCount(session), 0); EXPECT_EQ(Listen::getCount(session), 0); + EXPECT_EQ(MediaLibrary::getCount(session), 0); EXPECT_EQ(Release::getCount(session), 0); EXPECT_EQ(StarredArtist::getCount(session), 0); EXPECT_EQ(StarredRelease::getCount(session), 0); From 889096190a397e84e091ea786f5b4975fa66ab4e Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Feb 2024 11:22:58 +0100 Subject: [PATCH 10/12] Do not use DISTINCT on track queries, as this is useless and slowdowns queries --- src/libs/database/impl/Track.cpp | 5 +- src/libs/database/include/database/Track.hpp | 2 - src/libs/database/test/Artist.cpp | 22 +++--- src/libs/database/test/Cluster.cpp | 78 +++++++++---------- src/libs/database/test/Release.cpp | 13 ++-- .../impl/PlaylistGeneratorService.cpp | 1 - src/lms/ui/PlayQueue.cpp | 1 - src/lms/ui/explore/PlayQueueController.cpp | 1 - src/lms/ui/explore/TrackListView.cpp | 1 - 9 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 0f9ae193..4a53b593 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -45,8 +45,7 @@ namespace Database { session.checkReadTransaction(); - std::string selectStatement{ params.distinct ? "SELECT DISTINCT" : "SELECT" }; - auto query{ session.getDboSession().query(selectStatement + " " + std::string{ itemToSelect } + " FROM track t") }; + auto query{ session.getDboSession().query("SELECT " + std::string{ itemToSelect } + " FROM track t") }; assert(params.keywords.empty() || params.name.empty()); for (std::string_view keyword : params.keywords) @@ -118,6 +117,8 @@ namespace Database } query.where(oss.str()); } + + query.groupBy("t.id"); } assert(!(params.nonRelease && params.release.isValid())); diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index 4237f528..4a815323 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -79,7 +79,6 @@ namespace Database { TrackListId trackList; // matching this trackList std::optional trackNumber; // matching this track number MediaLibraryId mediaLibrary; // If set, tracks in this library - bool distinct{ true }; FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } @@ -96,7 +95,6 @@ namespace Database { FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } - FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } }; struct PathResult diff --git a/src/libs/database/test/Artist.cpp b/src/libs/database/test/Artist.cpp index 38ef7a94..3030706c 100644 --- a/src/libs/database/test/Artist.cpp +++ b/src/libs/database/test/Artist.cpp @@ -92,7 +92,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); } { @@ -108,7 +108,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack) EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId()); ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1); - EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty()); + EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); EXPECT_EQ(track->getArtists({}).size(), 1); } @@ -120,7 +120,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack) EXPECT_EQ(artists.front(), artist.getId()); ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1); - EXPECT_TRUE(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).empty()); + EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).size(), 0); EXPECT_EQ(track->getArtistIds({}).size(), 1); } @@ -195,7 +195,7 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty()); + EXPECT_EQ(Artist::findOrphanIds(session, Range{}).results.size(), 0); } { @@ -260,7 +260,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); } { @@ -273,7 +273,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) EXPECT_EQ(track->getArtists({}).size(), 2); EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2); - EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty()); + EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 2); EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2); } @@ -317,7 +317,7 @@ TEST_F(DatabaseFixture, Artist_findByName) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.empty()); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.size(), 0); const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"A"})) }; ASSERT_EQ(artistsByAAA.results.size(), 1); @@ -327,7 +327,7 @@ TEST_F(DatabaseFixture, Artist_findByName) ASSERT_EQ(artistsByZZZ.results.size(), 1); EXPECT_EQ(artistsByZZZ.results.front(), artist.getId()); - EXPECT_TRUE(Artist::find(session, "NNN").empty()); + EXPECT_EQ(Artist::find(session, "NNN").size(), 0); EXPECT_EQ(Artist::find(session, "AAA").size(), 1); } } @@ -348,19 +348,19 @@ TEST_F(DatabaseFixture, Artist_findByNameEscaped) const auto artists{ Artist::find(session, R"(MyArtist%)") }; ASSERT_TRUE(artists.size() == 1); EXPECT_EQ(artists.front()->getId(), artist1.getId()); - EXPECT_TRUE(Artist::find(session, R"(MyArtistFoo)").empty()); + EXPECT_EQ(Artist::find(session, R"(MyArtistFoo)").size(), 0); } { const auto artists{ Artist::find(session, R"(%MyArtist)") }; ASSERT_TRUE(artists.size() == 1); EXPECT_EQ(artists.front()->getId(), artist2.getId()); - EXPECT_TRUE(Artist::find(session, R"(FooMyArtist)").empty()); + EXPECT_EQ(Artist::find(session, R"(FooMyArtist)").size(), 0); } { const auto artists{ Artist::find(session, R"(%_MyArtist)") }; ASSERT_TRUE(artists.size() == 1); ASSERT_EQ(artists.front()->getId(), artist3.getId()); - EXPECT_TRUE(Artist::find(session, R"(%CMyArtist)").empty()); + EXPECT_EQ(Artist::find(session, R"(%CMyArtist)").size(), 0); } } diff --git a/src/libs/database/test/Cluster.cpp b/src/libs/database/test/Cluster.cpp index dd940b24..1325fa92 100644 --- a/src/libs/database/test/Cluster.cpp +++ b/src/libs/database/test/Cluster.cpp @@ -69,7 +69,7 @@ TEST_F(DatabaseFixture, Cluster) EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); clusterTypes = ClusterType::findOrphanIds(session); - EXPECT_TRUE(clusterTypes.results.empty()); + EXPECT_EQ(clusterTypes.results.size(), 0); } } @@ -80,7 +80,7 @@ TEST_F(DatabaseFixture, Cluster) ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - ASSERT_TRUE(ClusterType::findUsed(session).results.empty()); + ASSERT_EQ(ClusterType::findUsed(session).results.size(), 0); } } @@ -91,7 +91,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); auto clusterTypes{ ClusterType::findOrphanIds(session) }; ASSERT_EQ(clusterTypes.results.size(), 1); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); @@ -104,8 +104,8 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) auto transaction{ session.createReadTransaction() }; auto clusters{ Cluster::findOrphanIds(session) }; EXPECT_EQ(clusters.results.size(), 2); - EXPECT_TRUE(track->getClusters().empty()); - EXPECT_TRUE(track->getClusterIds().empty()); + EXPECT_EQ(track->getClusters().size(), 0); + EXPECT_EQ(track->getClusterIds().size(), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); } @@ -131,7 +131,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), cluster2.getId()); - EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty()); + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); } { @@ -142,7 +142,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack) EXPECT_EQ(tracks.results.front(), track.getId()); tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.getId() })); - EXPECT_TRUE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 0); } { @@ -172,7 +172,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters) auto transaction{ session.createReadTransaction() }; const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - EXPECT_TRUE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 0); } { @@ -185,7 +185,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters) auto transaction{ session.createReadTransaction() }; const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - EXPECT_TRUE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); } @@ -200,7 +200,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters) auto transaction{ session.createReadTransaction() }; const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - ASSERT_FALSE(tracks.results.empty()); + ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track.getId()); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1); @@ -225,7 +225,7 @@ TEST_F(DatabaseFixture, Cluster_multiTracks) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size()); @@ -242,8 +242,8 @@ TEST_F(DatabaseFixture, ClusterType_singleTrack) { { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}).results.empty()); - EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.empty()); + EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).results.size(), 0); + EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.size(), 0); } ScopedClusterType clusterType{ session, "MyClusterType" }; @@ -275,7 +275,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); } ScopedClusterType clusterType{ session, "MyClusterType" }; @@ -285,7 +285,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) { auto transaction{ session.createReadTransaction() }; ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2); - EXPECT_TRUE(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.empty()); + EXPECT_EQ(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.size(), 0); EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1); EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); @@ -360,10 +360,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty()); + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 2); - EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); } { @@ -379,8 +379,8 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); - EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.empty()); - EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.empty()); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.size(), 0); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); cluster2.get().modify()->addTrack(track.get()); } @@ -400,7 +400,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); - EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.empty()); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); } } @@ -421,9 +421,9 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); } { @@ -461,8 +461,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); } { @@ -572,10 +572,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); - EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); } { @@ -706,7 +706,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters) EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); } - EXPECT_TRUE(trackList->getSimilarTracks(10, 10).empty()); + EXPECT_EQ(trackList->getSimilarTracks(10, 10).size(), 0); } } @@ -721,9 +721,9 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(artist1->findSimilarArtistIds().results.empty()); - EXPECT_TRUE(artist2->findSimilarArtistIds().results.empty()); - EXPECT_TRUE(artist3->findSimilarArtistIds().results.empty()); + EXPECT_EQ(artist1->findSimilarArtistIds().results.size(), 0); + EXPECT_EQ(artist2->findSimilarArtistIds().results.size(), 0); + EXPECT_EQ(artist3->findSimilarArtistIds().results.size(), 0); } std::list tracks; @@ -768,7 +768,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) { auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; - EXPECT_EQ(artists.results.empty(), 1); + EXPECT_EQ(artists.results.size(), 1); } { @@ -779,7 +779,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) { auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Composer}) }; - EXPECT_TRUE(artists.results.empty()); + EXPECT_EQ(artists.results.size(), 0); } { @@ -802,9 +802,9 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(release1->getSimilarReleases().empty()); - EXPECT_TRUE(release2->getSimilarReleases().empty()); - EXPECT_TRUE(release3->getSimilarReleases().empty()); + EXPECT_EQ(release1->getSimilarReleases().size(), 0); + EXPECT_EQ(release2->getSimilarReleases().size(), 0); + EXPECT_EQ(release3->getSimilarReleases().size(), 0); } std::list tracks; diff --git a/src/libs/database/test/Release.cpp b/src/libs/database/test/Release.cpp index 5fd781e8..0096545b 100644 --- a/src/libs/database/test/Release.cpp +++ b/src/libs/database/test/Release.cpp @@ -89,7 +89,7 @@ TEST_F(DatabaseFixture, Release_singleTrack) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; ASSERT_EQ(tracks.results.size(), 1); @@ -125,7 +125,7 @@ TEST_F(DatabaseFixture, Release_singleTrack) auto transaction{ session.createWriteTransaction() }; const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; - EXPECT_TRUE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 0); auto releases{ Release::findOrphanIds(session) }; ASSERT_EQ(releases.results.size(), 1); @@ -357,8 +357,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) { auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.empty()); - EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.empty()); + EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.size(), 0); + EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.size(), 0); } { @@ -383,13 +383,13 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) { const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) }; - ASSERT_FALSE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results.front(), track1A.getId()); } { const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; - ASSERT_FALSE(tracks.results.empty()); + EXPECT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results.front(), track2B.getId()); } } @@ -422,7 +422,6 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate) track2A.get().modify()->setRelease(release2.get()); track2B.get().modify()->setRelease(release2.get()); - track1A.get().modify()->setDate(release1Date); track1B.get().modify()->setDate(release1Date); track1A.get().modify()->setOriginalDate(release1OriginalDate); diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp index 419d68d8..88a0f3bd 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp @@ -105,7 +105,6 @@ namespace Recommendation Track::FindParameters params; params.setTrackList(tracklistId); params.setSortMethod(TrackSortMethod::TrackList); - params.setDistinct(false); for (const TrackId trackId : Track::findIds(dbSession, params).results) tracks.push_back(trackId); diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 803e2501..63eaf5ff 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -795,7 +795,6 @@ namespace UserInterface Track::FindParameters params; params.setTrackList(_queueId); - params.setDistinct(false); params.setSortMethod(TrackSortMethod::TrackList); Track::find(session, params, [&](const Track::pointer& track) diff --git a/src/lms/ui/explore/PlayQueueController.cpp b/src/lms/ui/explore/PlayQueueController.cpp index c29f7269..bf800d19 100644 --- a/src/lms/ui/explore/PlayQueueController.cpp +++ b/src/lms/ui/explore/PlayQueueController.cpp @@ -100,7 +100,6 @@ namespace UserInterface params.setClusters(clusters); params.setRange(Database::Range{ 0, maxTrackCount }); params.setSortMethod(TrackSortMethod::TrackList); - params.setDistinct(false); return Database::Track::findIds(session, params).results; } diff --git a/src/lms/ui/explore/TrackListView.cpp b/src/lms/ui/explore/TrackListView.cpp index e03ad8c1..8f783be0 100644 --- a/src/lms/ui/explore/TrackListView.cpp +++ b/src/lms/ui/explore/TrackListView.cpp @@ -187,7 +187,6 @@ namespace UserInterface params.setTrackList(_trackListId); params.setSortMethod(Database::TrackSortMethod::TrackList); params.setRange(Database::Range{ static_cast(_container->getCount()), _batchSize }); - params.setDistinct(false); Database::Track::find(LmsApp->getDbSession(), params, [this](const Track::pointer& track) { From a64a2de52ed66d374e69bf65aabe785660dbef81 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Feb 2024 13:35:16 +0100 Subject: [PATCH 11/12] Fixed bad testU --- src/libs/database/test/Cluster.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/database/test/Cluster.cpp b/src/libs/database/test/Cluster.cpp index 1325fa92..58ce6a3c 100644 --- a/src/libs/database/test/Cluster.cpp +++ b/src/libs/database/test/Cluster.cpp @@ -768,7 +768,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) { auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; - EXPECT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.size(), 0); } { From 7d53aba7554cfc919baba409462b1386d70688b7 Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 5 Feb 2024 15:48:22 +0100 Subject: [PATCH 12/12] Expose artist display name in media session API (instead of just the first artist) --- src/lms/ui/MediaPlayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index 1c2891b9..aeeca1a1 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -256,7 +256,7 @@ namespace UserInterface << " duration: " << std::chrono::duration_cast(track->getDuration()).count() << "," << " replayGain: " << replayGain << "," << " title: \"" << StringUtils::jsEscape(track->getName()) << "\"," - << " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\"," + << " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\"," << " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," << " artwork: [" << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"