Merge branch 'develop' for release v3.49.0
This commit is contained in:
+8
-1
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
class Session;
|
class Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Feedback::ListenBrainz::Utils
|
namespace Feedback::ListenBrainz::Utils
|
||||||
{
|
{
|
||||||
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
|
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
|
||||||
std::string parseValidateToken(std::string_view msgBody);
|
std::string parseValidateToken(std::string_view msgBody);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,175 +40,173 @@
|
|||||||
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using namespace Scrobbling::ListenBrainz;
|
|
||||||
|
|
||||||
std::optional<Wt::Json::Object> listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
|
||||||
{
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
|
|
||||||
const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) };
|
|
||||||
if (!track)
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) };
|
|
||||||
if (artists.empty())
|
|
||||||
artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist });
|
|
||||||
|
|
||||||
if (artists.empty())
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist");
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::Json::Object additionalInfo;
|
|
||||||
additionalInfo["listening_from"] = "LMS";
|
|
||||||
additionalInfo["duration_ms"] = std::chrono::duration_cast<std::chrono::milliseconds>(track->getDuration()).count();
|
|
||||||
if (track->getRelease())
|
|
||||||
{
|
|
||||||
if (auto MBID{ track->getRelease()->getMBID() })
|
|
||||||
additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
Wt::Json::Array artistMBIDs;
|
|
||||||
for (const Database::Artist::pointer& artist : artists)
|
|
||||||
{
|
|
||||||
if (auto MBID{ artist->getMBID() })
|
|
||||||
artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!artistMBIDs.empty())
|
|
||||||
additionalInfo["artist_mbids"] = std::move(artistMBIDs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto MBID{ track->getTrackMBID() })
|
|
||||||
additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
|
||||||
|
|
||||||
if (auto MBID{ track->getRecordingMBID() })
|
|
||||||
additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
|
||||||
|
|
||||||
if (const std::optional<std::size_t> trackNumber{ track->getTrackNumber() })
|
|
||||||
additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast<long long int>(*trackNumber) };
|
|
||||||
|
|
||||||
Wt::Json::Object trackMetadata;
|
|
||||||
trackMetadata["additional_info"] = std::move(additionalInfo);
|
|
||||||
trackMetadata["artist_name"] = Wt::Json::Value{ artists.front()->getName() };
|
|
||||||
trackMetadata["track_name"] = Wt::Json::Value{ track->getName() };
|
|
||||||
if (track->getRelease())
|
|
||||||
trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() };
|
|
||||||
|
|
||||||
Wt::Json::Object payload;
|
|
||||||
payload["track_metadata"] = std::move(trackMetadata);
|
|
||||||
if (timePoint.isValid())
|
|
||||||
payload["listened_at"] = Wt::Json::Value{ static_cast<long long int>(timePoint.toTime_t()) };
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType)
|
|
||||||
{
|
|
||||||
std::string res;
|
|
||||||
|
|
||||||
std::optional<Wt::Json::Object> payload{ listenToJsonPayload(session, listen, timePoint) };
|
|
||||||
if (!payload)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
Wt::Json::Object root;
|
|
||||||
root["listen_type"] = Wt::Json::Value{ std::string {listenType} };
|
|
||||||
root["payload"] = Wt::Json::Array{ std::move(*payload) };
|
|
||||||
|
|
||||||
res = Wt::Json::serialize(root);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::size_t> parseListenCount(std::string_view msgBody)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Wt::Json::Object root;
|
|
||||||
Wt::Json::parse(std::string{ msgBody }, root);
|
|
||||||
|
|
||||||
const Wt::Json::Object& payload{ static_cast<const Wt::Json::Object&>(root.get("payload")) };
|
|
||||||
return static_cast<int>(payload.get("count"));
|
|
||||||
}
|
|
||||||
catch (const Wt::WException& e)
|
|
||||||
{
|
|
||||||
LOG(ERROR, "Cannot parse listen count response: " << e.what());
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen)
|
|
||||||
{
|
|
||||||
using namespace Database;
|
|
||||||
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
|
|
||||||
// first try to match using track MBID, and then fallback on possibly ambiguous info
|
|
||||||
if (listen.trackMBID)
|
|
||||||
{
|
|
||||||
const auto tracks{ Track::findByMBID(session, *listen.trackMBID) };
|
|
||||||
// if duplicated files, do not record it (let the user correct its database)
|
|
||||||
if (tracks.size() == 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Matched listen '" << listen << "' using track MBID");
|
|
||||||
return tracks.front()->getId();
|
|
||||||
}
|
|
||||||
else if (tracks.size() > 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Too many matches for listen '" << listen << "' using track MBID!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen.recordingMBID)
|
|
||||||
{
|
|
||||||
const auto tracks{ Track::findByRecordingMBID(session, *listen.recordingMBID) };
|
|
||||||
// if duplicated files, do not record it (let the user correct its database)
|
|
||||||
if (tracks.size() == 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Matched listen '" << listen << "' using recording MBID");
|
|
||||||
return tracks.front()->getId();
|
|
||||||
}
|
|
||||||
else if (tracks.size() > 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Too many matches for listen '" << listen << "' using recording MBID!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(!listen.trackName.empty() && !listen.artistName.empty());
|
|
||||||
|
|
||||||
// TODO check release MBID?
|
|
||||||
Track::FindParameters params;
|
|
||||||
params.setName(listen.trackName);
|
|
||||||
params.setReleaseName(listen.releaseName);
|
|
||||||
params.setArtistName(listen.artistName);
|
|
||||||
if (listen.trackNumber)
|
|
||||||
params.setTrackNumber(*listen.trackNumber);
|
|
||||||
|
|
||||||
const auto tracks{ Track::findIds(session, params) };
|
|
||||||
// conservative behavior: in case of multiple matches: reject
|
|
||||||
if (tracks.results.size() == 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Matched listen '" << listen << "' using metadata");
|
|
||||||
return tracks.results.front();
|
|
||||||
}
|
|
||||||
else if (tracks.results.size() > 1)
|
|
||||||
{
|
|
||||||
LOG(DEBUG, "Too many matches for listen '" << listen << "' using metadata");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(DEBUG, "No match for listen '" << listen << "'");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Scrobbling::ListenBrainz
|
namespace Scrobbling::ListenBrainz
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<Wt::Json::Object> listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) };
|
||||||
|
if (!track)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) };
|
||||||
|
if (artists.empty())
|
||||||
|
artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist });
|
||||||
|
|
||||||
|
if (artists.empty())
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::Json::Object additionalInfo;
|
||||||
|
additionalInfo["listening_from"] = "LMS";
|
||||||
|
additionalInfo["duration_ms"] = std::chrono::duration_cast<std::chrono::milliseconds>(track->getDuration()).count();
|
||||||
|
if (track->getRelease())
|
||||||
|
{
|
||||||
|
if (auto MBID{ track->getRelease()->getMBID() })
|
||||||
|
additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::Json::Array artistMBIDs;
|
||||||
|
for (const Database::Artist::pointer& artist : artists)
|
||||||
|
{
|
||||||
|
if (auto MBID{ artist->getMBID() })
|
||||||
|
artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!artistMBIDs.empty())
|
||||||
|
additionalInfo["artist_mbids"] = std::move(artistMBIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto MBID{ track->getTrackMBID() })
|
||||||
|
additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
|
|
||||||
|
if (auto MBID{ track->getRecordingMBID() })
|
||||||
|
additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
|
|
||||||
|
if (const std::optional<std::size_t> trackNumber{ track->getTrackNumber() })
|
||||||
|
additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast<long long int>(*trackNumber) };
|
||||||
|
|
||||||
|
Wt::Json::Object trackMetadata;
|
||||||
|
trackMetadata["additional_info"] = std::move(additionalInfo);
|
||||||
|
trackMetadata["artist_name"] = Wt::Json::Value{ artists.front()->getName() };
|
||||||
|
trackMetadata["track_name"] = Wt::Json::Value{ track->getName() };
|
||||||
|
if (track->getRelease())
|
||||||
|
trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() };
|
||||||
|
|
||||||
|
Wt::Json::Object payload;
|
||||||
|
payload["track_metadata"] = std::move(trackMetadata);
|
||||||
|
if (timePoint.isValid())
|
||||||
|
payload["listened_at"] = Wt::Json::Value{ static_cast<long long int>(timePoint.toTime_t()) };
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType)
|
||||||
|
{
|
||||||
|
std::string res;
|
||||||
|
|
||||||
|
std::optional<Wt::Json::Object> payload{ listenToJsonPayload(session, listen, timePoint) };
|
||||||
|
if (!payload)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
Wt::Json::Object root;
|
||||||
|
root["listen_type"] = Wt::Json::Value{ std::string {listenType} };
|
||||||
|
root["payload"] = Wt::Json::Array{ std::move(*payload) };
|
||||||
|
|
||||||
|
res = Wt::Json::serialize(root);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> parseListenCount(std::string_view msgBody)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Wt::Json::Object root;
|
||||||
|
Wt::Json::parse(std::string{ msgBody }, root);
|
||||||
|
|
||||||
|
const Wt::Json::Object& payload{ static_cast<const Wt::Json::Object&>(root.get("payload")) };
|
||||||
|
return static_cast<int>(payload.get("count"));
|
||||||
|
}
|
||||||
|
catch (const Wt::WException& e)
|
||||||
|
{
|
||||||
|
LOG(ERROR, "Cannot parse listen count response: " << e.what());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen)
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
// first try to match using track MBID, and then fallback on possibly ambiguous info
|
||||||
|
if (listen.trackMBID)
|
||||||
|
{
|
||||||
|
const auto tracks{ Track::findByMBID(session, *listen.trackMBID) };
|
||||||
|
// if duplicated files, do not record it (let the user correct its database)
|
||||||
|
if (tracks.size() == 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Matched listen '" << listen << "' using track MBID");
|
||||||
|
return tracks.front()->getId();
|
||||||
|
}
|
||||||
|
else if (tracks.size() > 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Too many matches for listen '" << listen << "' using track MBID!");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen.recordingMBID)
|
||||||
|
{
|
||||||
|
const auto tracks{ Track::findByRecordingMBID(session, *listen.recordingMBID) };
|
||||||
|
// if duplicated files, do not record it (let the user correct its database)
|
||||||
|
if (tracks.size() == 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Matched listen '" << listen << "' using recording MBID");
|
||||||
|
return tracks.front()->getId();
|
||||||
|
}
|
||||||
|
else if (tracks.size() > 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Too many matches for listen '" << listen << "' using recording MBID!");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!listen.trackName.empty() && !listen.artistName.empty());
|
||||||
|
|
||||||
|
// TODO check release MBID?
|
||||||
|
Track::FindParameters params;
|
||||||
|
params.setName(listen.trackName);
|
||||||
|
params.setReleaseName(listen.releaseName);
|
||||||
|
params.setArtistName(listen.artistName);
|
||||||
|
if (listen.trackNumber)
|
||||||
|
params.setTrackNumber(*listen.trackNumber);
|
||||||
|
|
||||||
|
const auto tracks{ Track::findIds(session, params) };
|
||||||
|
// conservative behavior: in case of multiple matches: reject
|
||||||
|
if (tracks.results.size() == 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Matched listen '" << listen << "' using metadata");
|
||||||
|
return tracks.results.front();
|
||||||
|
}
|
||||||
|
else if (tracks.results.size() > 1)
|
||||||
|
{
|
||||||
|
LOG(DEBUG, "Too many matches for listen '" << listen << "' using metadata");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(DEBUG, "No match for listen '" << listen << "'");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
|
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,15 +23,15 @@
|
|||||||
#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
|
||||||
{
|
{
|
||||||
class Session;
|
class Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Scrobbling::ListenBrainz::Utils
|
namespace Scrobbling::ListenBrainz::Utils
|
||||||
{
|
{
|
||||||
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
|
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId);
|
||||||
std::string parseValidateToken(std::string_view msgBody);
|
std::string parseValidateToken(std::string_view msgBody);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
add_executable(bench-som
|
||||||
|
SomBench.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(bench-som PRIVATE
|
||||||
|
lmssom
|
||||||
|
benchmark
|
||||||
|
)
|
||||||
@@ -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();
|
||||||
@@ -26,108 +26,104 @@
|
|||||||
|
|
||||||
namespace SOM
|
namespace SOM
|
||||||
{
|
{
|
||||||
|
using Coordinate = unsigned;
|
||||||
|
|
||||||
using Coordinate = unsigned;
|
struct Position
|
||||||
using Norm = InputVector::value_type;
|
{
|
||||||
|
Coordinate x;
|
||||||
|
Coordinate y;
|
||||||
|
|
||||||
struct Position
|
bool operator<(const Position& other) const
|
||||||
{
|
{
|
||||||
Coordinate x;
|
if (x == other.x)
|
||||||
Coordinate y;
|
return y < other.y;
|
||||||
|
else
|
||||||
|
return x < other.x;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator<(const Position& other) const
|
bool operator==(const Position& other) const
|
||||||
{
|
{
|
||||||
if (x == other.x)
|
return x == other.x && y == other.y;
|
||||||
return y < other.y;
|
}
|
||||||
else
|
};
|
||||||
return x < other.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Position& other) const
|
template <typename T>
|
||||||
{
|
class Matrix
|
||||||
return x == other.x && y == other.y;
|
{
|
||||||
}
|
public:
|
||||||
};
|
Matrix() = default;
|
||||||
|
|
||||||
template <typename T>
|
Matrix(Coordinate width, Coordinate height)
|
||||||
class Matrix
|
: _width{ width }
|
||||||
{
|
, _height{ height }
|
||||||
public:
|
{
|
||||||
Matrix() = default;
|
_values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height));
|
||||||
|
}
|
||||||
|
|
||||||
Matrix(Coordinate width, Coordinate height)
|
template<typename... CtrArgs>
|
||||||
: _width {width}
|
Matrix(Coordinate width, Coordinate height, CtrArgs&& ... args)
|
||||||
, _height {height}
|
: _width{ width }
|
||||||
{
|
, _height{ height }
|
||||||
_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), T{ std::forward<CtrArgs>(args)... });
|
||||||
|
}
|
||||||
|
|
||||||
template<typename... CtArgs>
|
void clear()
|
||||||
Matrix(Coordinate width, Coordinate height, CtArgs... args)
|
{
|
||||||
: _width {width}
|
_values.clear();
|
||||||
, _height {height}
|
}
|
||||||
{
|
|
||||||
_values.resize(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height), T{args...});
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
Coordinate getHeight() const { return _height; }
|
||||||
{
|
Coordinate getWidth() const { return _width; }
|
||||||
std::vector<T> values(static_cast<std::size_t>(_width) * static_cast<std::size_t>(_height));
|
|
||||||
_values.swap(values);
|
|
||||||
}
|
|
||||||
|
|
||||||
Coordinate getHeight() const { return _height; }
|
T& get(const Position& position)
|
||||||
Coordinate getWidth() const { return _width; }
|
{
|
||||||
|
assert(position.x < _width);
|
||||||
|
assert(position.y < _height);
|
||||||
|
return _values[position.x + _width * position.y];
|
||||||
|
}
|
||||||
|
|
||||||
T& get(const Position& position)
|
const T& get(const Position& position) const
|
||||||
{
|
{
|
||||||
assert(position.x < _width);
|
assert(position.x < _width);
|
||||||
assert(position.y < _height);
|
assert(position.y < _height);
|
||||||
return _values[position.x + _width*position.y];
|
return _values[position.x + _width * position.y];
|
||||||
}
|
}
|
||||||
|
|
||||||
const T& get(const Position& position) const
|
T& operator[](const Position& position) { return get(position); }
|
||||||
{
|
const T& operator[](const Position& position) const { return get(position); }
|
||||||
assert(position.x < _width);
|
|
||||||
assert(position.y < _height);
|
|
||||||
return _values[position.x + _width*position.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
T& operator[](const Position& position) { return get(position); }
|
template <typename Func>
|
||||||
const T& operator[](const Position& position) const { return get(position); }
|
Position getPositionMinElement(Func func) const
|
||||||
|
{
|
||||||
|
assert(!_values.empty());
|
||||||
|
|
||||||
template <typename Func>
|
const auto it{ std::min_element(_values.begin(), _values.end(), std::move(func)) };
|
||||||
Position getPositionMinElement(Func func) const
|
const auto index{ static_cast<Coordinate>(std::distance(_values.begin(), it)) };
|
||||||
{
|
|
||||||
assert(!_values.empty());
|
|
||||||
|
|
||||||
auto it {std::min_element(_values.begin(), _values.end(), std::move(func))};
|
return Position{ index % _height, index / _height };
|
||||||
auto index {static_cast<Coordinate>(std::distance(_values.begin(), it))};
|
}
|
||||||
|
|
||||||
return {index % _height, index / _height};
|
private:
|
||||||
}
|
Coordinate _width{};
|
||||||
|
Coordinate _height{};
|
||||||
private:
|
std::vector<T> _values;
|
||||||
Coordinate _width {};
|
};
|
||||||
Coordinate _height {};
|
|
||||||
std::vector<T> _values;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // ns SOM
|
} // ns SOM
|
||||||
|
|
||||||
namespace std {
|
namespace std
|
||||||
|
|
||||||
template<>
|
|
||||||
class hash<SOM::Position>
|
|
||||||
{
|
{
|
||||||
public:
|
template<>
|
||||||
size_t operator()(const SOM::Position& s) const
|
class hash<SOM::Position>
|
||||||
{
|
{
|
||||||
size_t h1 = std::hash<SOM::Coordinate>()(s.x);
|
public:
|
||||||
size_t h2 = std::hash<SOM::Coordinate>()(s.y);
|
size_t operator()(const SOM::Position& s) const
|
||||||
return h1 ^ (h2 << 1);
|
{
|
||||||
}
|
size_t h1 = std::hash<SOM::Coordinate>()(s.x);
|
||||||
};
|
size_t h2 = std::hash<SOM::Coordinate>()(s.y);
|
||||||
|
return h1 ^ (h2 << 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
} // ns std
|
} // ns std
|
||||||
|
|
||||||
|
|||||||
@@ -30,78 +30,78 @@
|
|||||||
namespace SOM
|
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
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Init a network with random values
|
||||||
|
Network(Coordinate width, Coordinate height, std::size_t inputDimCount);
|
||||||
|
|
||||||
class Network
|
Coordinate getWidth() const { return _refVectors.getWidth(); }
|
||||||
{
|
Coordinate getHeight() const { return _refVectors.getHeight(); }
|
||||||
public:
|
std::size_t getInputDimCount() const { return _inputDimCount; }
|
||||||
// Init a network with random values
|
const InputVector& getDataWeights() const { return _weights; }
|
||||||
Network(Coordinate width, Coordinate height, std::size_t inputDimCount);
|
|
||||||
|
|
||||||
Coordinate getWidth() const { return _refVectors.getWidth(); }
|
// Set weight for each dimension (default is 1 for each weight)
|
||||||
Coordinate getHeight() const { return _refVectors.getHeight(); }
|
void setDataWeights(const InputVector& weights);
|
||||||
std::size_t getInputDimCount() const { return _inputDimCount; }
|
|
||||||
const InputVector& getDataWeights() const { return _weights; }
|
|
||||||
|
|
||||||
// Set weight for each dimension (default is 1 for each weight)
|
// use this to manually construct a network without training
|
||||||
void setDataWeights(const InputVector& weights);
|
void setRefVector(const Position& position, const InputVector& data);
|
||||||
|
|
||||||
// use this to manually construct a network without training
|
// <!> data must be normalized
|
||||||
void setRefVector(const Position& position, const InputVector& data);
|
struct CurrentIteration
|
||||||
|
{
|
||||||
|
std::size_t idIteration;
|
||||||
|
std::size_t iterationCount;
|
||||||
|
};
|
||||||
|
using ProgressCallback = std::function<void(const CurrentIteration&)>;
|
||||||
|
using RequestStopCallback = std::function<bool()>;
|
||||||
|
void train(const std::vector<InputVector>& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{});
|
||||||
|
|
||||||
// <!> data must be normalized
|
const InputVector& getRefVector(const Position& position) const;
|
||||||
struct CurrentIteration
|
Position getClosestRefVectorPosition(const InputVector& data) const;
|
||||||
{
|
std::optional<Position> getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
|
||||||
std::size_t idIteration;
|
|
||||||
std::size_t iterationCount;
|
|
||||||
};
|
|
||||||
using ProgressCallback = std::function<void(const CurrentIteration&)>;
|
|
||||||
using RequestStopCallback = std::function<bool()>;
|
|
||||||
void train(const std::vector<InputVector>& dataSamples, std::size_t nbIterations, ProgressCallback = ProgressCallback{}, RequestStopCallback = RequestStopCallback{});
|
|
||||||
|
|
||||||
const InputVector& getRefVector(const Position& position) const;
|
std::optional<Position> getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
|
||||||
Position getClosestRefVectorPosition(const InputVector& data) const;
|
|
||||||
std::optional<Position> getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const;
|
|
||||||
|
|
||||||
std::optional<Position> getClosestRefVectorPosition(const std::vector<Position>& refVectorsPosition, InputVector::Distance maxDistance) const;
|
InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
|
||||||
|
|
||||||
InputVector::Distance getRefVectorsDistance(const Position& position1, const Position& position2) const;
|
InputVector::Distance computeRefVectorsDistanceMean() const;
|
||||||
|
InputVector::Distance computeRefVectorsDistanceMedian() const;
|
||||||
|
|
||||||
InputVector::Distance computeRefVectorsDistanceMean() const;
|
void dump(std::ostream& os) const;
|
||||||
InputVector::Distance computeRefVectorsDistanceMedian() const;
|
|
||||||
|
|
||||||
void dump(std::ostream& os) const;
|
// For each ref vector, update formula is:
|
||||||
|
// i is the current iteration
|
||||||
|
// refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector)
|
||||||
|
|
||||||
// For each ref vector, update formula is:
|
using DistanceFunc = std::function<InputVector::Distance(const InputVector& /* a */, const InputVector& /* b */, const InputVector& /* weights */)>;
|
||||||
// i is the current iteration
|
void setDistanceFunc(DistanceFunc distanceFunc);
|
||||||
// refVector(i+1) = refVector(i) + LearningFactor(i) * NeighbourhoodFunc(i) * (MatchingRefVector - refVector)
|
DistanceFunc getDistanceFunc() { return _distanceFunc; }
|
||||||
|
|
||||||
using DistanceFunc = std::function<InputVector::Distance(const InputVector& /* a */, const InputVector& /* b */, const InputVector& /* weights */)>;
|
using LearningFactorFunc = std::function<LearningFactor(const CurrentIteration&)>;
|
||||||
void setDistanceFunc(DistanceFunc distanceFunc);
|
void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
|
||||||
DistanceFunc getDistanceFunc() { return _distanceFunc; }
|
|
||||||
|
|
||||||
using LearningFactorFunc = std::function<LearningFactor(const CurrentIteration&)>;
|
using NeighbourhoodFunc = std::function<InputVector::value_type(Norm /* norm(Position - CoordMatchingRefVector) */, const CurrentIteration&)>;
|
||||||
void setLearningFactorFunc(LearningFactorFunc learningFactorFunc);
|
void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc);
|
||||||
|
|
||||||
using NeighbourhoodFunc = std::function<InputVector::value_type(Norm /* norm(Position - CoordMatchingRefVector) */, const CurrentIteration&)>;
|
private:
|
||||||
void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc);
|
|
||||||
|
|
||||||
private:
|
void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration);
|
||||||
|
|
||||||
void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration);
|
std::size_t _inputDimCount{};
|
||||||
|
InputVector _weights; // weight for each dimension
|
||||||
|
Matrix<InputVector> _refVectors;
|
||||||
|
|
||||||
std::size_t _inputDimCount {};
|
DistanceFunc _distanceFunc;
|
||||||
InputVector _weights; // weight for each dimension
|
LearningFactorFunc _learningFactorFunc;
|
||||||
Matrix<InputVector> _refVectors;
|
NeighbourhoodFunc _neighbourhoodFunc;
|
||||||
|
};
|
||||||
DistanceFunc _distanceFunc;
|
|
||||||
LearningFactorFunc _learningFactorFunc;
|
|
||||||
NeighbourhoodFunc _neighbourhoodFunc;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace SOM
|
} // namespace SOM
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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\" },"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() };
|
||||||
|
|||||||
@@ -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())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
+100
-107
@@ -31,142 +31,135 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "ModalManager.hpp"
|
#include "ModalManager.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface
|
||||||
|
|
||||||
using namespace Database;
|
|
||||||
|
|
||||||
void
|
|
||||||
Filters::showDialog()
|
|
||||||
{
|
{
|
||||||
auto dialog {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.add-filter"))};
|
using namespace Database;
|
||||||
Wt::WWidget* dialogPtr {dialog.get()};
|
|
||||||
dialog->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
|
||||||
dialog->addFunction("id", &Wt::WTemplate::Functions::id);
|
|
||||||
|
|
||||||
Wt::WComboBox* typeCombo {dialog->bindNew<Wt::WComboBox>("type")};
|
void Filters::showDialog()
|
||||||
Wt::WComboBox* valueCombo {dialog->bindNew<Wt::WComboBox>("value")};
|
{
|
||||||
|
auto dialog{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.add-filter")) };
|
||||||
|
Wt::WWidget* dialogPtr{ dialog.get() };
|
||||||
|
dialog->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
dialog->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
Wt::WPushButton* addBtn {dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter"))};
|
Wt::WComboBox* typeCombo{ dialog->bindNew<Wt::WComboBox>("type") };
|
||||||
addBtn->clicked().connect([=]
|
Wt::WComboBox* valueCombo{ dialog->bindNew<Wt::WComboBox>("value") };
|
||||||
{
|
|
||||||
const std::string type {typeCombo->valueText().toUTF8()};
|
|
||||||
const std::string value {valueCombo->valueText().toUTF8()};
|
|
||||||
|
|
||||||
// TODO use a model to store the cluster.id() values
|
Wt::WPushButton* addBtn{ dialog->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) };
|
||||||
ClusterId clusterId {};
|
addBtn->clicked().connect([this, typeCombo, valueCombo, dialogPtr]
|
||||||
|
{
|
||||||
|
const std::string type{ typeCombo->valueText().toUTF8() };
|
||||||
|
const std::string value{ valueCombo->valueText().toUTF8() };
|
||||||
|
|
||||||
{
|
// TODO use a model to store the cluster.id() values
|
||||||
auto transaction {LmsApp->getDbSession().createReadTransaction()};
|
ClusterId clusterId{};
|
||||||
|
|
||||||
ClusterType::pointer clusterType {ClusterType::find(LmsApp->getDbSession(), type)};
|
{
|
||||||
if (!clusterType)
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
return;
|
|
||||||
|
|
||||||
Cluster::pointer cluster {clusterType->getCluster(value)};
|
ClusterType::pointer clusterType{ ClusterType::find(LmsApp->getDbSession(), type) };
|
||||||
if (!cluster)
|
if (!clusterType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
clusterId = cluster->getId();
|
Cluster::pointer cluster{ clusterType->getCluster(value) };
|
||||||
}
|
if (!cluster)
|
||||||
|
return;
|
||||||
|
|
||||||
add(clusterId);
|
clusterId = cluster->getId();
|
||||||
LmsApp->getModalManager().dispose(dialogPtr);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Wt::WPushButton* cancelBtn {dialog->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
|
add(clusterId);
|
||||||
cancelBtn->clicked().connect([=]
|
LmsApp->getModalManager().dispose(dialogPtr);
|
||||||
{
|
});
|
||||||
LmsApp->getModalManager().dispose(dialogPtr);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
Wt::WPushButton* cancelBtn{ dialog->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
|
||||||
|
cancelBtn->clicked().connect([=]
|
||||||
|
{
|
||||||
|
LmsApp->getModalManager().dispose(dialogPtr);
|
||||||
|
});
|
||||||
|
|
||||||
// Populate data
|
// Populate data
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createReadTransaction()};
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|
||||||
const auto clusterTypesIds {ClusterType::findUsed(LmsApp->getDbSession())};
|
const auto clusterTypesIds{ ClusterType::findUsed(LmsApp->getDbSession()) };
|
||||||
for (const ClusterTypeId clusterTypeId : clusterTypesIds.results)
|
for (const ClusterTypeId clusterTypeId : clusterTypesIds.results)
|
||||||
{
|
{
|
||||||
const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypeId)};
|
const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypeId) };
|
||||||
typeCombo->addItem(Wt::WString::fromUTF8(std::string{ clusterType->getName() }));
|
typeCombo->addItem(Wt::WString::fromUTF8(std::string{ clusterType->getName() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clusterTypesIds.results.empty())
|
if (!clusterTypesIds.results.empty())
|
||||||
{
|
{
|
||||||
const auto clusterType {ClusterType::find(LmsApp->getDbSession(), clusterTypesIds.results.front())};
|
const auto clusterType{ ClusterType::find(LmsApp->getDbSession(), clusterTypesIds.results.front()) };
|
||||||
|
|
||||||
for (const Cluster::pointer& cluster : clusterType->getClusters())
|
for (const Cluster::pointer& cluster : clusterType->getClusters())
|
||||||
{
|
{
|
||||||
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
|
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
|
||||||
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
|
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeCombo->changed().connect([=]
|
typeCombo->changed().connect([this, typeCombo, valueCombo]
|
||||||
{
|
{
|
||||||
const std::string name {typeCombo->valueText().toUTF8()};
|
const std::string name{ typeCombo->valueText().toUTF8() };
|
||||||
|
|
||||||
valueCombo->clear();
|
valueCombo->clear();
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createReadTransaction()};
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|
||||||
auto clusterType {ClusterType::find(LmsApp->getDbSession(), name)};
|
auto clusterType{ ClusterType::find(LmsApp->getDbSession(), name) };
|
||||||
for (const Cluster::pointer& cluster : clusterType->getClusters())
|
for (const Cluster::pointer& cluster : clusterType->getClusters())
|
||||||
{
|
{
|
||||||
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
|
if (std::find(std::cbegin(_clusterIds), std::cend(_clusterIds), cluster->getId()) == _clusterIds.end())
|
||||||
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
|
valueCombo->addItem(Wt::WString::fromUTF8(std::string{ cluster->getName() }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
Wt::WInteractWidget* filter {};
|
Wt::WInteractWidget* filter{};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto cluster {Utils::createCluster(clusterId, true)};
|
auto cluster{ Utils::createCluster(clusterId, true) };
|
||||||
if (!cluster)
|
if (!cluster)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
filter = _filters->addWidget(std::move(cluster));
|
filter = _filters->addWidget(std::move(cluster));
|
||||||
}
|
}
|
||||||
|
|
||||||
_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));
|
||||||
_sigUpdated.emit();
|
_sigUpdated.emit();
|
||||||
});
|
});
|
||||||
|
|
||||||
LmsApp->notifyMsg(Notification::Type::Info,
|
LmsApp->notifyMsg(Notification::Type::Info,
|
||||||
Wt::WString::tr("Lms.Explore.filters"),
|
Wt::WString::tr("Lms.Explore.filters"),
|
||||||
Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2});
|
Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds{ 2 });
|
||||||
|
|
||||||
_sigUpdated.emit();
|
_sigUpdated.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Filters::Filters()
|
|
||||||
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.template.filters")}
|
|
||||||
{
|
|
||||||
addFunction("tr", &Functions::tr);
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
Wt::WPushButton *addFilterBtn = bindNew<Wt::WPushButton>("add-filter", Wt::WText::tr("Lms.Explore.add-filter"));
|
|
||||||
addFilterBtn->clicked().connect(this, &Filters::showDialog);
|
|
||||||
|
|
||||||
_filters = bindNew<Wt::WContainerWidget>("clusters");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Filters::Filters()
|
||||||
|
: Wt::WTemplate{ Wt::WString::tr("Lms.Explore.template.filters") }
|
||||||
|
{
|
||||||
|
addFunction("tr", &Functions::tr);
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
Wt::WPushButton* addFilterBtn = bindNew<Wt::WPushButton>("add-filter", Wt::WText::tr("Lms.Explore.add-filter"));
|
||||||
|
addFilterBtn->clicked().connect(this, &Filters::showDialog);
|
||||||
|
|
||||||
|
_filters = bindNew<Wt::WContainerWidget>("clusters");
|
||||||
|
}
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -30,21 +30,22 @@
|
|||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
class Filters : public Wt::WTemplate
|
class Filters : public Wt::WTemplate
|
||||||
{
|
{
|
||||||
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; }
|
|
||||||
|
|
||||||
private:
|
Wt::Signal<>& updated() { return _sigUpdated; }
|
||||||
void showDialog();
|
|
||||||
|
|
||||||
Wt::WContainerWidget *_filters;
|
private:
|
||||||
Wt::Signal<> _sigUpdated;
|
void showDialog();
|
||||||
std::vector<Database::ClusterId> _clusterIds;
|
|
||||||
};
|
Wt::WContainerWidget* _filters;
|
||||||
|
Wt::Signal<> _sigUpdated;
|
||||||
|
std::vector<Database::ClusterId> _clusterIds;
|
||||||
|
};
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,13 +102,17 @@ 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;
|
||||||
clusters.push_back(*Random::pickRandom(context.genres));
|
if (!context.genres.empty())
|
||||||
clusters.push_back(*Random::pickRandom(context.moods));
|
clusters.push_back(*Random::pickRandom(context.genres));
|
||||||
|
if (!context.moods.empty())
|
||||||
|
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>();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user