Merge branch 'develop' for release v3.49.0

This commit is contained in:
emeric
2024-02-05 16:18:05 +01:00
54 changed files with 798 additions and 609 deletions
+8 -1
View File
@@ -4,7 +4,7 @@ project(lms)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/) 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) set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
if (UNIX) if (UNIX)
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
@@ -70,6 +70,13 @@ elseif (IMAGE_LIBRARY STREQUAL STB AND NOT STB_FOUND)
endif () endif ()
message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}") message(STATUS "IMAGE_LIBRARY set to ${IMAGE_LIBRARY}")
# Benchmark
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
if (BUILD_BENCHMARKS)
find_package(benchmark REQUIRED)
message(STATUS "Building benchmarks")
endif()
add_subdirectory(src) add_subdirectory(src)
install(DIRECTORY approot DESTINATION share/lms) install(DIRECTORY approot DESTINATION share/lms)
+2 -1
View File
@@ -15,6 +15,7 @@ ARG LMS_BUILD_PACKAGES=" \
gcc \ gcc \
g++ \ g++ \
musl-dev \ musl-dev \
benchmark-dev \
boost-dev \ boost-dev \
ffmpeg-dev \ ffmpeg-dev \
libarchive-dev \ libarchive-dev \
@@ -35,7 +36,7 @@ ARG LMS_BUILD_TYPE="Release"
RUN \ RUN \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \ DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \
xx-info is-cross && export BUILD_TESTS=OFF || export BUILD_TESTS=ON && \ 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) && \ VERBOSE=1 make -j$(nproc) && \
xx-verify src/lms/lms && \ xx-verify src/lms/lms && \
(xx-info is-cross || make test) (xx-info is-cross || make test)
+2 -1
View File
@@ -1,6 +1,7 @@
FROM archlinux:latest FROM archlinux:latest
ARG BUILD_PACKAGES="\ ARG BUILD_PACKAGES="\
benchmark \
clang \ clang \
cmake \ cmake \
boost \ boost \
@@ -22,6 +23,6 @@ COPY . /tmp/lms/
ARG LMS_BUILD_TYPE="Release" ARG LMS_BUILD_TYPE="Release"
RUN \ RUN \
DIR=/tmp/lms/build && mkdir -p ${DIR} && cd ${DIR} && \ 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) && \ VERBOSE=1 make -j$(nproc) && \
make test make test
-3
View File
@@ -110,9 +110,6 @@ cmake_push_check_state()
set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY}) 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 # Normalize and check the component list we were given
set(want_components ${Filesystem_FIND_COMPONENTS}) set(want_components ${Filesystem_FIND_COMPONENTS})
if(Filesystem_FIND_COMPONENTS STREQUAL "") if(Filesystem_FIND_COMPONENTS STREQUAL "")
@@ -68,7 +68,7 @@ namespace Av::Transcoding
{ {
Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; Wt::Http::ResponseContinuation* continuation{ response.createContinuation() };
continuation->waitForMoreData(); 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"); LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back");
+1 -1
View File
@@ -43,7 +43,7 @@ namespace Database
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM cluster"); return session.getDboSession().query<int>("SELECT COUNT(*) FROM media_library");
} }
MediaLibrary::pointer MediaLibrary::find(Session& session, MediaLibraryId id) MediaLibrary::pointer MediaLibrary::find(Session& session, MediaLibraryId id)
+33 -4
View File
@@ -182,8 +182,8 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
static void migrateFromV40(Session& session) static void migrateFromV40(Session& session)
{ {
// add artist_display_name in Release and Track // add artist_display_name in Release and Track
session.getDboSession().execute("ALTER TABLE release 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"); 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 // 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"); 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) static void migrateFromV44(Session& session)
{ {
// add bitrate // 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 // 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"); 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 // 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("DROP TABLE track");
session.getDboSession().execute("ALTER TABLE track_backup RENAME TO track"); session.getDboSession().execute("ALTER TABLE track_backup RENAME TO track");
} }
+3 -2
View File
@@ -45,8 +45,7 @@ namespace Database
{ {
session.checkReadTransaction(); session.checkReadTransaction();
std::string selectStatement{ params.distinct ? "SELECT DISTINCT" : "SELECT" }; auto query{ session.getDboSession().query<ResultType>("SELECT " + std::string{ itemToSelect } + " FROM track t") };
auto query{ session.getDboSession().query<ResultType>(selectStatement + " " + std::string{ itemToSelect } + " FROM track t") };
assert(params.keywords.empty() || params.name.empty()); assert(params.keywords.empty() || params.name.empty());
for (std::string_view keyword : params.keywords) for (std::string_view keyword : params.keywords)
@@ -118,6 +117,8 @@ namespace Database
} }
query.where(oss.str()); query.where(oss.str());
} }
query.groupBy("t.id");
} }
assert(!(params.nonRelease && params.release.isValid())); assert(!(params.nonRelease && params.release.isValid()));
@@ -17,6 +17,8 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#pragma once
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -38,7 +40,7 @@ namespace Database
MediaLibrary() = default; MediaLibrary() = default;
// find // 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, MediaLibraryId id);
static pointer find(Session& session, std::string_view name); static pointer find(Session& session, std::string_view name);
static pointer find(Session& session, const std::filesystem::path& path); static pointer find(Session& session, const std::filesystem::path& path);
@@ -79,7 +79,6 @@ namespace Database {
TrackListId trackList; // matching this trackList TrackListId trackList; // matching this trackList
std::optional<int> trackNumber; // matching this track number std::optional<int> trackNumber; // matching this track number
MediaLibraryId mediaLibrary; // If set, tracks in this library MediaLibraryId mediaLibrary; // If set, tracks in this library
bool distinct{ true };
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; } FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; } FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
@@ -96,7 +95,6 @@ namespace Database {
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; }
FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; }
FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; }
}; };
struct PathResult struct PathResult
+11 -11
View File
@@ -92,7 +92,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
{ {
auto transaction{ session.createReadTransaction() }; 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()); EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1); 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); EXPECT_EQ(track->getArtists({}).size(), 1);
} }
@@ -120,7 +120,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
EXPECT_EQ(artists.front(), artist.getId()); EXPECT_EQ(artists.front(), artist.getId());
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1); 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); EXPECT_EQ(track->getArtistIds({}).size(), 1);
} }
@@ -195,7 +195,7 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
{ {
auto transaction{ session.createReadTransaction() }; 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() }; 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({}).size(), 2);
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).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{}).results.size(), 2);
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).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() }; 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"})) }; const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"A"})) };
ASSERT_EQ(artistsByAAA.results.size(), 1); ASSERT_EQ(artistsByAAA.results.size(), 1);
@@ -327,7 +327,7 @@ TEST_F(DatabaseFixture, Artist_findByName)
ASSERT_EQ(artistsByZZZ.results.size(), 1); ASSERT_EQ(artistsByZZZ.results.size(), 1);
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId()); 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); 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%)") }; const auto artists{ Artist::find(session, R"(MyArtist%)") };
ASSERT_TRUE(artists.size() == 1); ASSERT_TRUE(artists.size() == 1);
EXPECT_EQ(artists.front()->getId(), artist1.getId()); 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)") }; const auto artists{ Artist::find(session, R"(%MyArtist)") };
ASSERT_TRUE(artists.size() == 1); ASSERT_TRUE(artists.size() == 1);
EXPECT_EQ(artists.front()->getId(), artist2.getId()); 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)") }; const auto artists{ Artist::find(session, R"(%_MyArtist)") };
ASSERT_TRUE(artists.size() == 1); ASSERT_TRUE(artists.size() == 1);
ASSERT_EQ(artists.front()->getId(), artist3.getId()); ASSERT_EQ(artists.front()->getId(), artist3.getId());
EXPECT_TRUE(Artist::find(session, R"(%CMyArtist)").empty()); EXPECT_EQ(Artist::find(session, R"(%CMyArtist)").size(), 0);
} }
} }
+39 -39
View File
@@ -69,7 +69,7 @@ TEST_F(DatabaseFixture, Cluster)
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
clusterTypes = ClusterType::findOrphanIds(session); 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); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); 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() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
auto clusterTypes{ ClusterType::findOrphanIds(session) }; auto clusterTypes{ ClusterType::findOrphanIds(session) };
ASSERT_EQ(clusterTypes.results.size(), 1); ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
@@ -104,8 +104,8 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
auto clusters{ Cluster::findOrphanIds(session) }; auto clusters{ Cluster::findOrphanIds(session) };
EXPECT_EQ(clusters.results.size(), 2); EXPECT_EQ(clusters.results.size(), 2);
EXPECT_TRUE(track->getClusters().empty()); EXPECT_EQ(track->getClusters().size(), 0);
EXPECT_TRUE(track->getClusterIds().empty()); EXPECT_EQ(track->getClusterIds().size(), 0);
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0);
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.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); ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), cluster2.getId()); 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()); EXPECT_EQ(tracks.results.front(), track.getId());
tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.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() }; auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; 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() }; auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; 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, cluster1.getId()), 1);
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0);
} }
@@ -200,7 +200,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; 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(tracks.results.front(), track.getId());
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1);
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1); EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1);
@@ -225,7 +225,7 @@ TEST_F(DatabaseFixture, Cluster_multiTracks)
{ {
auto transaction{ session.createReadTransaction() }; 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()); EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size());
@@ -242,8 +242,8 @@ TEST_F(DatabaseFixture, ClusterType_singleTrack)
{ {
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}).results.empty()); EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).results.size(), 0);
EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.empty()); EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.size(), 0);
} }
ScopedClusterType clusterType{ session, "MyClusterType" }; ScopedClusterType clusterType{ session, "MyClusterType" };
@@ -275,7 +275,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
} }
ScopedClusterType clusterType{ session, "MyClusterType" }; ScopedClusterType clusterType{ session, "MyClusterType" };
@@ -285,7 +285,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2); 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(Release::find(session, Release::FindParameters{}).results.size(), 1);
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0);
@@ -360,10 +360,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
{ {
auto transaction{ session.createReadTransaction() }; 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_EQ(Cluster::findOrphanIds(session).results.size(), 2);
EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
} }
{ {
@@ -379,8 +379,8 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); EXPECT_EQ(artists.results.front(), artist.getId());
EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.empty()); EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.size(), 0);
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);
cluster2.get().modify()->addTrack(track.get()); cluster2.get().modify()->addTrack(track.get());
} }
@@ -400,7 +400,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
ASSERT_EQ(artists.results.size(), 1); ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId()); 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() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
} }
{ {
@@ -461,8 +461,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
} }
{ {
@@ -572,10 +572,10 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty()); EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty()); EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); 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(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() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(artist1->findSimilarArtistIds().results.empty()); EXPECT_EQ(artist1->findSimilarArtistIds().results.size(), 0);
EXPECT_TRUE(artist2->findSimilarArtistIds().results.empty()); EXPECT_EQ(artist2->findSimilarArtistIds().results.size(), 0);
EXPECT_TRUE(artist3->findSimilarArtistIds().results.empty()); EXPECT_EQ(artist3->findSimilarArtistIds().results.size(), 0);
} }
std::list<ScopedTrack> tracks; std::list<ScopedTrack> tracks;
@@ -768,7 +768,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
{ {
auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) };
EXPECT_EQ(artists.results.empty(), 1); EXPECT_EQ(artists.results.size(), 0);
} }
{ {
@@ -779,7 +779,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
{ {
auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Composer}) }; 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() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(release1->getSimilarReleases().empty()); EXPECT_EQ(release1->getSimilarReleases().size(), 0);
EXPECT_TRUE(release2->getSimilarReleases().empty()); EXPECT_EQ(release2->getSimilarReleases().size(), 0);
EXPECT_TRUE(release3->getSimilarReleases().empty()); EXPECT_EQ(release3->getSimilarReleases().size(), 0);
} }
std::list<ScopedTrack> tracks; std::list<ScopedTrack> tracks;
+2
View File
@@ -23,6 +23,7 @@
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/Listen.hpp" #include "database/Listen.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/StarredArtist.hpp" #include "database/StarredArtist.hpp"
@@ -77,6 +78,7 @@ void DatabaseFixture::testDatabaseEmpty()
EXPECT_EQ(Cluster::getCount(session), 0); EXPECT_EQ(Cluster::getCount(session), 0);
EXPECT_EQ(ClusterType::getCount(session), 0); EXPECT_EQ(ClusterType::getCount(session), 0);
EXPECT_EQ(Listen::getCount(session), 0); EXPECT_EQ(Listen::getCount(session), 0);
EXPECT_EQ(MediaLibrary::getCount(session), 0);
EXPECT_EQ(Release::getCount(session), 0); EXPECT_EQ(Release::getCount(session), 0);
EXPECT_EQ(StarredArtist::getCount(session), 0); EXPECT_EQ(StarredArtist::getCount(session), 0);
EXPECT_EQ(StarredRelease::getCount(session), 0); EXPECT_EQ(StarredRelease::getCount(session), 0);
+6 -7
View File
@@ -89,7 +89,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
{ {
auto transaction{ session.createReadTransaction() }; 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())) }; const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
ASSERT_EQ(tracks.results.size(), 1); ASSERT_EQ(tracks.results.size(), 1);
@@ -125,7 +125,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
auto transaction{ session.createWriteTransaction() }; auto transaction{ session.createWriteTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; 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) }; auto releases{ Release::findOrphanIds(session) };
ASSERT_EQ(releases.results.size(), 1); ASSERT_EQ(releases.results.size(), 1);
@@ -357,8 +357,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.empty()); EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.size(), 0);
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.empty()); 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)) }; 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()); EXPECT_EQ(tracks.results.front(), track1A.getId());
} }
{ {
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; 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()); EXPECT_EQ(tracks.results.front(), track2B.getId());
} }
} }
@@ -422,7 +422,6 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate)
track2A.get().modify()->setRelease(release2.get()); track2A.get().modify()->setRelease(release2.get());
track2B.get().modify()->setRelease(release2.get()); track2B.get().modify()->setRelease(release2.get());
track1A.get().modify()->setDate(release1Date); track1A.get().modify()->setDate(release1Date);
track1B.get().modify()->setDate(release1Date); track1B.get().modify()->setDate(release1Date);
track1A.get().modify()->setOriginalDate(release1OriginalDate); track1A.get().modify()->setOriginalDate(release1OriginalDate);
+4 -4
View File
@@ -336,19 +336,19 @@ namespace MetaData
if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid()) if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid())
track.date = date; track.date = date;
else if (!track.year) else if (!track.year)
track.year = StringUtils::readAs<int>(value); track.year = Utils::parseYear(value);
} }
else if (tag == "YEAR") else if (tag == "YEAR")
track.year = StringUtils::readAs<int>(value); track.year = Utils::parseYear(value);
else if (tag == "ORIGINALDATE") else if (tag == "ORIGINALDATE")
{ {
if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid()) if (const Wt::WDate date{ Utils::parseDate(value) }; date.isValid())
track.originalDate = date; track.originalDate = date;
else if (!track.originalYear) else if (!track.originalYear)
track.originalYear = StringUtils::readAs<int>(value); track.originalYear = Utils::parseYear(value);
} }
else if (tag == "ORIGINALYEAR") else if (tag == "ORIGINALYEAR")
track.originalYear = StringUtils::readAs<int>(value); track.originalYear = Utils::parseYear(value);
else if (tag == "METADATA_BLOCK_PICTURE") else if (tag == "METADATA_BLOCK_PICTURE")
track.hasCover = true; track.hasCover = true;
else if (tag == "COPYRIGHT") else if (tag == "COPYRIGHT")
+32
View File
@@ -64,6 +64,38 @@ namespace MetaData::Utils
return {}; return {};
} }
std::optional<int> 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) std::string_view readStyleToString(ParserReadStyle readStyle)
{ {
switch (readStyle) switch (readStyle)
+2
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <optional>
#include <string_view> #include <string_view>
#include <Wt/WDate.h> #include <Wt/WDate.h>
@@ -28,6 +29,7 @@
namespace MetaData::Utils namespace MetaData::Utils
{ {
Wt::WDate parseDate(std::string_view dateStr); Wt::WDate parseDate(std::string_view dateStr);
std::optional<int> parseYear(std::string_view yearStr);
std::string_view readStyleToString(ParserReadStyle readStyle); std::string_view readStyleToString(ParserReadStyle readStyle);
struct PerformerArtist struct PerformerArtist
+37
View File
@@ -76,6 +76,43 @@ TEST(MetaData, parseDate)
} }
} }
TEST(MetaData, parseYear)
{
using namespace MetaData::Utils;
struct TestCase
{
std::string str;
std::optional<int> 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<int> parsed{ parseYear(testCase.str) };
EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'";
}
}
TEST(MetaData, extractPerformerAndRole) TEST(MetaData, extractPerformerAndRole)
{ {
using namespace MetaData::Utils; using namespace MetaData::Utils;
@@ -131,9 +131,9 @@ namespace Feedback::ListenBrainz
request.message.addBodyText(Wt::Json::serialize(root)); request.message.addBodyText(Wt::Json::serialize(root));
request.message.addHeader("Content-Type", "application/json"); 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); onFeedbackSent(type, starredTrackId);
}); });
@@ -404,7 +404,7 @@ namespace Feedback::ListenBrainz
} }
}); });
}; };
request.onFailureFunc = [=, &context] request.onFailureFunc = [this, &context]
{ {
onSyncEnded(context); onSyncEnded(context);
}; };
@@ -105,7 +105,6 @@ namespace Recommendation
Track::FindParameters params; Track::FindParameters params;
params.setTrackList(tracklistId); params.setTrackList(tracklistId);
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
params.setDistinct(false);
for (const TrackId trackId : Track::findIds(dbSession, params).results) for (const TrackId trackId : Track::findIds(dbSession, params).results)
tracks.push_back(trackId); tracks.push_back(trackId);
@@ -139,7 +139,7 @@ namespace Scanner
void ScannerService::requestImmediateScan(bool force) void ScannerService::requestImmediateScan(bool force)
{ {
abortScan(); abortScan();
_ioService.post([=]() _ioService.post([this, force]
{ {
if (_abortScan) if (_abortScan)
return; return;
@@ -156,7 +156,7 @@ namespace Scanner
void ScannerService::requestReload() void ScannerService::requestReload()
{ {
abortScan(); abortScan();
_ioService.post([=]() _ioService.post([this]()
{ {
if (_abortScan) if (_abortScan)
return; return;
@@ -234,7 +234,7 @@ namespace Scanner
void ScannerService::scheduleScan(bool force, const Wt::WDateTime& dateTime) 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) if (ec)
return; return;
@@ -40,10 +40,10 @@
#include "Utils.hpp" #include "Utils.hpp"
namespace Scrobbling::ListenBrainz
{
namespace namespace
{ {
using namespace Scrobbling::ListenBrainz;
std::optional<Wt::Json::Object> listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint) std::optional<Wt::Json::Object> listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
{ {
auto transaction{ session.createReadTransaction() }; auto transaction{ session.createReadTransaction() };
@@ -207,8 +207,6 @@ namespace
} }
} }
namespace Scrobbling::ListenBrainz
{
ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client) ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
: _ioContext{ ioContext } : _ioContext{ ioContext }
, _db{ db } , _db{ db }
@@ -244,13 +242,13 @@ namespace Scrobbling::ListenBrainz
saveListen(timedListen, Database::SyncState::PendingAdd); saveListen(timedListen, Database::SyncState::PendingAdd);
request.priority = Http::ClientRequestParameters::Priority::Normal; 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)) if (saveListen(timedListen, Database::SyncState::Synchronized))
{ {
UserContext& context{ getUserContext(listen.userId) }; UserContext& context{ getUserContext(timedListen.userId) };
if (context.listenCount) if (context.listenCount)
(*context.listenCount)++; (*context.listenCount)++;
} }
@@ -484,11 +482,11 @@ namespace Scrobbling::ListenBrainz
Http::ClientGETRequestParameters request; Http::ClientGETRequestParameters request;
request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count"; request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count";
request.priority = Http::ClientRequestParameters::Priority::Low; 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) if (listenCount)
LOG(DEBUG, "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount); LOG(DEBUG, "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount);
@@ -520,7 +518,7 @@ namespace Scrobbling::ListenBrainz
Http::ClientGETRequestParameters request; Http::ClientGETRequestParameters request;
request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t()); request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t());
request.priority = Http::ClientRequestParameters::Priority::Low; request.priority = Http::ClientRequestParameters::Priority::Low;
request.onSuccessFunc = [=, &context](std::string_view msgBody) request.onSuccessFunc = [this, &context](std::string_view msgBody)
{ {
processGetListensResponse(msgBody, context); processGetListensResponse(msgBody, context);
if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid()) if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid())
@@ -531,7 +529,7 @@ namespace Scrobbling::ListenBrainz
enqueGetListens(context); enqueGetListens(context);
}; };
request.onFailureFunc = [=, &context] request.onFailureFunc = [this, &context]
{ {
onSyncEnded(context); onSyncEnded(context);
}; };
@@ -23,7 +23,7 @@
#include "utils/ILogger.hpp" #include "utils/ILogger.hpp"
#include "utils/UUID.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 namespace Database
{ {
+4
View File
@@ -22,3 +22,7 @@ install(TARGETS lmssom DESTINATION lib)
if(BUILD_TESTING) if(BUILD_TESTING)
add_subdirectory(test) add_subdirectory(test)
endif() endif()
if (BUILD_BENCHMARKS)
add_subdirectory(bench)
endif()
+9
View File
@@ -0,0 +1,9 @@
add_executable(bench-som
SomBench.cpp
)
target_link_libraries(bench-som PRIVATE
lmssom
benchmark
)
+54
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <random>
#include <benchmark/benchmark.h>
#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<int> matrix{ static_cast<Coordinate>(state.range(0)), static_cast<Coordinate>(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();
+9 -13
View File
@@ -26,9 +26,7 @@
namespace SOM namespace SOM
{ {
using Coordinate = unsigned; using Coordinate = unsigned;
using Norm = InputVector::value_type;
struct Position struct Position
{ {
@@ -62,18 +60,17 @@ class Matrix
_values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height)); _values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height));
} }
template<typename... CtArgs> template<typename... CtrArgs>
Matrix(Coordinate width, Coordinate height, CtArgs... args) Matrix(Coordinate width, Coordinate height, CtrArgs&& ... args)
: _width{ width } : _width{ width }
, _height{ height } , _height{ height }
{ {
_values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height), T{args...}); _values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height), T{ std::forward<CtrArgs>(args)... });
} }
void clear() void clear()
{ {
std::vector<T> values(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height)); _values.clear();
_values.swap(values);
} }
Coordinate getHeight() const { return _height; } Coordinate getHeight() const { return _height; }
@@ -101,10 +98,10 @@ class Matrix
{ {
assert(!_values.empty()); assert(!_values.empty());
auto it {std::min_element(_values.begin(), _values.end(), std::move(func))}; const auto it{ std::min_element(_values.begin(), _values.end(), std::move(func)) };
auto index {static_cast<Coordinate>(std::distance(_values.begin(), it))}; const auto index{ static_cast<Coordinate>(std::distance(_values.begin(), it)) };
return {index % _height, index / _height}; return Position{ index % _height, index / _height };
} }
private: private:
@@ -115,8 +112,8 @@ class Matrix
} // ns SOM } // ns SOM
namespace std { namespace std
{
template<> template<>
class hash<SOM::Position> class hash<SOM::Position>
{ {
@@ -128,6 +125,5 @@ class hash<SOM::Position>
return h1 ^ (h2 << 1); return h1 ^ (h2 << 1);
} }
}; };
} // ns std } // ns std
+1 -1
View File
@@ -31,12 +31,12 @@ 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, const InputVector& b);
void checkSameDimensions(const InputVector& a, std::size_t inputDimCount); void checkSameDimensions(const InputVector& a, std::size_t inputDimCount);
std::ostream& operator<<(std::ostream& os, const InputVector& a); std::ostream& operator<<(std::ostream& os, const InputVector& a);
class Network class Network
{ {
public: public:
+1 -1
View File
@@ -66,7 +66,7 @@ namespace Http
{ {
_client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) _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); onClientDone(ec, msg);
}); });
+3
View File
@@ -173,9 +173,12 @@ TEST(StringUtils, readAs_int)
EXPECT_EQ(StringUtils::readAs<int>("-1"), -1); EXPECT_EQ(StringUtils::readAs<int>("-1"), -1);
EXPECT_EQ(StringUtils::readAs<int>(""), std::nullopt); EXPECT_EQ(StringUtils::readAs<int>(""), std::nullopt);
EXPECT_EQ(StringUtils::readAs<int>("a"), std::nullopt); EXPECT_EQ(StringUtils::readAs<int>("a"), std::nullopt);
EXPECT_EQ(StringUtils::readAs<int>("-"), std::nullopt);
EXPECT_EQ(StringUtils::readAs<int>("1024-1"), 1024); EXPECT_EQ(StringUtils::readAs<int>("1024-1"), 1024);
EXPECT_EQ(StringUtils::readAs<int>("1024-"), 1024); EXPECT_EQ(StringUtils::readAs<int>("1024-"), 1024);
EXPECT_EQ(StringUtils::readAs<int>("1024/5"), 1024); EXPECT_EQ(StringUtils::readAs<int>("1024/5"), 1024);
EXPECT_EQ(StringUtils::readAs<int>("1024a"), 1024);
EXPECT_EQ(StringUtils::readAs<int>("a1024a"), std::nullopt);
} }
TEST(StringUtils, capitalize) TEST(StringUtils, capitalize)
+1 -1
View File
@@ -171,7 +171,7 @@ Auth::Auth()
{ {
auto model {std::make_shared<AuthModel>()}; auto model {std::make_shared<AuthModel>()};
auto processAuth = [=]() auto processAuth = [this, model]
{ {
updateModel(model.get()); updateModel(model.get());
+3 -3
View File
@@ -448,12 +448,12 @@ namespace UserInterface
_playQueue = mainStack->addWidget(std::move(playQueue)); _playQueue = mainStack->addWidget(std::move(playQueue));
mainStack->addNew<SettingsView>(); mainStack->addNew<SettingsView>();
searchEdit->enterPressed().connect([=] searchEdit->enterPressed().connect([this]
{ {
setInternalPath("/search", true); setInternalPath("/search", true);
}); });
searchEdit->textInput().connect([=] searchEdit->textInput().connect([this, explore, searchEdit]
{ {
setInternalPath("/search", true); setInternalPath("/search", true);
explore->search(searchEdit->text()); explore->search(searchEdit->text());
@@ -518,7 +518,7 @@ namespace UserInterface
const bool isAdmin{ getUserType() == Database::UserType::ADMIN }; const bool isAdmin{ getUserType() == Database::UserType::ADMIN };
if (isAdmin) if (isAdmin)
{ {
_scannerEvents.scanComplete.connect([=](const Scanner::ScanStats& stats) _scannerEvents.scanComplete.connect([this](const Scanner::ScanStats& stats)
{ {
notifyMsg(Notification::Type::Info, notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.database"),
+1 -1
View File
@@ -256,7 +256,7 @@ namespace UserInterface
<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << "," << " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
<< " replayGain: " << replayGain << "," << " replayGain: " << replayGain << ","
<< " title: \"" << StringUtils::jsEscape(track->getName()) << "\"," << " 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()) : "") << "\"," << " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
<< " artwork: [" << " artwork: ["
<< " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
+1 -1
View File
@@ -25,7 +25,7 @@ namespace UserInterface
ModalManager::ModalManager() ModalManager::ModalManager()
: _closed{ this, "closed" } : _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 << "'"); LMS_LOG(UI, DEBUG, "Received closed for id '" << id << "'");
for (int i{}; i < count(); ++i) for (int i{}; i < count(); ++i)
+1 -1
View File
@@ -80,7 +80,7 @@ namespace UserInterface
{ {
NotificationWidget* notification{ addNew<NotificationWidget>(type, category, message, duration) }; NotificationWidget* notification{ addNew<NotificationWidget>(type, category, message, duration) };
notification->closed.connect([=] notification->closed.connect([this, notification]
{ {
removeWidget(notification); removeWidget(notification);
}); });
+11 -12
View File
@@ -126,13 +126,13 @@ namespace UserInterface
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WPushButton* clearBtn{ bindNew<Wt::WPushButton>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* clearBtn{ bindNew<Wt::WPushButton>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML) };
clearBtn->clicked().connect([=] clearBtn->clicked().connect([this]
{ {
clearTracks(); clearTracks();
}); });
Wt::WPushButton* saveBtn{ bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* saveBtn{ bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML) };
saveBtn->clicked().connect([=] saveBtn->clicked().connect([this]
{ {
saveAsTrackList(); saveAsTrackList();
}); });
@@ -145,7 +145,7 @@ namespace UserInterface
}); });
Wt::WPushButton* shuffleBtn{ bindNew<Wt::WPushButton>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* shuffleBtn{ bindNew<Wt::WPushButton>("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() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -163,7 +163,7 @@ namespace UserInterface
}); });
_repeatBtn = bindNew<Wt::WCheckBox>("repeat-btn"); _repeatBtn = bindNew<Wt::WCheckBox>("repeat-btn");
_repeatBtn->clicked().connect([=] _repeatBtn->clicked().connect([this]
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -177,7 +177,7 @@ namespace UserInterface
} }
_radioBtn = bindNew<Wt::WCheckBox>("radio-btn"); _radioBtn = bindNew<Wt::WCheckBox>("radio-btn");
_radioBtn->clicked().connect([=] _radioBtn->clicked().connect([this]
{ {
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -203,7 +203,7 @@ namespace UserInterface
_nbTracks = bindNew<Wt::WText>("track-count"); _nbTracks = bindNew<Wt::WText>("track-count");
_duration = bindNew<Wt::WText>("duration"); _duration = bindNew<Wt::WText>("duration");
LmsApp->getMediaPlayer().settingsLoaded.connect([=] LmsApp->getMediaPlayer().settingsLoaded.connect([this]
{ {
if (_mediaPlayerSettingsLoaded) if (_mediaPlayerSettingsLoaded)
return; return;
@@ -220,7 +220,7 @@ namespace UserInterface
loadTrack(trackPos, false); loadTrack(trackPos, false);
}); });
LmsApp->preQuit().connect([=] LmsApp->preQuit().connect([this]
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -537,7 +537,7 @@ namespace UserInterface
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain); entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
playBtn->clicked().connect([=] playBtn->clicked().connect([this, entry]
{ {
const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) }; const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) };
if (pos) if (pos)
@@ -546,7 +546,7 @@ namespace UserInterface
Wt::WPushButton* delBtn{ entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* delBtn{ entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML) };
delBtn->setToolTip(Wt::WString::tr("Lms.delete")); 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 // Remove the entry n both the widget tree and the playqueue
{ {
@@ -572,7 +572,7 @@ namespace UserInterface
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play")) entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([=] ->clicked().connect([this, entry]
{ {
const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) }; const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) };
if (pos) if (pos)
@@ -734,7 +734,7 @@ namespace UserInterface
replaceTrackList->updateView(replaceTrackListModel.get()); replaceTrackList->updateView(replaceTrackListModel.get());
auto* saveBtn{ modal->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.save")) }; auto* saveBtn{ modal->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.save")) };
saveBtn->clicked().connect([=] saveBtn->clicked().connect([=, this]
{ {
bool success{}; bool success{};
switch (contentStack->currentIndex()) switch (contentStack->currentIndex())
@@ -795,7 +795,6 @@ namespace UserInterface
Track::FindParameters params; Track::FindParameters params;
params.setTrackList(_queueId); params.setTrackList(_queueId);
params.setDistinct(false);
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
Track::find(session, params, [&](const Track::pointer& track) Track::find(session, params, [&](const Track::pointer& track)
+1 -1
View File
@@ -131,7 +131,7 @@ InitWizardView::InitWizardView()
Wt::WPushButton* saveButton = bindNew<Wt::WPushButton>("create-btn", Wt::WString::tr("Lms.create")); Wt::WPushButton* saveButton = bindNew<Wt::WPushButton>("create-btn", Wt::WString::tr("Lms.create"));
saveButton->clicked().connect([=] saveButton->clicked().connect([=, this]
{ {
updateModel(model.get()); updateModel(model.get());
+7 -7
View File
@@ -44,7 +44,7 @@ namespace UserInterface
auto mediaLibraryModal{ std::make_unique<MediaLibraryModal>(Database::MediaLibraryId{}) }; auto mediaLibraryModal{ std::make_unique<MediaLibraryModal>(Database::MediaLibraryId{}) };
MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() };
mediaLibraryModalPtr->saved().connect(this, [=](Database::MediaLibraryId newMediaLibraryId) mediaLibraryModalPtr->saved().connect(this, [this, mediaLibraryModalPtr](Database::MediaLibraryId newMediaLibraryId)
{ {
Wt::WTemplate* entry{ addEntry() }; Wt::WTemplate* entry{ addEntry() };
updateEntry(newMediaLibraryId, entry); updateEntry(newMediaLibraryId, entry);
@@ -53,7 +53,7 @@ namespace UserInterface
LmsApp->getModalManager().dispose(mediaLibraryModalPtr); LmsApp->getModalManager().dispose(mediaLibraryModalPtr);
}); });
mediaLibraryModalPtr->cancelled().connect(this, [=] mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr]
{ {
LmsApp->getModalManager().dispose(mediaLibraryModalPtr); LmsApp->getModalManager().dispose(mediaLibraryModalPtr);
}); });
@@ -95,7 +95,7 @@ namespace UserInterface
Wt::WWidget* modalPtr{ modal.get() }; Wt::WWidget* modalPtr{ modal.get() };
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) }; auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
delBtn->clicked().connect([=] delBtn->clicked().connect([=, this]
{ {
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -138,12 +138,12 @@ namespace UserInterface
Wt::WPushButton* editBtn{ entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* editBtn{ entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML) };
editBtn->setToolTip(Wt::WString::tr("Lms.edit")); editBtn->setToolTip(Wt::WString::tr("Lms.edit"));
editBtn->clicked().connect([=] editBtn->clicked().connect([this, mediaLibraryId, entry]
{ {
auto mediaLibraryModal{ std::make_unique<MediaLibraryModal>(mediaLibraryId) }; auto mediaLibraryModal{ std::make_unique<MediaLibraryModal>(mediaLibraryId) };
MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() };
mediaLibraryModalPtr->saved().connect(this, [=](Database::MediaLibraryId newMediaLibraryId) mediaLibraryModalPtr->saved().connect(this, [=, this](Database::MediaLibraryId newMediaLibraryId)
{ {
updateEntry(newMediaLibraryId, entry); updateEntry(newMediaLibraryId, entry);
@@ -154,7 +154,7 @@ namespace UserInterface
LmsApp->getModalManager().dispose(mediaLibraryModalPtr); LmsApp->getModalManager().dispose(mediaLibraryModalPtr);
}); });
mediaLibraryModalPtr->cancelled().connect(this, [=] mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr]
{ {
LmsApp->getModalManager().dispose(mediaLibraryModalPtr); LmsApp->getModalManager().dispose(mediaLibraryModalPtr);
}); });
@@ -164,7 +164,7 @@ namespace UserInterface
Wt::WPushButton* delBtn{ entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* delBtn{ entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) };
delBtn->setToolTip(Wt::WString::tr("Lms.delete")); delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
delBtn->clicked().connect([=] delBtn->clicked().connect([this, mediaLibraryId, entry]
{ {
showDeleteLibraryModal(mediaLibraryId, entry); showDeleteLibraryModal(mediaLibraryId, entry);
}); });
+2 -2
View File
@@ -189,7 +189,7 @@ namespace UserInterface
setFormWidget(MediaLibraryModel::DirectoryField, std::make_unique<Wt::WLineEdit>()); setFormWidget(MediaLibraryModel::DirectoryField, std::make_unique<Wt::WLineEdit>());
Wt::WPushButton* saveBtn{ bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr(mediaLibraryId.isValid() ? "Lms.save" : "Lms.create")) }; Wt::WPushButton* saveBtn{ bindNew<Wt::WPushButton>("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()); updateModel(model.get());
@@ -205,7 +205,7 @@ namespace UserInterface
}); });
Wt::WPushButton* cancelBtn{ bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) }; Wt::WPushButton* cancelBtn{ bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
cancelBtn->clicked().connect(this, [=] {cancelled().emit();}); cancelBtn->clicked().connect(this, [this] { cancelled().emit(); });
updateView(model.get()); updateView(model.get());
} }
+2 -2
View File
@@ -101,14 +101,14 @@ namespace UserInterface
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML); Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML);
delBtn->setToolTip(Wt::WString::tr("Lms.delete")); delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
delBtn->clicked().connect([=] delBtn->clicked().connect([this, userId, entry]
{ {
auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user")) }; auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user")) };
modal->addFunction("tr", &Wt::WTemplate::Functions::tr); modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WWidget* modalPtr{ modal.get() }; Wt::WWidget* modalPtr{ modal.get() };
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) }; auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
delBtn->clicked().connect([=] delBtn->clicked().connect([=, this]
{ {
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
+7 -7
View File
@@ -139,7 +139,7 @@ namespace UserInterface
{ {
const Database::ClusterId clusterId = cluster->getId(); const Database::ClusterId clusterId = cluster->getId();
Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) };
entry->clicked().connect([=] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
}); });
@@ -150,23 +150,23 @@ namespace UserInterface
bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain); bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::Play, { _artistId }); _playQueueController.processCommand(PlayQueueController::Command::Play, { _artistId });
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _artistId }); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _artistId });
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _artistId }); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _artistId });
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _artistId }); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _artistId });
}); });
@@ -174,10 +174,10 @@ namespace UserInterface
->setLink(Wt::WLink{ std::make_unique<DownloadArtistResource>(_artistId) }); ->setLink(Wt::WLink{ std::make_unique<DownloadArtistResource>(_artistId) });
{ {
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _artistId); } }; auto isStarred{ [this] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _artistId); } };
Wt::WPushButton* starBtn{ bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; Wt::WPushButton* starBtn{ bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
starBtn->clicked().connect([=] starBtn->clicked().connect([=, this]
{ {
if (isStarred()) if (isStarred())
{ {
+1 -1
View File
@@ -44,7 +44,7 @@ Artists::Artists(Filters& filters)
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ArtistCollector::Mode mode) auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ArtistCollector::Mode mode)
{ {
auto *menuItem {bindNew<Wt::WPushButton>(var, title)}; auto *menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([=, this]
{ {
refreshView(mode); refreshView(mode);
_currentActiveItem->removeStyleClass("active"); _currentActiveItem->removeStyleClass("active");
+7 -14
View File
@@ -31,12 +31,11 @@
#include "Utils.hpp" #include "Utils.hpp"
#include "ModalManager.hpp" #include "ModalManager.hpp"
namespace UserInterface { namespace UserInterface
{
using namespace Database; using namespace Database;
void void Filters::showDialog()
Filters::showDialog()
{ {
auto dialog{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.add-filter")) }; auto dialog{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.add-filter")) };
Wt::WWidget* dialogPtr{ dialog.get() }; Wt::WWidget* dialogPtr{ dialog.get() };
@@ -47,7 +46,7 @@ Filters::showDialog()
Wt::WComboBox* valueCombo{ dialog->bindNew<Wt::WComboBox>("value") }; Wt::WComboBox* valueCombo{ dialog->bindNew<Wt::WComboBox>("value") };
Wt::WPushButton* addBtn{ dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) }; Wt::WPushButton* addBtn{ dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) };
addBtn->clicked().connect([=] addBtn->clicked().connect([this, typeCombo, valueCombo, dialogPtr]
{ {
const std::string type{ typeCombo->valueText().toUTF8() }; const std::string type{ typeCombo->valueText().toUTF8() };
const std::string value{ valueCombo->valueText().toUTF8() }; const std::string value{ valueCombo->valueText().toUTF8() };
@@ -79,7 +78,6 @@ Filters::showDialog()
LmsApp->getModalManager().dispose(dialogPtr); LmsApp->getModalManager().dispose(dialogPtr);
}); });
// Populate data // Populate data
{ {
auto transaction{ LmsApp->getDbSession().createReadTransaction() }; auto transaction{ LmsApp->getDbSession().createReadTransaction() };
@@ -103,7 +101,7 @@ Filters::showDialog()
} }
} }
typeCombo->changed().connect([=] typeCombo->changed().connect([this, typeCombo, valueCombo]
{ {
const std::string name{ typeCombo->valueText().toUTF8() }; const std::string name{ typeCombo->valueText().toUTF8() };
@@ -122,8 +120,7 @@ Filters::showDialog()
LmsApp->getModalManager().show(std::move(dialog)); LmsApp->getModalManager().show(std::move(dialog));
} }
void void Filters::add(ClusterId clusterId)
Filters::add(ClusterId clusterId)
{ {
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds)) if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), clusterId) != std::cend(_clusterIds))
return; return;
@@ -140,7 +137,7 @@ Filters::add(ClusterId clusterId)
_clusterIds.push_back(clusterId); _clusterIds.push_back(clusterId);
filter->clicked().connect([=] filter->clicked().connect([this, filter, clusterId]
{ {
_filters->removeWidget(filter); _filters->removeWidget(filter);
_clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](ClusterId id) { return id == clusterId; }), std::end(_clusterIds)); _clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](ClusterId id) { return id == clusterId; }), std::end(_clusterIds));
@@ -165,8 +162,4 @@ Filters::Filters()
_filters = bindNew<Wt::WContainerWidget>("clusters"); _filters = bindNew<Wt::WContainerWidget>("clusters");
} }
} // namespace UserInterface } // namespace UserInterface
+2 -1
View File
@@ -35,8 +35,9 @@ namespace UserInterface
public: public:
Filters(); Filters();
void add(Database::ClusterId clusterId);
const std::vector<Database::ClusterId>& getClusterIds() const { return _clusterIds; } const std::vector<Database::ClusterId>& getClusterIds() const { return _clusterIds; }
void add(Database::ClusterId clusterId);
Wt::Signal<>& updated() { return _sigUpdated; } Wt::Signal<>& updated() { return _sigUpdated; }
private: private:
@@ -100,7 +100,6 @@ namespace UserInterface
params.setClusters(clusters); params.setClusters(clusters);
params.setRange(Database::Range{ 0, maxTrackCount }); params.setRange(Database::Range{ 0, maxTrackCount });
params.setSortMethod(TrackSortMethod::TrackList); params.setSortMethod(TrackSortMethod::TrackList);
params.setDistinct(false);
return Database::Track::findIds(session, params).results; return Database::Track::findIds(session, params).results;
} }
+14 -16
View File
@@ -271,7 +271,7 @@ namespace UserInterface
{ {
const ClusterId clusterId{ cluster->getId() }; const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) };
entry->clicked().connect([=] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
}); });
@@ -280,25 +280,25 @@ namespace UserInterface
} }
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId }); _playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId });
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId }); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId });
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId }); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId });
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId }); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId });
}); });
@@ -307,16 +307,16 @@ namespace UserInterface
->setLink(Wt::WLink{ std::make_unique<DownloadReleaseResource>(_releaseId) }); ->setLink(Wt::WLink{ std::make_unique<DownloadReleaseResource>(_releaseId) });
bindNew<Wt::WPushButton>("release-info", Wt::WString::tr("Lms.Explore.release-info")) bindNew<Wt::WPushButton>("release-info", Wt::WString::tr("Lms.Explore.release-info"))
->clicked().connect([=] ->clicked().connect([this]
{ {
showReleaseInfoModal(_releaseId); showReleaseInfoModal(_releaseId);
}); });
{ {
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _releaseId); } }; auto isStarred{ [this] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _releaseId); } };
Wt::WPushButton* starBtn{ bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; Wt::WPushButton* starBtn{ bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
starBtn->clicked().connect([=] starBtn->clicked().connect([=, this]
{ {
if (isStarred()) if (isStarred())
{ {
@@ -410,7 +410,7 @@ namespace UserInterface
} }
Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
playBtn->clicked().connect([=] playBtn->clicked().connect([this, trackId]
{ {
_playQueueController.playTrackInRelease(trackId); _playQueueController.playTrackInRelease(trackId);
}); });
@@ -418,17 +418,17 @@ namespace UserInterface
{ {
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play")) entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([=] ->clicked().connect([this, trackId]
{ {
_playQueueController.playTrackInRelease(trackId); _playQueueController.playTrackInRelease(trackId);
}); });
entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next")) entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
->clicked().connect([=] ->clicked().connect([this, trackId]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId }); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId });
}); });
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last")) entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=] ->clicked().connect([this, trackId]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId }); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId });
}); });
@@ -436,10 +436,8 @@ namespace UserInterface
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } }; auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } };
Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("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()) if (isStarred())
{ {
Service<Feedback::IFeedbackService>::get()->unstar(LmsApp->getUserId(), trackId); Service<Feedback::IFeedbackService>::get()->unstar(LmsApp->getUserId(), trackId);
@@ -456,7 +454,7 @@ namespace UserInterface
->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) }); ->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) });
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info")) entry->bindNew<Wt::WPushButton>("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); entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
+4 -4
View File
@@ -47,7 +47,7 @@ namespace UserInterface
auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode) auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode)
{ {
auto* menuItem {bindNew<Wt::WPushButton>(var, title)}; auto* menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([this, mode, menuItem]
{ {
refreshView(mode); refreshView(mode);
_currentActiveItem->removeStyleClass("active"); _currentActiveItem->removeStyleClass("active");
@@ -76,17 +76,17 @@ namespace UserInterface
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases()); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllReleases());
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllReleases()); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllReleases());
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases()); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllReleases());
}); });
+2 -2
View File
@@ -77,7 +77,7 @@ namespace UserInterface
auto bindMenuItem{ [this](std::size_t index, const std::string& var, const Wt::WString& title) auto bindMenuItem{ [this](std::size_t index, const std::string& var, const Wt::WString& title)
{ {
Wt::WPushButton* menuItem {bindNew<Wt::WPushButton>(var, title)}; Wt::WPushButton* menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([this, menuItem, index]
{ {
_stack->setCurrentIndex(index); _stack->setCurrentIndex(index);
_currentActiveItem->removeStyleClass("active"); _currentActiveItem->removeStyleClass("active");
@@ -96,7 +96,7 @@ namespace UserInterface
bindMenuItem(1, "artists", Wt::WString::tr("Lms.Explore.artists")); bindMenuItem(1, "artists", Wt::WString::tr("Lms.Explore.artists"));
bindMenuItem(2, "tracks", Wt::WString::tr("Lms.Explore.tracks")); bindMenuItem(2, "tracks", Wt::WString::tr("Lms.Explore.tracks"));
filters.updated().connect([=] filters.updated().connect([this]
{ {
refreshView(); refreshView();
}); });
+6 -7
View File
@@ -108,7 +108,7 @@ namespace UserInterface
{ {
const ClusterId clusterId{ cluster->getId() }; const ClusterId clusterId{ cluster->getId() };
Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) };
entry->clicked().connect([=] entry->clicked().connect([this, clusterId]
{ {
_filters.add(clusterId); _filters.add(clusterId);
}); });
@@ -117,19 +117,19 @@ namespace UserInterface
} }
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=] ->clicked().connect([this, trackListId]
{ {
_playQueueController.processCommand(PlayQueueController::Command::Play, *trackListId); _playQueueController.processCommand(PlayQueueController::Command::Play, *trackListId);
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this, trackListId]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, *trackListId); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, *trackListId);
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this, trackListId]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, *trackListId); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, *trackListId);
}); });
@@ -138,14 +138,14 @@ namespace UserInterface
->setLink(Wt::WLink{ std::make_unique<DownloadTrackListResource>(*trackListId) }); ->setLink(Wt::WLink{ std::make_unique<DownloadTrackListResource>(*trackListId) });
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete")) bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
->clicked().connect([=] ->clicked().connect([this, trackListId]
{ {
auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) }; auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) };
modal->addFunction("tr", &Wt::WTemplate::Functions::tr); modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WWidget* modalPtr{ modal.get() }; Wt::WWidget* modalPtr{ modal.get() };
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) }; auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
delBtn->clicked().connect([=] delBtn->clicked().connect([this, trackListId, modalPtr]
{ {
{ {
auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
@@ -187,7 +187,6 @@ namespace UserInterface
params.setTrackList(_trackListId); params.setTrackList(_trackListId);
params.setSortMethod(Database::TrackSortMethod::TrackList); params.setSortMethod(Database::TrackSortMethod::TrackList);
params.setRange(Database::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize }); params.setRange(Database::Range{ static_cast<std::size_t>(_container->getCount()), _batchSize });
params.setDistinct(false);
Database::Track::find(LmsApp->getDbSession(), params, [this](const Track::pointer& track) Database::Track::find(LmsApp->getDbSession(), params, [this](const Track::pointer& track)
{ {
+1 -1
View File
@@ -44,7 +44,7 @@ namespace UserInterface
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, Mode mode) auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, Mode mode)
{ {
auto *menuItem {bindNew<Wt::WPushButton>(var, title)}; auto *menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([this, mode, menuItem]
{ {
_mode = mode; _mode = mode;
refreshView(); refreshView();
+5 -5
View File
@@ -47,7 +47,7 @@ namespace UserInterface
auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, TrackCollector::Mode mode) auto bindMenuItem{ [this](const std::string& var, const Wt::WString& title, TrackCollector::Mode mode)
{ {
auto* menuItem {bindNew<Wt::WPushButton>(var, title)}; auto* menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=] menuItem->clicked().connect([this, mode, menuItem]
{ {
refreshView(mode); refreshView(mode);
_currentActiveItem->removeStyleClass("active"); _currentActiveItem->removeStyleClass("active");
@@ -70,23 +70,23 @@ namespace UserInterface
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), TrackCollector::Mode::All); bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), TrackCollector::Mode::All);
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::Play, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayNext, getAllTracks());
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=] ->clicked().connect([this]
{ {
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks()); _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks());
}); });
+13
View File
@@ -31,6 +31,7 @@
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
@@ -45,6 +46,7 @@ namespace
{ {
struct GeneratorParameters struct GeneratorParameters
{ {
std::size_t mediaLibraryCount{ 1 };
std::size_t releaseCountPerBatch{ 1000 }; std::size_t releaseCountPerBatch{ 1000 };
std::size_t releaseCount{ 100 }; std::size_t releaseCount{ 100 };
std::size_t trackCountPerRelease{ 10 }; std::size_t trackCountPerRelease{ 10 };
@@ -59,6 +61,7 @@ namespace
struct GenerationContext struct GenerationContext
{ {
Database::Session& session; Database::Session& session;
std::vector<Database::MediaLibrary::pointer> mediaLibraries;
std::vector<Database::Cluster::pointer> genres; std::vector<Database::Cluster::pointer> genres;
std::vector<Database::Cluster::pointer> moods; std::vector<Database::Cluster::pointer> moods;
GenerationContext(Database::Session& _session) : session{ _session } {} GenerationContext(Database::Session& _session) : session{ _session } {}
@@ -99,12 +102,16 @@ namespace
track.modify()->setTrackMBID(UUID::generate()); track.modify()->setTrackMBID(UUID::generate());
track.modify()->setRecordingMBID(UUID::generate()); track.modify()->setRecordingMBID(UUID::generate());
track.modify()->setTotalTrack(params.trackCountPerRelease); 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::Artist);
TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist); TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist);
std::vector<ObjectPtr<Cluster>> clusters; std::vector<ObjectPtr<Cluster>> clusters;
if (!context.genres.empty())
clusters.push_back(*Random::pickRandom(context.genres)); clusters.push_back(*Random::pickRandom(context.genres));
if (!context.moods.empty())
clusters.push_back(*Random::pickRandom(context.moods)); clusters.push_back(*Random::pickRandom(context.moods));
track.modify()->setClusters(clusters); track.modify()->setClusters(clusters);
} }
@@ -128,6 +135,10 @@ namespace
{ {
auto transaction{ context.session.createWriteTransaction() }; 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<Database::MediaLibrary>());
// create some random genres/moods // create some random genres/moods
{ {
Database::ClusterType::pointer genre{ Database::ClusterType::find(context.session, "GENRE") }; 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" }; po::options_description options{ "Options" };
options.add_options() options.add_options()
("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "lms config file") ("conf,c", po::value<std::string>()->default_value("/etc/lms.conf"), "lms config file")
("media-library-count", po::value<unsigned>()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use")
("release-count-per-batch", po::value<unsigned>()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction") ("release-count-per-batch", po::value<unsigned>()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction")
("release-count", po::value<unsigned>()->default_value(defaultParams.releaseCount), "Number of releases to generate") ("release-count", po::value<unsigned>()->default_value(defaultParams.releaseCount), "Number of releases to generate")
("track-count-per-release", po::value<unsigned>()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release") ("track-count-per-release", po::value<unsigned>()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release")
@@ -187,6 +199,7 @@ int main(int argc, char* argv[])
po::notify(vm); po::notify(vm);
GeneratorParameters genParams; GeneratorParameters genParams;
genParams.mediaLibraryCount = vm["media-library-count"].as<unsigned>();
genParams.releaseCountPerBatch = vm["release-count-per-batch"].as<unsigned>(); genParams.releaseCountPerBatch = vm["release-count-per-batch"].as<unsigned>();
genParams.releaseCount = vm["release-count"].as<unsigned>(); genParams.releaseCount = vm["release-count"].as<unsigned>();
genParams.trackCountPerRelease = vm["track-count-per-release"].as<unsigned>(); genParams.trackCountPerRelease = vm["track-count-per-release"].as<unsigned>();
+21 -5
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
if [ "$#" -ne 6 ]; then if [ "$#" -lt 6 ] || [ "$#" -gt 7 ]; then
echo "Usage: $0 <base_url> <user> <artist_count> <album_count> <song_count> <batch_size>" echo "Usage: $0 <base_url> <user> <artist_count> <album_count> <song_count> <batch_size> [musicFolderId]"
exit 1 exit 1
fi fi
@@ -19,21 +19,37 @@ album_count="$4"
song_count="$5" song_count="$5"
batch_size="$6" 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) start_time=$(date +%s.%3N)
# artists # artists
echo "Fetching $artist_count artists..."
for ((i = 0; i < artist_count; i += $batch_size)); do 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 done
# albums # albums
echo "Fetching $album_count albums..."
for ((i = 0; i < album_count; i += $batch_size)); do 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 done
# songs # songs
echo "Fetching $song_count songs..."
for ((i = 0; i < song_count; i += $batch_size)); do 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 done
end_time=$(date +%s.%3N) end_time=$(date +%s.%3N)