Added support for work/movements, fixes #834
This commit is contained in:
@@ -27,8 +27,10 @@ add_library(lmsdatabase STATIC
|
||||
impl/objects/StarredArtist.cpp
|
||||
impl/objects/StarredRelease.cpp
|
||||
impl/objects/StarredTrack.cpp
|
||||
impl/objects/Movement.cpp
|
||||
impl/objects/Track.cpp
|
||||
impl/objects/TrackArtistLink.cpp
|
||||
impl/objects/Work.cpp
|
||||
impl/objects/TrackBookmark.cpp
|
||||
impl/objects/TrackEmbeddedImage.cpp
|
||||
impl/objects/TrackEmbeddedImageLink.cpp
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 108 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 109 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1922,6 +1922,40 @@ WHERE ct.name = 'GROUPING')");
|
||||
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM cluster_type WHERE name IN ('GENRE', 'MOOD', 'LANGUAGE', 'GROUPING'))");
|
||||
}
|
||||
|
||||
void migrateFromV108(Session& session)
|
||||
{
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "work" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null,
|
||||
"mbid" blob
|
||||
))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "track_work" (
|
||||
"work_id" bigint,
|
||||
"track_id" bigint,
|
||||
primary key ("work_id", "track_id"),
|
||||
constraint "fk_track_work_key1" foreign key ("work_id") references "work" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_work_key2" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "track_work_work" on "track_work" ("work_id"))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "track_work_track" on "track_work" ("track_id"))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "track_movement" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null,
|
||||
"number" integer,
|
||||
"count" integer,
|
||||
"track_id" bigint,
|
||||
constraint "fk_track_movement_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
|
||||
// Just increment the scan version of the settings to make the next scan rescan all audio files
|
||||
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET audio_scan_version = audio_scan_version + 1");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -2006,6 +2040,7 @@ WHERE ct.name = 'GROUPING')");
|
||||
{ 105, migrateFromV105 },
|
||||
{ 106, migrateFromV106 },
|
||||
{ 107, migrateFromV107 },
|
||||
{ 108, migrateFromV108 },
|
||||
};
|
||||
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Database", "Migration");
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/PlayQueue.hpp"
|
||||
#include "database/objects/Podcast.hpp"
|
||||
@@ -61,6 +62,7 @@
|
||||
#include "database/objects/TrackMusicNNEmbeddings.hpp"
|
||||
#include "database/objects/UIState.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Db.hpp"
|
||||
#include "Migration.hpp"
|
||||
@@ -111,6 +113,7 @@ namespace lms::db
|
||||
_session.mapClass<StarredRelease>("starred_release");
|
||||
_session.mapClass<StarredTrack>("starred_track");
|
||||
_session.mapClass<Track>("track");
|
||||
_session.mapClass<Movement>("track_movement");
|
||||
_session.mapClass<TrackBookmark>("track_bookmark");
|
||||
_session.mapClass<TrackArtistLink>("track_artist_link");
|
||||
_session.mapClass<TrackEmbeddedImage>("track_embedded_image");
|
||||
@@ -120,6 +123,7 @@ namespace lms::db
|
||||
_session.mapClass<TrackListEntry>("tracklist_entry");
|
||||
_session.mapClass<TrackLyrics>("track_lyrics");
|
||||
_session.mapClass<UIState>("ui_state");
|
||||
_session.mapClass<Work>("work");
|
||||
_session.mapClass<User>("user");
|
||||
_session.mapClass<VersionInfo>("version_info");
|
||||
}
|
||||
@@ -322,6 +326,10 @@ namespace lms::db
|
||||
"CREATE INDEX IF NOT EXISTS track_lyrics_directory_idx ON track_lyrics(directory_id)",
|
||||
"CREATE INDEX IF NOT EXISTS track_lyrics_track_idx ON track_lyrics(track_id)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS track_movement_track_idx ON track_movement(track_id)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS work_mbid_idx ON work(mbid)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)",
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/Movement.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::Movement)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
Movement::Movement(std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track)
|
||||
: _name{ name.substr(0, maxNameLength) }
|
||||
, _number{ number }
|
||||
, _count{ count }
|
||||
, _track{ getDboPtr(track) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Movement name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
Movement::pointer Movement::create(Session& session, std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Movement>{ new Movement{ name, number, count, track } });
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -40,6 +41,7 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
@@ -36,12 +36,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
@@ -673,6 +675,40 @@ namespace lms::db
|
||||
_moods.insert(getDboPtr(mood));
|
||||
}
|
||||
|
||||
void Track::setWorks(std::span<const ObjectPtr<Work>> works)
|
||||
{
|
||||
_works.clear();
|
||||
for (const ObjectPtr<Work>& work : works)
|
||||
_works.insert(getDboPtr(work));
|
||||
}
|
||||
|
||||
std::vector<Work::pointer> Track::getWorks() const
|
||||
{
|
||||
// deterministic order, callers rely on the first entry
|
||||
return utils::fetchQueryResults<Work::pointer>(_works.find().orderBy("id"));
|
||||
}
|
||||
|
||||
bool Track::hasWork() const
|
||||
{
|
||||
return !_works.empty();
|
||||
}
|
||||
|
||||
void Track::clearMovements()
|
||||
{
|
||||
_movements.clear();
|
||||
}
|
||||
|
||||
std::vector<Movement::pointer> Track::getMovements() const
|
||||
{
|
||||
// deterministic order, callers rely on the first entry
|
||||
return utils::fetchQueryResults<Movement::pointer>(_movements.find().orderBy("id"));
|
||||
}
|
||||
|
||||
bool Track::hasMovement() const
|
||||
{
|
||||
return !_movements.empty();
|
||||
}
|
||||
|
||||
void Track::clearLyrics()
|
||||
{
|
||||
_trackLyrics.clear();
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/UUIDTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::Work)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
Work::Work(std::string_view name, const std::optional<core::UUID>& mbid)
|
||||
: _mbid{ mbid }
|
||||
{
|
||||
setName(name);
|
||||
}
|
||||
|
||||
void Work::setName(std::string_view name)
|
||||
{
|
||||
_name = name.substr(0, maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Work name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
Work::pointer Work::create(Session& session, std::string_view name, const std::optional<core::UUID>& mbid)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Work>{ new Work{ name, mbid } });
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, WorkId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Work>().where("id = ?").bind(id));
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Work>().where("mbid = ?").bind(mbid));
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, ReleaseId releaseId, std::string_view name)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
|
||||
auto query{
|
||||
session.getDboSession()->query<Wt::Dbo::ptr<Work>>("SELECT w FROM work w")
|
||||
// clang-format off
|
||||
.join("track_work t_w ON t_w.work_id = w.id")
|
||||
.join("track t ON t.id = t_w.track_id")
|
||||
.where("t.release_id = ?").bind(releaseId)
|
||||
.where("w.name = ?").bind(std::string{ name })
|
||||
.where("w.mbid IS NULL")
|
||||
.groupBy("w.id")
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
return utils::fetchQuerySingleResult(query);
|
||||
}
|
||||
|
||||
std::vector<WorkId> Work::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<WorkId>("SELECT w.id FROM work w WHERE NOT EXISTS (SELECT 1 FROM track_work t_w WHERE t_w.work_id = w.id)") };
|
||||
return utils::execRangeQuery<WorkId>(query, range);
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/objects/MovementId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class Movement final : public Object<Movement, MovementId>
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t maxNameLength{ 512 };
|
||||
|
||||
Movement() = default;
|
||||
|
||||
static pointer create(Session& session, std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
std::optional<std::size_t> getNumber() const { return _number; }
|
||||
std::optional<std::size_t> getCount() const { return _count; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _number, "number");
|
||||
Wt::Dbo::field(a, _count, "count");
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
Movement(std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track);
|
||||
|
||||
std::string _name;
|
||||
std::optional<int> _number;
|
||||
std::optional<int> _count;
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
};
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(MovementId)
|
||||
@@ -51,12 +51,14 @@
|
||||
#include "database/objects/MediaLibraryId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/MoodId.hpp"
|
||||
#include "database/objects/MovementId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/TrackListId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/UserId.hpp"
|
||||
#include "database/objects/WorkId.hpp"
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -69,16 +71,18 @@ namespace lms::db
|
||||
class Genre;
|
||||
class Grouping;
|
||||
class Language;
|
||||
class Medium;
|
||||
class Movement;
|
||||
class Mood;
|
||||
class TrackEmbeddedImageLink;
|
||||
class MediaLibrary;
|
||||
class Medium;
|
||||
class Release;
|
||||
class Session;
|
||||
class TrackArtistLink;
|
||||
class TrackLyrics;
|
||||
class TrackStats;
|
||||
class User;
|
||||
class Work;
|
||||
|
||||
class Track final : public Object<Track, TrackId>
|
||||
{
|
||||
@@ -279,6 +283,8 @@ namespace lms::db
|
||||
void setGroupings(std::span<const ObjectPtr<Grouping>> groupings);
|
||||
void setLanguages(std::span<const ObjectPtr<Language>> languages);
|
||||
void setMoods(std::span<const ObjectPtr<Mood>> moods);
|
||||
void setWorks(std::span<const ObjectPtr<Work>> works);
|
||||
void clearMovements();
|
||||
void clearLyrics();
|
||||
void clearEmbeddedLyrics();
|
||||
void addLyrics(const ObjectPtr<TrackLyrics>& lyrics);
|
||||
@@ -310,7 +316,7 @@ namespace lms::db
|
||||
|
||||
// Metadata
|
||||
std::optional<std::size_t> getTrackNumber() const { return _trackNumber; }
|
||||
std::string getName() const { return _name; }
|
||||
std::string_view getName() const { return _name; }
|
||||
const core::PartialDateTime& getDate() const { return _date; }
|
||||
std::optional<int> getYear() const;
|
||||
const core::PartialDateTime& getOriginalDate() const { return _originalDate; }
|
||||
@@ -347,6 +353,10 @@ namespace lms::db
|
||||
std::vector<LanguageId> getLanguageIds() const;
|
||||
std::vector<ObjectPtr<Mood>> getMoods() const;
|
||||
std::vector<MoodId> getMoodIds() const;
|
||||
std::vector<ObjectPtr<Work>> getWorks() const;
|
||||
bool hasWork() const;
|
||||
std::vector<ObjectPtr<Movement>> getMovements() const;
|
||||
bool hasMovement() const;
|
||||
ObjectPtr<MediaLibrary> getMediaLibrary() const;
|
||||
ObjectPtr<Directory> getDirectory() const;
|
||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||
@@ -393,12 +403,14 @@ namespace lms::db
|
||||
Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::belongsTo(a, _preferredMediaArtwork, "preferred_media_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::hasMany(a, _movements, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _groupings, Wt::Dbo::ManyToMany, "track_grouping", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _languages, Wt::Dbo::ManyToMany, "track_language", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _moods, Wt::Dbo::ManyToMany, "track_mood", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _works, Wt::Dbo::ManyToMany, "track_work", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _trackLyrics, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _embeddedImageLinks, Wt::Dbo::ManyToOne, "track");
|
||||
}
|
||||
@@ -441,7 +453,6 @@ namespace lms::db
|
||||
std::string _artistDisplayName;
|
||||
std::string _comment;
|
||||
Advisory _advisory{ Advisory::UnSet };
|
||||
|
||||
Wt::Dbo::ptr<Medium> _medium;
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::ptr<MediaLibrary> _mediaLibrary;
|
||||
@@ -454,6 +465,8 @@ namespace lms::db
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Grouping>> _groupings;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Language>> _languages;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Mood>> _moods;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Work>> _works;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Movement>> _movements;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackLyrics>> _trackLyrics;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackEmbeddedImageLink>> _embeddedImageLinks;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
#include <Wt/Dbo/collection.h>
|
||||
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/WorkId.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class Work final : public Object<Work, WorkId>
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t maxNameLength{ 512 };
|
||||
|
||||
Work() = default;
|
||||
|
||||
static pointer find(Session& session, WorkId id);
|
||||
// Global lookup: MusicBrainz Work Id is a strong, unambiguous identity shared across the whole library
|
||||
static pointer find(Session& session, const core::UUID& mbid);
|
||||
// Name-only lookup, scoped to works already linked to a track of the given release: work titles are
|
||||
// often generic (e.g. "Symphony No. 5") and collide across unrelated works, so without an mbid we only
|
||||
// ever match within the same release instead of matching globally by name
|
||||
static pointer find(Session& session, ReleaseId releaseId, std::string_view name);
|
||||
static std::vector<WorkId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
void setName(std::string_view name);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
std::optional<core::UUID> getMBID() const { return _mbid; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _mbid, "mbid");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_work", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
Work(std::string_view name, const std::optional<core::UUID>& mbid);
|
||||
static pointer create(Session& session, std::string_view name, const std::optional<core::UUID>& mbid);
|
||||
|
||||
std::string _name;
|
||||
std::optional<core::UUID> _mbid;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
||||
};
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(WorkId)
|
||||
@@ -16,6 +16,7 @@ add_executable(test-database
|
||||
Medium.cpp
|
||||
Migration.cpp
|
||||
Mood.cpp
|
||||
Movement.cpp
|
||||
PlayListFile.cpp
|
||||
Podcast.cpp
|
||||
RatedArtist.cpp
|
||||
@@ -29,6 +30,7 @@ add_executable(test-database
|
||||
StarredTrack.cpp
|
||||
Track.cpp
|
||||
TrackArtistLink.cpp
|
||||
Work.cpp
|
||||
TrackBookmark.cpp
|
||||
TrackEmbeddedImage.cpp
|
||||
TrackList.cpp
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
TEST_F(DatabaseFixture, Movement_create)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "Allegro con brio", std::size_t{ 1 }, std::size_t{ 4 }, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto movements{ track->getMovements() };
|
||||
ASSERT_EQ(movements.size(), 1);
|
||||
ASSERT_FALSE(movements[0]->getName().empty());
|
||||
EXPECT_EQ(movements[0]->getName(), "Allegro con brio");
|
||||
ASSERT_TRUE(movements[0]->getNumber());
|
||||
EXPECT_EQ(*movements[0]->getNumber(), std::size_t{ 1 });
|
||||
ASSERT_TRUE(movements[0]->getCount());
|
||||
EXPECT_EQ(*movements[0]->getCount(), std::size_t{ 4 });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_createWithNullFields)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "", std::nullopt, std::nullopt, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto movements{ track->getMovements() };
|
||||
ASSERT_EQ(movements.size(), 1);
|
||||
EXPECT_TRUE(movements[0]->getName().empty());
|
||||
EXPECT_FALSE(movements[0]->getNumber());
|
||||
EXPECT_FALSE(movements[0]->getCount());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_multipleMovementsOnTrack)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "Allegro con brio", std::size_t{ 1 }, std::size_t{ 4 }, track.get());
|
||||
Movement::create(session, "Andante con moto", std::size_t{ 2 }, std::size_t{ 4 }, track.get());
|
||||
Movement::create(session, "Scherzo. Allegro", std::size_t{ 3 }, std::size_t{ 4 }, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto movements{ track->getMovements() };
|
||||
EXPECT_EQ(movements.size(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_cascadeDeleteWithTrack)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "Allegro", std::size_t{ 1 }, std::nullopt, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(track->getMovements().size(), 1);
|
||||
}
|
||||
|
||||
// track goes out of scope here — movements are cascade deleted
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_clearMovements)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "Allegro", std::size_t{ 1 }, std::size_t{ 2 }, track.get());
|
||||
Movement::create(session, "Andante", std::size_t{ 2 }, std::size_t{ 2 }, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(track->getMovements().size(), 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->clearMovements();
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(track->getMovements().size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_replacingMovements)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "OldMovement", std::size_t{ 1 }, std::nullopt, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->clearMovements();
|
||||
Movement::create(session, "NewMovement", std::size_t{ 1 }, std::nullopt, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto movements{ track->getMovements() };
|
||||
ASSERT_EQ(movements.size(), 1);
|
||||
ASSERT_FALSE(movements[0]->getName().empty());
|
||||
EXPECT_EQ(movements[0]->getName(), "NewMovement");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Movement_getNumberReturnsCorrectSizeT)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Movement::create(session, "", std::size_t{ 42 }, std::size_t{ 100 }, track.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto movements{ track->getMovements() };
|
||||
ASSERT_EQ(movements.size(), 1);
|
||||
ASSERT_TRUE(movements[0]->getNumber());
|
||||
EXPECT_EQ(*movements[0]->getNumber(), std::size_t{ 42 });
|
||||
ASSERT_TRUE(movements[0]->getCount());
|
||||
EXPECT_EQ(*movements[0]->getCount(), std::size_t{ 100 });
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedWork = ScopedEntity<db::Work>;
|
||||
|
||||
TEST_F(DatabaseFixture, Work_create)
|
||||
{
|
||||
ScopedWork work{ session, "Symphony No. 5", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Work::pointer found{ Work::find(session, work.getId()) };
|
||||
ASSERT_TRUE(found);
|
||||
EXPECT_EQ(found->getName(), "Symphony No. 5");
|
||||
EXPECT_FALSE(found->getMBID());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_createWithMBID)
|
||||
{
|
||||
const auto mbid{ core::UUID::fromString("8f3471b3-7e93-4de1-a9c7-3b843c21b84e") };
|
||||
ASSERT_TRUE(mbid);
|
||||
|
||||
ScopedWork work{ session, "Symphony No. 9", mbid };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Work::pointer found{ Work::find(session, work.getId()) };
|
||||
ASSERT_TRUE(found);
|
||||
EXPECT_EQ(found->getName(), "Symphony No. 9");
|
||||
ASSERT_TRUE(found->getMBID());
|
||||
EXPECT_EQ(found->getMBID(), mbid);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_findByMBID)
|
||||
{
|
||||
const auto mbid{ core::UUID::fromString("8f3471b3-7e93-4de1-a9c7-3b843c21b84e") };
|
||||
ASSERT_TRUE(mbid);
|
||||
|
||||
ScopedWork workWithMbid{ session, "Piano Sonata", mbid };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const Work::pointer byMbid{ Work::find(session, *mbid) };
|
||||
ASSERT_TRUE(byMbid);
|
||||
EXPECT_EQ(byMbid->getId(), workWithMbid.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_findByNameScopedToRelease)
|
||||
{
|
||||
// Work titles are often generic (e.g. "Piano Sonata"): two unrelated releases can each have
|
||||
// their own work with the exact same name, and lookup must not merge them
|
||||
ScopedRelease release1{ session, "Release1" };
|
||||
ScopedRelease release2{ session, "Release2" };
|
||||
ScopedTrack track1{ session };
|
||||
ScopedTrack track2{ session };
|
||||
ScopedWork work1{ session, "Piano Sonata", std::optional<core::UUID>{} };
|
||||
ScopedWork work2{ session, "Piano Sonata", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setRelease(release1.get());
|
||||
track1.get().modify()->setWorks(std::array{ work1.get() });
|
||||
track2.get().modify()->setRelease(release2.get());
|
||||
track2.get().modify()->setWorks(std::array{ work2.get() });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
// Same name, but each release resolves to its own work
|
||||
const Work::pointer foundInRelease1{ Work::find(session, release1.getId(), "Piano Sonata") };
|
||||
ASSERT_TRUE(foundInRelease1);
|
||||
EXPECT_EQ(foundInRelease1->getId(), work1.getId());
|
||||
|
||||
const Work::pointer foundInRelease2{ Work::find(session, release2.getId(), "Piano Sonata") };
|
||||
ASSERT_TRUE(foundInRelease2);
|
||||
EXPECT_EQ(foundInRelease2->getId(), work2.getId());
|
||||
|
||||
// Unknown name in a known release returns null
|
||||
EXPECT_FALSE(Work::find(session, release1.getId(), "Unknown Work"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_orphan)
|
||||
{
|
||||
ScopedWork work{ session, "Unlinked Work", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto orphans{ Work::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), work.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_singleTrack)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
ScopedWork work1{ session, "Requiem", std::optional<core::UUID>{} };
|
||||
ScopedWork work2{ session, "Missa Solemnis", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Work::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(track->getWorks().size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setWorks(std::array{ work1.get() });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto orphans{ Work::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), work2.getId());
|
||||
|
||||
const auto trackWorks{ track->getWorks() };
|
||||
ASSERT_EQ(trackWorks.size(), 1);
|
||||
EXPECT_EQ(trackWorks.front()->getId(), work1.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_multipleWorksOnTrack)
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
ScopedWork work1{ session, "Symphony No. 5", std::optional<core::UUID>{} };
|
||||
ScopedWork work2{ session, "Symphony No. 6", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setWorks(std::array{ work1.get(), work2.get() });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Work::findOrphanIds(session).size(), 0);
|
||||
|
||||
const auto trackWorks{ track->getWorks() };
|
||||
EXPECT_EQ(trackWorks.size(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_multipleTracksOnWork)
|
||||
{
|
||||
ScopedTrack track1{ session };
|
||||
ScopedTrack track2{ session };
|
||||
ScopedTrack track3{ session };
|
||||
ScopedWork work{ session, "The Four Seasons", std::optional<core::UUID>{} };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setWorks(std::array{ work.get() });
|
||||
track2.get().modify()->setWorks(std::array{ work.get() });
|
||||
track3.get().modify()->setWorks(std::array{ work.get() });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Work::findOrphanIds(session).size(), 0);
|
||||
|
||||
EXPECT_EQ(track1->getWorks().size(), 1);
|
||||
EXPECT_EQ(track2->getWorks().size(), 1);
|
||||
EXPECT_EQ(track3->getWorks().size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Work_nameTruncation)
|
||||
{
|
||||
const std::string longName(Work::maxNameLength + 100, 'x');
|
||||
const std::string expectedName(Work::maxNameLength, 'x');
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
Work::pointer work{ session.create<Work>(longName, std::optional<core::UUID>{}) };
|
||||
ASSERT_TRUE(work);
|
||||
EXPECT_EQ(work->getName().size(), Work::maxNameLength);
|
||||
EXPECT_EQ(work->getName(), expectedName);
|
||||
work.remove();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lms::db::tests
|
||||
Reference in New Issue
Block a user