Introducing new table medium, ref #699
This commit is contained in:
@@ -8,6 +8,7 @@ add_library(lmsdatabase STATIC
|
||||
impl/objects/Image.cpp
|
||||
impl/objects/Listen.cpp
|
||||
impl/objects/MediaLibrary.cpp
|
||||
impl/objects/Medium.cpp
|
||||
impl/objects/PlayListFile.cpp
|
||||
impl/objects/PlayQueue.cpp
|
||||
impl/objects/TrackArtistLink.cpp
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 98 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 99 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1423,6 +1423,111 @@ WHERE art.image_id IS NULL)");
|
||||
dropIndexes(session);
|
||||
}
|
||||
|
||||
void migrateFromV98(Session& session)
|
||||
{
|
||||
// Medium support
|
||||
dropIndexes(session);
|
||||
|
||||
// New table for mediums
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "medium" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null,
|
||||
"position" integer,
|
||||
"track_count" integer,
|
||||
"media" text not null,
|
||||
"replay_gain" real,
|
||||
"release_id" bigint,
|
||||
"preferred_artwork_id" bigint,
|
||||
constraint "fk_medium_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_medium_preferred_artwork" foreign key ("preferred_artwork_id") references "artwork" ("id") on delete set null deferrable initially deferred
|
||||
))");
|
||||
|
||||
// New primary key in track -> need to recreate the table
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "track_backup" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"scan_version" integer not null,
|
||||
"track_number" integer,
|
||||
"name" text not null,
|
||||
"duration" integer,
|
||||
"bitrate" integer not null,
|
||||
"bits_per_sample" integer not null,
|
||||
"channel_count" integer not null,
|
||||
"sample_rate" integer not null,
|
||||
"date" text,
|
||||
"original_date" text,
|
||||
"absolute_file_path" text not null,
|
||||
"file_size" bigint not null,
|
||||
"file_last_write" text,
|
||||
"file_added" text,
|
||||
"mbid" text not null,
|
||||
"recording_mbid" text not null,
|
||||
"copyright" text not null,
|
||||
"copyright_url" text not null,
|
||||
"advisory" integer not null,
|
||||
"replay_gain" real,
|
||||
"artist_display_name" text not null,
|
||||
"comment" text not null,
|
||||
"medium_id" bigint,
|
||||
"release_id" bigint,
|
||||
"media_library_id" bigint,
|
||||
"directory_id" bigint,
|
||||
"preferred_artwork_id" bigint,
|
||||
"preferred_media_artwork_id" bigint,
|
||||
constraint "fk_track_medium" foreign key ("medium_id") references "medium" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_media_library" foreign key ("media_library_id") references "media_library" ("id") on delete set null deferrable initially deferred,
|
||||
constraint "fk_track_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_preferred_artwork" foreign key ("preferred_artwork_id") references "artwork" ("id") on delete set null deferrable initially deferred,
|
||||
constraint "fk_track_preferred_media_artwork" foreign key ("preferred_media_artwork_id") references "artwork" ("id") on delete set null deferrable initially deferred
|
||||
))");
|
||||
|
||||
// Migrate data:
|
||||
// - removed disc_number, total_track, disc_subtitle, release_replay_gain
|
||||
// - renamed track_replay_gain to replay_gain
|
||||
// - with the new medium_id field set to null
|
||||
utils::executeCommand(*session.getDboSession(), R"(INSERT INTO track_backup
|
||||
SELECT
|
||||
id,
|
||||
version,
|
||||
scan_version,
|
||||
track_number,
|
||||
name,
|
||||
duration,
|
||||
bitrate,
|
||||
bits_per_sample,
|
||||
channel_count,
|
||||
sample_rate,
|
||||
date,
|
||||
original_date,
|
||||
absolute_file_path,
|
||||
file_size,
|
||||
file_last_write,
|
||||
file_added,
|
||||
mbid,
|
||||
recording_mbid,
|
||||
copyright,
|
||||
copyright_url,
|
||||
advisory,
|
||||
track_replay_gain AS replay_gain,
|
||||
artist_display_name,
|
||||
comment,
|
||||
NULL as medium_id,
|
||||
release_id,
|
||||
media_library_id,
|
||||
directory_id,
|
||||
preferred_artwork_id,
|
||||
preferred_media_artwork_id
|
||||
FROM track)");
|
||||
|
||||
utils::executeCommand(*session.getDboSession(), "DROP TABLE track");
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_backup RENAME TO track");
|
||||
|
||||
// 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 artist_info_scan_version = artist_info_scan_version + 1");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -1497,6 +1602,7 @@ WHERE art.image_id IS NULL)");
|
||||
{ 95, migrateFromV95 },
|
||||
{ 96, migrateFromV96 },
|
||||
{ 97, migrateFromV97 },
|
||||
{ 98, migrateFromV98 },
|
||||
};
|
||||
|
||||
bool migrationPerformed{};
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "database/objects/Image.hpp"
|
||||
#include "database/objects/Listen.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/PlayQueue.hpp"
|
||||
#include "database/objects/RatedArtist.hpp"
|
||||
@@ -82,6 +83,7 @@ namespace lms::db
|
||||
_session.mapClass<Label>("label");
|
||||
_session.mapClass<Listen>("listen");
|
||||
_session.mapClass<MediaLibrary>("media_library");
|
||||
_session.mapClass<Medium>("medium");
|
||||
_session.mapClass<PlayListFile>("playlist_file");
|
||||
_session.mapClass<PlayQueue>("playqueue");
|
||||
_session.mapClass<RatedArtist>("rated_artist");
|
||||
@@ -235,6 +237,8 @@ namespace lms::db
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS media_library_id_idx ON media_library(id)");
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS medium_release_position_idx ON medium(release_id, position)");
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_id_idx ON playlist_file(id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_directory_idx ON playlist_file(directory_id);");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_absolute_file_path_idx ON playlist_file(absolute_file_path)");
|
||||
@@ -262,12 +266,12 @@ namespace lms::db
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_last_write_idx ON track(file_last_write)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_idx ON track(media_library_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_release_idx ON track(media_library_id, release_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_medium_idx ON track(medium_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_file_size_idx ON track(name, file_size)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_nocase_idx ON track(name COLLATE NOCASE)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_disc_idx ON track(release_id, disc_number)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_date_idx ON track(release_id, date)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_last_write_idx ON track(release_id, file_last_write)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_added_idx ON track(release_id, file_added)");
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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/Medium.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#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/MediaLibrary.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"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::Medium);
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
Medium::Medium(ObjectPtr<Release> release)
|
||||
: _release(getDboPtr(release))
|
||||
{
|
||||
}
|
||||
|
||||
Medium::pointer Medium::create(Session& session, ObjectPtr<Release> release)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Medium>{ new Medium{ release } });
|
||||
}
|
||||
|
||||
std::size_t Medium::getCount(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM medium"));
|
||||
}
|
||||
|
||||
Medium::pointer Medium::find(Session& session, MediumId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Medium>>("SELECT m from medium m").where("m.id = ?").bind(id));
|
||||
}
|
||||
|
||||
Medium::pointer Medium::find(Session& session, ReleaseId releaseId, std::optional<std::size_t> position)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
std::optional<int> dbPosition;
|
||||
if (position)
|
||||
dbPosition = static_cast<int>(*position);
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Medium>>("SELECT m from medium m") };
|
||||
query.where("m.release_id = ?").bind(releaseId);
|
||||
if (position)
|
||||
query.where("m.position = ?").bind(*dbPosition);
|
||||
else
|
||||
query.where("m.position IS NULL");
|
||||
|
||||
return utils::fetchQuerySingleResult(query);
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
@@ -577,28 +578,6 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(createQuery<int>(session, "COUNT(DISTINCT r.id)", params));
|
||||
}
|
||||
|
||||
std::size_t Release::getDiscCount() const
|
||||
{
|
||||
assert(session());
|
||||
int res{ utils::fetchQuerySingleResult(session()->query<int>("SELECT COUNT(DISTINCT disc_number) FROM track t").where("t.release_id = ?").bind(getId())) };
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<DiscInfo> Release::getDiscs() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
using ResultType = std::tuple<int, std::string>;
|
||||
const auto query{ session()->query<ResultType>("SELECT DISTINCT disc_number, disc_subtitle FROM track t").where("t.release_id = ?").bind(getId()).orderBy("disc_number") };
|
||||
|
||||
std::vector<DiscInfo> discs;
|
||||
utils::forEachQueryResult(query, [&](ResultType&& res) {
|
||||
discs.emplace_back(DiscInfo{ static_cast<std::size_t>(std::get<int>(res)), std::move(std::get<std::string>(res)) });
|
||||
});
|
||||
|
||||
return discs;
|
||||
}
|
||||
|
||||
core::PartialDateTime Release::getDate() const
|
||||
{
|
||||
return getDate(false);
|
||||
@@ -753,6 +732,18 @@ namespace lms::db
|
||||
return _preferredArtwork.id();
|
||||
}
|
||||
|
||||
std::vector<ObjectPtr<Medium>> Release::getMediums() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
// Select the similar releases using the 5 most used clusters of the release
|
||||
auto query{ session()->query<Wt::Dbo::ptr<Medium>>("SELECT m from medium m") };
|
||||
query.where("m.release_id = ?").bind(getId());
|
||||
query.orderBy("m.position");
|
||||
|
||||
return utils::fetchQueryResults<Medium::pointer>(query);
|
||||
}
|
||||
|
||||
void Release::clearLabels()
|
||||
{
|
||||
_labels.clear();
|
||||
@@ -794,11 +785,6 @@ namespace lms::db
|
||||
return getArtists().size() > 1;
|
||||
}
|
||||
|
||||
bool Release::hasDiscSubtitle() const
|
||||
{
|
||||
return utils::fetchQuerySingleResult(session()->query<int>("SELECT EXISTS (SELECT 1 FROM track WHERE disc_subtitle IS NOT NULL AND disc_subtitle <> '' AND release_id = ?)").bind(getId()));
|
||||
}
|
||||
|
||||
std::size_t Release::getTrackCount() const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
@@ -146,6 +148,9 @@ namespace lms::db
|
||||
query.where("r.name = ?").bind(params.releaseName);
|
||||
}
|
||||
|
||||
if (params.medium.isValid())
|
||||
query.where("t.medium_id = ?").bind(params.medium);
|
||||
|
||||
if (params.trackList.isValid() || params.sortMethod == TrackSortMethod::TrackList)
|
||||
{
|
||||
query.join("tracklist t_l ON t_l_e.tracklist_id = t_l.id");
|
||||
@@ -156,8 +161,11 @@ namespace lms::db
|
||||
if (params.trackNumber)
|
||||
query.where("t.track_number = ?").bind(*params.trackNumber);
|
||||
|
||||
if (params.discNumber)
|
||||
query.where("t.disc_number = ?").bind(*params.discNumber);
|
||||
if (params.sortMethod == TrackSortMethod::DateDescAndRelease
|
||||
|| params.sortMethod == TrackSortMethod::Release)
|
||||
{
|
||||
query.join("medium m ON t.medium_id = m.id");
|
||||
}
|
||||
|
||||
if (params.filters.mediaLibrary.isValid())
|
||||
query.where("t.media_library_id = ?").bind(params.filters.mediaLibrary);
|
||||
@@ -213,16 +221,19 @@ namespace lms::db
|
||||
query.orderBy("t.absolute_file_path COLLATE NOCASE");
|
||||
break;
|
||||
case TrackSortMethod::DateDescAndRelease:
|
||||
query.orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number");
|
||||
query.orderBy("t.date DESC,t.release_id,m.position,t.track_number");
|
||||
break;
|
||||
case TrackSortMethod::Release:
|
||||
query.orderBy("t.disc_number,t.track_number");
|
||||
query.orderBy("m.position,t.track_number");
|
||||
break;
|
||||
case TrackSortMethod::TrackList:
|
||||
assert(params.trackList.isValid());
|
||||
query.orderBy("t_l_e.id");
|
||||
break;
|
||||
case TrackSortMethod::TrackNumber:
|
||||
query.orderBy("t.track_number");
|
||||
break;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -360,7 +371,7 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") };
|
||||
auto query{ session.getDboSession()->query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.mbid") };
|
||||
|
||||
return utils::execRangeQuery<TrackId>(query, range);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ namespace lms::db
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackEmbeddedImage>>("SELECT t_e_i FROM track_embedded_image t_e_i") };
|
||||
|
||||
if (params.artist.isValid()
|
||||
|| params.discNumber.has_value()
|
||||
if (params.medium.isValid()
|
||||
|| params.track.isValid()
|
||||
|| params.release.isValid()
|
||||
|| params.trackList.isValid()
|
||||
@@ -54,39 +53,27 @@ namespace lms::db
|
||||
{
|
||||
query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_embedded_image_id = t_e_i.id");
|
||||
|
||||
if (params.artist.isValid())
|
||||
{
|
||||
query.join("track_artist_link t_a_l ON t_a_l.track_id = t_e_i_l.track_id");
|
||||
query.where("t_a_l.artist_id = ?").bind(params.artist);
|
||||
|
||||
if (!params.trackArtistLinkTypes.empty())
|
||||
{
|
||||
std::string clause{ "t_a_l.type IN (" };
|
||||
for (const auto& type : params.trackArtistLinkTypes)
|
||||
{
|
||||
if (clause.back() != '(')
|
||||
clause += ",";
|
||||
clause += "?";
|
||||
query.bind(type);
|
||||
}
|
||||
clause += ")";
|
||||
query.where(clause);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.track.isValid())
|
||||
query.where("t_e_i_l.track_id = ?").bind(params.track);
|
||||
|
||||
if (params.release.isValid()
|
||||
|| params.discNumber.has_value()
|
||||
|| params.medium.isValid()
|
||||
|| params.sortMethod == TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc
|
||||
|| params.sortMethod == TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc)
|
||||
{
|
||||
assert(!(params.release.isValid() && params.medium.isValid()));
|
||||
|
||||
assert((params.sortMethod != TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc || params.release.isValid()));
|
||||
assert((params.sortMethod != TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc || params.medium.isValid()));
|
||||
|
||||
query.join("track t ON t_e_i_l.track_id = t.id");
|
||||
if (params.release.isValid())
|
||||
query.where("t.release_id = ?").bind(params.release);
|
||||
if (params.discNumber.has_value())
|
||||
query.where("t.disc_number = ?").bind(params.discNumber.value());
|
||||
if (params.medium.isValid())
|
||||
query.where("t.medium_id = ?").bind(params.medium);
|
||||
|
||||
if (params.sortMethod == TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc)
|
||||
query.join("medium m ON m.id = t.medium_id");
|
||||
}
|
||||
|
||||
if (params.trackList.isValid())
|
||||
@@ -107,7 +94,7 @@ namespace lms::db
|
||||
query.orderBy("t_e_i.size DESC");
|
||||
break;
|
||||
case TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc:
|
||||
query.orderBy("t.disc_number, t.track_number, t_e_i.size DESC");
|
||||
query.orderBy("m.position, t.track_number, t_e_i.size DESC");
|
||||
break;
|
||||
case TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc:
|
||||
query.orderBy("t.track_number, t_e_i.size DESC");
|
||||
|
||||
@@ -119,12 +119,6 @@ namespace lms::db
|
||||
int end{};
|
||||
};
|
||||
|
||||
struct DiscInfo
|
||||
{
|
||||
std::size_t position;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct FileInfo
|
||||
{
|
||||
Wt::WDateTime lastWrittenTime;
|
||||
@@ -163,6 +157,12 @@ namespace lms::db
|
||||
Name,
|
||||
};
|
||||
|
||||
enum class MediumSortMethod
|
||||
{
|
||||
None,
|
||||
PositionAsc,
|
||||
};
|
||||
|
||||
enum class ReleaseSortMethod
|
||||
{
|
||||
None,
|
||||
@@ -215,6 +215,7 @@ namespace lms::db
|
||||
DateDescAndRelease,
|
||||
Release, // order by disc/track number
|
||||
TrackList, // order by asc order in tracklist
|
||||
TrackNumber,
|
||||
};
|
||||
|
||||
enum class TrackLyricsSortMethod
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
#include <Wt/Dbo/collection.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ArtworkId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Artwork;
|
||||
class Release;
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class Medium final : public Object<Medium, MediumId>
|
||||
{
|
||||
public:
|
||||
static const std::size_t maxMediaLength{ 64 };
|
||||
|
||||
Medium() = default;
|
||||
|
||||
// find
|
||||
struct FindParameters
|
||||
{
|
||||
ReleaseId release;
|
||||
MediumSortMethod sortMethod{ MediumSortMethod::None };
|
||||
std::optional<Range> range;
|
||||
|
||||
FindParameters& setRelease(ReleaseId _release)
|
||||
{
|
||||
release = _release;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setSortMethod(MediumSortMethod _sortMethod)
|
||||
{
|
||||
sortMethod = _sortMethod;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRange(std::optional<Range> _range)
|
||||
{
|
||||
range = _range;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, MediumId id);
|
||||
static pointer find(Session& session, ReleaseId id, std::optional<std::size_t> position);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
|
||||
// getters
|
||||
std::string_view getName() const { return _name; }
|
||||
std::optional<std::size_t> getPosition() const { return _position; }
|
||||
std::optional<std::size_t> getTrackCount() const { return _trackCount; } // not necessarily the number of tracks in the medium, but the number of tracks that should be in the medium
|
||||
std::string_view getMedia() const { return _media; }
|
||||
std::optional<float> getReplayGain() const { return _replayGain; }
|
||||
ReleaseId getReleaseId() const { return _release.id(); }
|
||||
ObjectPtr<Release> getRelease() const { return _release; }
|
||||
ObjectPtr<Artwork> getPreferredArtwork() const { return _preferredArtwork; }
|
||||
ArtworkId getPreferredArtworkId() const { return _preferredArtwork.id(); }
|
||||
|
||||
// setters
|
||||
void setName(std::string_view name) { _name = name; }
|
||||
void setPosition(std::optional<std::size_t> position) { _position = position; }
|
||||
void setTrackCount(std::optional<std::size_t> trackCount) { _trackCount = trackCount; }
|
||||
void setMedia(std::string_view media) { _media = media; }
|
||||
void setReplayGain(std::optional<float> replayGain) { _replayGain = replayGain; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _position, "position");
|
||||
Wt::Dbo::field(a, _trackCount, "track_count");
|
||||
Wt::Dbo::field(a, _media, "media");
|
||||
Wt::Dbo::field(a, _replayGain, "replay_gain");
|
||||
|
||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "medium");
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
Medium(ObjectPtr<Release> release);
|
||||
static pointer create(Session& session, ObjectPtr<Release> release);
|
||||
|
||||
std::string _name;
|
||||
std::optional<int> _position; // position in the release
|
||||
std::optional<int> _trackCount;
|
||||
std::string _media; // CD, etc.
|
||||
std::optional<float> _replayGain;
|
||||
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::ptr<Artwork> _preferredArtwork;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // tracks that match this medium
|
||||
};
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(MediumId)
|
||||
@@ -51,6 +51,7 @@ namespace lms::db
|
||||
class Artwork;
|
||||
class Cluster;
|
||||
class ClusterType;
|
||||
class Medium;
|
||||
class Release;
|
||||
class Session;
|
||||
class Track;
|
||||
@@ -282,9 +283,7 @@ namespace lms::db
|
||||
std::string_view getSortName() const { return _sortName; }
|
||||
std::optional<core::UUID> getMBID() const { return core::UUID::fromString(_MBID); }
|
||||
std::optional<core::UUID> getGroupMBID() const { return core::UUID::fromString(_groupMBID); }
|
||||
std::optional<std::size_t> getTotalDisc() const { return _totalDisc; }
|
||||
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
|
||||
std::vector<DiscInfo> getDiscs() const;
|
||||
std::optional<std::size_t> getTotalDisc() const { return _totalDisc; } // the number of discs this release should have if complete
|
||||
std::chrono::milliseconds getDuration() const;
|
||||
Wt::WDateTime getAddedTime() const;
|
||||
Wt::WDateTime getLastWrittenTime() const;
|
||||
@@ -301,6 +300,7 @@ namespace lms::db
|
||||
std::string_view getComment() const { return _comment; }
|
||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||
ArtworkId getPreferredArtworkId() const;
|
||||
std::vector<ObjectPtr<Medium>> getMediums() const;
|
||||
|
||||
// Setters
|
||||
void setName(std::string_view name) { _name = name; }
|
||||
@@ -326,7 +326,6 @@ namespace lms::db
|
||||
std::vector<ObjectPtr<Artist>> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
|
||||
bool hasVariousArtists() const;
|
||||
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
|
||||
bool hasDiscSubtitle() const;
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "database/objects/DirectoryId.hpp"
|
||||
#include "database/objects/Filters.hpp"
|
||||
#include "database/objects/MediaLibraryId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
@@ -58,6 +59,7 @@ namespace lms::db
|
||||
class Directory;
|
||||
class TrackEmbeddedImageLink;
|
||||
class MediaLibrary;
|
||||
class Medium;
|
||||
class Release;
|
||||
class Session;
|
||||
class TrackArtistLink;
|
||||
@@ -82,11 +84,11 @@ namespace lms::db
|
||||
std::string artistName; // only tracks that involve this artist name
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||
bool nonRelease{}; // only tracks that do not belong to a release
|
||||
MediumId medium; // matching this medium
|
||||
ReleaseId release; // matching this release
|
||||
std::string releaseName; // matching this release name
|
||||
TrackListId trackList; // matching this trackList
|
||||
std::optional<int> trackNumber; // matching this track number
|
||||
std::optional<int> discNumber; // matching this disc number
|
||||
DirectoryId directory; // if set, tracks in this directory
|
||||
std::optional<std::size_t> fileSize; // if set, tracks that match this file size
|
||||
TrackEmbeddedImageId embeddedImageId; // if set, tracks that have this embedded image
|
||||
@@ -144,6 +146,11 @@ namespace lms::db
|
||||
nonRelease = _nonRelease;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setMedium(MediumId _medium)
|
||||
{
|
||||
medium = _medium;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRelease(ReleaseId _release)
|
||||
{
|
||||
release = _release;
|
||||
@@ -164,11 +171,6 @@ namespace lms::db
|
||||
trackNumber = _trackNumber;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setDiscNumber(int _discNumber)
|
||||
{
|
||||
discNumber = _discNumber;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setDirectory(DirectoryId _directory)
|
||||
{
|
||||
directory = _directory;
|
||||
@@ -217,9 +219,6 @@ namespace lms::db
|
||||
// Accessors
|
||||
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
||||
void setTrackNumber(std::optional<int> num) { _trackNumber = num; }
|
||||
void setDiscNumber(std::optional<int> num) { _discNumber = num; }
|
||||
void setTotalTrack(std::optional<int> totalTrack) { _totalTrack = totalTrack; }
|
||||
void setDiscSubtitle(std::string_view name) { _discSubtitle = name; }
|
||||
void setName(std::string_view name);
|
||||
void setAbsoluteFilePath(const std::filesystem::path& filePath);
|
||||
void setFileSize(std::size_t fileSize) { _fileSize = fileSize; }
|
||||
@@ -237,13 +236,13 @@ namespace lms::db
|
||||
void setCopyright(std::string_view copyright);
|
||||
void setCopyrightURL(std::string_view copyrightURL);
|
||||
void setAdvisory(Advisory advisory) { _advisory = advisory; }
|
||||
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
|
||||
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
|
||||
void setReplayGain(std::optional<float> replayGain) { _replayGain = replayGain; }
|
||||
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||
void setComment(std::string_view comment) { _comment = comment; }
|
||||
void clearArtistLinks();
|
||||
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
|
||||
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
|
||||
void setMedium(ObjectPtr<Medium> medium) { _medium = getDboPtr(medium); }
|
||||
void setClusters(const std::vector<ObjectPtr<Cluster>>& clusters);
|
||||
void clearLyrics();
|
||||
void clearEmbeddedLyrics();
|
||||
@@ -257,9 +256,6 @@ namespace lms::db
|
||||
|
||||
std::size_t getScanVersion() const { return _scanVersion; }
|
||||
std::optional<std::size_t> getTrackNumber() const { return _trackNumber; }
|
||||
std::optional<std::size_t> getTotalTrack() const { return _totalTrack; }
|
||||
std::optional<std::size_t> getDiscNumber() const { return _discNumber; }
|
||||
const std::string& getDiscSubtitle() const { return _discSubtitle; }
|
||||
std::string getName() const { return _name; }
|
||||
const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; }
|
||||
long long getFileSize() const { return _fileSize; }
|
||||
@@ -281,8 +277,7 @@ namespace lms::db
|
||||
std::optional<std::string> getCopyright() const;
|
||||
std::optional<std::string> getCopyrightURL() const;
|
||||
Advisory getAdvisory() const { return _advisory; }
|
||||
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
|
||||
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
|
||||
std::optional<float> getReplayGain() const { return _replayGain; }
|
||||
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
|
||||
std::string_view getComment() const { return _comment; }
|
||||
|
||||
@@ -292,6 +287,8 @@ namespace lms::db
|
||||
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
|
||||
ReleaseId getReleaseId() const { return _release.id(); }
|
||||
ObjectPtr<Release> getRelease() const { return _release; }
|
||||
MediumId getMediumId() const { return _medium.id(); }
|
||||
ObjectPtr<Medium> getMedium() const { return _medium; }
|
||||
std::vector<ObjectPtr<Cluster>> getClusters() const;
|
||||
std::vector<ClusterId> getClusterIds() const;
|
||||
ObjectPtr<MediaLibrary> getMediaLibrary() const;
|
||||
@@ -308,9 +305,6 @@ namespace lms::db
|
||||
{
|
||||
Wt::Dbo::field(a, _scanVersion, "scan_version");
|
||||
Wt::Dbo::field(a, _trackNumber, "track_number");
|
||||
Wt::Dbo::field(a, _discNumber, "disc_number");
|
||||
Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?)
|
||||
Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); // here in Track since Release does not have concept of "disc" (yet?)
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _duration, "duration");
|
||||
Wt::Dbo::field(a, _bitrate, "bitrate");
|
||||
@@ -328,11 +322,11 @@ namespace lms::db
|
||||
Wt::Dbo::field(a, _copyright, "copyright");
|
||||
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
||||
Wt::Dbo::field(a, _advisory, "advisory");
|
||||
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
|
||||
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
|
||||
Wt::Dbo::field(a, _replayGain, "replay_gain");
|
||||
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
|
||||
Wt::Dbo::field(a, _comment, "comment"); // TODO: move in a dedicated table
|
||||
|
||||
Wt::Dbo::belongsTo(a, _medium, "medium", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _mediaLibrary, "media_library", Wt::Dbo::OnDeleteSetNull); // don't delete track on media library removal, we want to wait for the next scan to have a chance to migrate files
|
||||
Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade);
|
||||
@@ -353,10 +347,7 @@ namespace lms::db
|
||||
static constexpr std::size_t _maxCopyrightURLLength{ 512 };
|
||||
|
||||
int _scanVersion{};
|
||||
std::optional<int> _trackNumber{};
|
||||
std::optional<int> _discNumber{};
|
||||
std::optional<int> _totalTrack{};
|
||||
std::string _discSubtitle;
|
||||
std::optional<int> _trackNumber;
|
||||
std::string _name;
|
||||
int _bitrate{}; // in bps
|
||||
int _bitsPerSample{};
|
||||
@@ -374,10 +365,10 @@ namespace lms::db
|
||||
std::string _copyright;
|
||||
std::string _copyrightURL;
|
||||
Advisory _advisory{ Advisory::UnSet };
|
||||
std::optional<float> _trackReplayGain;
|
||||
std::optional<float> _releaseReplayGain;
|
||||
std::optional<float> _replayGain;
|
||||
std::string _artistDisplayName;
|
||||
std::string _comment;
|
||||
Wt::Dbo::ptr<Medium> _medium;
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::ptr<MediaLibrary> _mediaLibrary;
|
||||
Wt::Dbo::ptr<Directory> _directory;
|
||||
|
||||
@@ -26,10 +26,9 @@
|
||||
#include <Wt/Dbo/Field.h>
|
||||
#include <Wt/Dbo/collection.h>
|
||||
|
||||
#include "core/EnumSet.hpp"
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ArtistId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
@@ -49,11 +48,9 @@ namespace lms::db
|
||||
struct FindParameters
|
||||
{
|
||||
std::optional<Range> range;
|
||||
ArtistId artist;
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes;
|
||||
TrackId track;
|
||||
ReleaseId release;
|
||||
std::optional<int> discNumber;
|
||||
MediumId medium;
|
||||
TrackListId trackList;
|
||||
std::optional<ImageType> imageType;
|
||||
TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None };
|
||||
@@ -63,13 +60,6 @@ namespace lms::db
|
||||
range = _range;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||
{
|
||||
artist = _artist;
|
||||
trackArtistLinkTypes = _trackArtistLinkTypes;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FindParameters& setTrack(TrackId _track)
|
||||
{
|
||||
track = _track;
|
||||
@@ -80,9 +70,9 @@ namespace lms::db
|
||||
release = _release;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setDiscNumber(std::optional<int> _discNumber)
|
||||
FindParameters& setMedium(MediumId _medium)
|
||||
{
|
||||
discNumber = _discNumber;
|
||||
medium = _medium;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrackList(TrackListId _trackList)
|
||||
|
||||
@@ -10,6 +10,7 @@ add_executable(test-database
|
||||
Directory.cpp
|
||||
Image.cpp
|
||||
Listen.cpp
|
||||
Medium.cpp
|
||||
Migration.cpp
|
||||
PlayListFile.cpp
|
||||
RatedArtist.cpp
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 "Common.hpp"
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedMedium = ScopedEntity<db::Medium>;
|
||||
|
||||
TEST_F(DatabaseFixture, Medium)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
EXPECT_EQ(Medium::getCount(session), 0);
|
||||
|
||||
Medium::pointer medium{ Medium::find(session, MediumId{}) };
|
||||
ASSERT_EQ(medium, Medium::pointer{});
|
||||
}
|
||||
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
EXPECT_EQ(Medium::getCount(session), 1);
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, medium.getId()) };
|
||||
ASSERT_NE(foundMedium, Medium::pointer{});
|
||||
EXPECT_EQ(foundMedium->getReleaseId(), release.getId());
|
||||
EXPECT_EQ(foundMedium->getPosition(), std::nullopt);
|
||||
EXPECT_EQ(foundMedium->getMedia(), "");
|
||||
EXPECT_EQ(foundMedium->getName(), "");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
medium.get().modify()->setName("MySubtitle");
|
||||
medium.get().modify()->setPosition(1);
|
||||
medium.get().modify()->setMedia("MyMedia");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, medium.getId()) };
|
||||
ASSERT_NE(foundMedium, Medium::pointer{});
|
||||
EXPECT_EQ(foundMedium->getName(), "MySubtitle");
|
||||
EXPECT_EQ(foundMedium->getPosition(), 1);
|
||||
EXPECT_EQ(foundMedium->getMedia(), "MyMedia");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, MediumFindByRelease_noPosition)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, release.getId(), std::nullopt) };
|
||||
ASSERT_EQ(foundMedium, Medium::pointer{});
|
||||
}
|
||||
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, release.getId(), std::nullopt) };
|
||||
ASSERT_NE(foundMedium, Medium::pointer{});
|
||||
EXPECT_EQ(foundMedium->getId(), medium.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, MediumFindByRelease_withPosition)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, release.getId(), 1) };
|
||||
ASSERT_EQ(foundMedium, Medium::pointer{});
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
medium.get().modify()->setPosition(1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Medium::pointer foundMedium{ Medium::find(session, release.getId(), 1) };
|
||||
ASSERT_NE(foundMedium, Medium::pointer{});
|
||||
EXPECT_EQ(foundMedium->getId(), medium.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Medium_findTracksByMedium)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
db::Track::FindParameters params;
|
||||
params.setMedium(medium.getId());
|
||||
params.setSortMethod(db::TrackSortMethod::TrackNumber);
|
||||
|
||||
bool visited{};
|
||||
db::Track::find(session, params, [&](const db::Track::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
ScopedTrack track1{ session };
|
||||
ScopedTrack track2{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
db::Track::FindParameters params;
|
||||
params.setMedium(medium.getId());
|
||||
params.setSortMethod(db::TrackSortMethod::TrackNumber);
|
||||
|
||||
bool visited{};
|
||||
db::Track::find(session, params, [&](const db::Track::pointer&) {
|
||||
visited = true;
|
||||
});
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track1.get().modify()->setMedium(medium.get());
|
||||
track2.get().modify()->setMedium(medium.get());
|
||||
|
||||
track1.get().modify()->setTrackNumber(3);
|
||||
track2.get().modify()->setTrackNumber(1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
db::Track::FindParameters params;
|
||||
params.setMedium(medium.getId());
|
||||
params.setSortMethod(db::TrackSortMethod::TrackNumber);
|
||||
|
||||
std::vector<db::TrackId> visitedTrackIds;
|
||||
db::Track::find(session, params, [&](const db::Track::pointer& track) {
|
||||
visitedTrackIds.push_back(track->getId());
|
||||
});
|
||||
ASSERT_EQ(visitedTrackIds.size(), 2);
|
||||
EXPECT_EQ(visitedTrackIds[0], track2.getId());
|
||||
EXPECT_EQ(visitedTrackIds[1], track1.getId());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lms::db::tests
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "database/objects/AuthToken.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Image.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/PlayQueue.hpp"
|
||||
#include "database/objects/RatedArtist.hpp"
|
||||
@@ -353,6 +354,7 @@ VALUES
|
||||
EXPECT_FALSE(TrackEmbeddedImage::find(session, TrackEmbeddedImageId{}));
|
||||
EXPECT_FALSE(TrackEmbeddedImageLink::find(session, TrackEmbeddedImageLinkId{}));
|
||||
EXPECT_FALSE(Image::find(session, ImageId{}));
|
||||
EXPECT_FALSE(Medium::find(session, MediumId{}));
|
||||
EXPECT_FALSE(Label::find(session, LabelId{}));
|
||||
EXPECT_FALSE(Listen::find(session, ListenId{}));
|
||||
EXPECT_FALSE(PlayListFile::find(session, PlayListFileId{}));
|
||||
|
||||
@@ -22,13 +22,15 @@
|
||||
#include "core/PartialDateTime.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Image.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedArtwork = ScopedEntity<db::Artwork>;
|
||||
using ScopedCountry = ScopedEntity<db::Country>;
|
||||
using ScopedImage = ScopedEntity<db::Image>;
|
||||
using ScopedLabel = ScopedEntity<db::Label>;
|
||||
using ScopedCountry = ScopedEntity<db::Country>;
|
||||
using ScopedMedium = ScopedEntity<db::Medium>;
|
||||
using ScopedReleaseType = ScopedEntity<db::ReleaseType>;
|
||||
|
||||
TEST_F(DatabaseFixture, Release)
|
||||
@@ -438,15 +440,12 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track1.get().modify()->setTotalTrack(36);
|
||||
release1.get().modify()->setTotalDisc(6);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
ASSERT_TRUE(track1->getTotalTrack());
|
||||
EXPECT_EQ(*track1->getTotalTrack(), 36);
|
||||
ASSERT_TRUE(release1->getTotalDisc());
|
||||
EXPECT_EQ(*release1->getTotalDisc(), 6);
|
||||
}
|
||||
@@ -456,15 +455,12 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track2.get().modify()->setRelease(release1.get());
|
||||
track2.get().modify()->setTotalTrack(37);
|
||||
release1.get().modify()->setTotalDisc(67);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
ASSERT_TRUE(track1->getTotalTrack());
|
||||
EXPECT_EQ(*track1->getTotalTrack(), 36);
|
||||
ASSERT_TRUE(release1->getTotalDisc());
|
||||
EXPECT_EQ(*release1->getTotalDisc(), 67);
|
||||
}
|
||||
@@ -481,18 +477,13 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track3.get().modify()->setRelease(release2.get());
|
||||
track3.get().modify()->setTotalTrack(7);
|
||||
release2.get().modify()->setTotalDisc(5);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
ASSERT_TRUE(track1->getTotalTrack());
|
||||
EXPECT_EQ(*track1->getTotalTrack(), 36);
|
||||
ASSERT_TRUE(release1->getTotalDisc());
|
||||
EXPECT_EQ(*release2->getTotalDisc(), 5);
|
||||
ASSERT_TRUE(track3->getTotalTrack());
|
||||
EXPECT_EQ(*track3->getTotalTrack(), 7);
|
||||
ASSERT_TRUE(release2->getTotalDisc());
|
||||
EXPECT_EQ(*release2->getTotalDisc(), 5);
|
||||
}
|
||||
@@ -501,7 +492,11 @@ namespace lms::db::tests
|
||||
TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
|
||||
{
|
||||
ScopedRelease release1{ session, "MyRelease1" };
|
||||
ScopedMedium medium1A{ session, release1.lockAndGet() };
|
||||
ScopedMedium medium1B{ session, release1.lockAndGet() };
|
||||
ScopedRelease release2{ session, "MyRelease2" };
|
||||
ScopedMedium medium2A{ session, release2.lockAndGet() };
|
||||
ScopedMedium medium2B{ session, release2.lockAndGet() };
|
||||
|
||||
ScopedTrack track1A{ session };
|
||||
ScopedTrack track1B{ session };
|
||||
@@ -519,17 +514,21 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
track1A.get().modify()->setRelease(release1.get());
|
||||
track1A.get().modify()->setMedium(medium1A.get());
|
||||
|
||||
track1B.get().modify()->setRelease(release1.get());
|
||||
track1B.get().modify()->setMedium(medium1B.get());
|
||||
|
||||
track2A.get().modify()->setRelease(release2.get());
|
||||
track2A.get().modify()->setMedium(medium2A.get());
|
||||
track2B.get().modify()->setRelease(release2.get());
|
||||
track2B.get().modify()->setMedium(medium2B.get());
|
||||
|
||||
track1A.get().modify()->setTrackNumber(1);
|
||||
track1B.get().modify()->setTrackNumber(2);
|
||||
|
||||
track2A.get().modify()->setDiscNumber(2);
|
||||
track2A.get().modify()->setTrackNumber(1);
|
||||
track2B.get().modify()->setTrackNumber(2);
|
||||
track2B.get().modify()->setDiscNumber(1);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -537,14 +536,16 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) };
|
||||
EXPECT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results.front(), track1A.getId());
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track1A.getId());
|
||||
EXPECT_EQ(tracks.results[1], track1B.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) };
|
||||
EXPECT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results.front(), track2B.getId());
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track2A.getId());
|
||||
EXPECT_EQ(tracks.results[1], track2B.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -815,52 +816,6 @@ namespace lms::db::tests
|
||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_getDiscCount)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedTrack track{ session };
|
||||
ScopedTrack track2{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release.get()->getDiscCount(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release.get()->getDiscCount(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setDiscNumber(5);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release.get()->getDiscCount(), 1);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
track2.get().modify()->setDiscNumber(5);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release.get()->getDiscCount(), 1);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track2.get().modify()->setDiscNumber(6);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(release.get()->getDiscCount(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_isCompilation)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
@@ -1549,4 +1504,43 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Release_mediums)
|
||||
{
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto mediums{ release->getMediums() };
|
||||
EXPECT_EQ(mediums.size(), 0);
|
||||
}
|
||||
|
||||
ScopedMedium medium2{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto mediums{ release->getMediums() };
|
||||
ASSERT_EQ(mediums.size(), 1);
|
||||
EXPECT_EQ(mediums[0]->getId(), medium2.getId());
|
||||
}
|
||||
|
||||
ScopedMedium medium1{ session, release.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
medium1.get().modify()->setPosition(1);
|
||||
medium2.get().modify()->setPosition(2);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto mediums{ release->getMediums() };
|
||||
ASSERT_EQ(mediums.size(), 2);
|
||||
EXPECT_EQ(mediums[0]->getId(), medium1.getId());
|
||||
EXPECT_EQ(mediums[1]->getId(), medium2.getId());
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <limits>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackList.hpp"
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedMedium = ScopedEntity<db::Medium>;
|
||||
using ScopedTrackEmbeddedImage = ScopedEntity<db::TrackEmbeddedImage>;
|
||||
using ScopedTrackEmbeddedImageLink = ScopedEntity<db::TrackEmbeddedImageLink>;
|
||||
|
||||
@@ -113,6 +115,7 @@ namespace lms::db::tests
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrack track{ session };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
@@ -133,6 +136,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
track.get().modify()->setMedium(medium.get());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -151,13 +155,24 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track.getId());
|
||||
params.setMedium(medium.getId());
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track.getId());
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_sorts)
|
||||
@@ -169,6 +184,8 @@ namespace lms::db::tests
|
||||
ScopedTrack track1{ session };
|
||||
ScopedTrack track2{ session };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedMedium medium1{ session, release.lockAndGet() };
|
||||
ScopedMedium medium2{ session, release.lockAndGet() };
|
||||
ScopedTrackEmbeddedImageLink link1{ session, track1.lockAndGet(), image1.lockAndGet() };
|
||||
ScopedTrackEmbeddedImageLink link2{ session, track1.lockAndGet(), image2.lockAndGet() };
|
||||
ScopedTrackEmbeddedImageLink link3{ session, track1.lockAndGet(), image3.lockAndGet() };
|
||||
@@ -176,7 +193,11 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
medium1.get().modify()->setPosition(1);
|
||||
medium2.get().modify()->setPosition(2);
|
||||
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
track1.get().modify()->setMedium(medium1.get());
|
||||
track1.get().modify()->setTrackNumber(2);
|
||||
|
||||
link1.get().modify()->setType(ImageType::FrontCover);
|
||||
@@ -187,6 +208,7 @@ namespace lms::db::tests
|
||||
image3.get().modify()->setSize(2000);
|
||||
|
||||
track2.get().modify()->setRelease(release.get());
|
||||
track2.get().modify()->setMedium(medium2.get());
|
||||
track2.get().modify()->setTrackNumber(1);
|
||||
|
||||
link4.get().modify()->setType(ImageType::Media);
|
||||
@@ -213,16 +235,15 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release.getId());
|
||||
params.setMedium(medium1.getId());
|
||||
params.setImageType(ImageType::Media);
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc);
|
||||
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); });
|
||||
ASSERT_EQ(visitedIds.size(), 3);
|
||||
EXPECT_EQ(visitedIds[0], image4.getId());
|
||||
EXPECT_EQ(visitedIds[1], image3.getId());
|
||||
EXPECT_EQ(visitedIds[2], image2.getId());
|
||||
ASSERT_EQ(visitedIds.size(), 2);
|
||||
EXPECT_EQ(visitedIds[0], image3.getId());
|
||||
EXPECT_EQ(visitedIds[1], image2.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -236,9 +257,9 @@ namespace lms::db::tests
|
||||
std::vector<TrackEmbeddedImageId> visitedIds;
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); });
|
||||
ASSERT_EQ(visitedIds.size(), 3);
|
||||
EXPECT_EQ(visitedIds[0], image4.getId());
|
||||
EXPECT_EQ(visitedIds[1], image3.getId());
|
||||
EXPECT_EQ(visitedIds[2], image2.getId());
|
||||
EXPECT_EQ(visitedIds[0], image3.getId());
|
||||
EXPECT_EQ(visitedIds[1], image2.getId());
|
||||
EXPECT_EQ(visitedIds[2], image4.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -255,59 +276,26 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_artist)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrack track{ session };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setArtist(artist.getId());
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; });
|
||||
ASSERT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist });
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; });
|
||||
ASSERT_FALSE(visited);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_discNumber)
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_medium)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrack track{ session };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedMedium medium{ session, release.lockAndGet() };
|
||||
ScopedMedium otherMedium{ session, release.lockAndGet() };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
track.get().modify()->setDiscNumber(1);
|
||||
track.get().modify()->setMedium(medium.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setDiscNumber(1);
|
||||
params.setMedium(medium.getId());
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; });
|
||||
@@ -318,7 +306,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setDiscNumber(2);
|
||||
params.setMedium(2);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer&) { visited = true; });
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -222,6 +223,26 @@ namespace lms::scanner
|
||||
return release;
|
||||
}
|
||||
|
||||
db::Medium::pointer getOrCreateMedium(db::Session& session, const metadata::Medium& medium, const db::Release::pointer& release)
|
||||
{
|
||||
db::Medium::pointer dbMedium{ db::Medium::find(session, release->getId(), medium.position) };
|
||||
if (!dbMedium)
|
||||
dbMedium = session.create<db::Medium>(release);
|
||||
|
||||
if (dbMedium->getPosition() != medium.position)
|
||||
dbMedium.modify()->setPosition(medium.position);
|
||||
if (dbMedium->getMedia() != medium.media)
|
||||
dbMedium.modify()->setMedia(medium.media);
|
||||
if (dbMedium->getName() != medium.name)
|
||||
dbMedium.modify()->setName(medium.name);
|
||||
if (dbMedium->getTrackCount() != medium.trackCount)
|
||||
dbMedium.modify()->setTrackCount(medium.trackCount);
|
||||
if (dbMedium->getReplayGain() != medium.replayGain)
|
||||
dbMedium.modify()->setReplayGain(medium.replayGain);
|
||||
|
||||
return dbMedium;
|
||||
}
|
||||
|
||||
std::vector<db::Cluster::pointer> getOrCreateClusters(db::Session& session, const metadata::Track& track)
|
||||
{
|
||||
std::vector<db::Cluster::pointer> clusters;
|
||||
@@ -387,8 +408,6 @@ namespace lms::scanner
|
||||
params.setFileSize(fileSize);
|
||||
if (parsedTrack.medium)
|
||||
{
|
||||
if (parsedTrack.medium->position)
|
||||
params.setDiscNumber(*parsedTrack.medium->position);
|
||||
if (parsedTrack.medium->release)
|
||||
params.setReleaseName(parsedTrack.medium->release->name);
|
||||
}
|
||||
@@ -691,17 +710,22 @@ namespace lms::scanner
|
||||
for (const auto& [role, performers] : _parsedTrack->performerArtists)
|
||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Performer, role, performers, allowFallback);
|
||||
|
||||
// For now, alway tie a medium to a release, and a release mst have at least one medium, even if no disc number is set
|
||||
if (_parsedTrack->medium && _parsedTrack->medium->release)
|
||||
track.modify()->setRelease(getOrCreateRelease(dbSession, *_parsedTrack->medium->release, directory));
|
||||
{
|
||||
db::Release::pointer release{ getOrCreateRelease(dbSession, *_parsedTrack->medium->release, directory) };
|
||||
assert(release);
|
||||
track.modify()->setRelease(release);
|
||||
track.modify()->setMedium(getOrCreateMedium(dbSession, *_parsedTrack->medium, release));
|
||||
}
|
||||
else
|
||||
{
|
||||
track.modify()->setRelease({});
|
||||
track.modify()->setTotalTrack(_parsedTrack->medium ? _parsedTrack->medium->trackCount : std::nullopt);
|
||||
track.modify()->setReleaseReplayGain(_parsedTrack->medium ? _parsedTrack->medium->replayGain : std::nullopt);
|
||||
track.modify()->setDiscSubtitle(_parsedTrack->medium ? _parsedTrack->medium->name : "");
|
||||
track.modify()->setMedium({});
|
||||
}
|
||||
track.modify()->setClusters(getOrCreateClusters(dbSession, *_parsedTrack));
|
||||
track.modify()->setName(title);
|
||||
track.modify()->setTrackNumber(_parsedTrack->position);
|
||||
track.modify()->setDiscNumber(_parsedTrack->medium ? _parsedTrack->medium->position : std::nullopt);
|
||||
track.modify()->setDate(_parsedTrack->date);
|
||||
track.modify()->setOriginalDate(_parsedTrack->originalDate);
|
||||
if (!track->getOriginalDate().isValid() && _parsedTrack->originalYear)
|
||||
@@ -719,7 +743,7 @@ namespace lms::scanner
|
||||
track.modify()->setCopyrightURL(_parsedTrack->copyrightURL);
|
||||
track.modify()->setAdvisory(getAdvisory(_parsedTrack->advisory));
|
||||
track.modify()->setComment(!_parsedTrack->comments.empty() ? _parsedTrack->comments.front() : ""); // only take the first one for now
|
||||
track.modify()->setTrackReplayGain(_parsedTrack->replayGain);
|
||||
track.modify()->setReplayGain(_parsedTrack->replayGain);
|
||||
track.modify()->setArtistDisplayName(_parsedTrack->artistDisplayName);
|
||||
|
||||
track.modify()->clearEmbeddedLyrics();
|
||||
|
||||
@@ -106,15 +106,13 @@ namespace lms::scanner
|
||||
return res;
|
||||
|
||||
// fallback on another track of the same disc
|
||||
const db::ReleaseId releaseId{ track->getReleaseId() };
|
||||
if (!releaseId.isValid())
|
||||
const db::MediumId mediumId{ track->getMediumId() };
|
||||
if (!mediumId.isValid())
|
||||
return res;
|
||||
|
||||
if (const auto discNumber{ track->getDiscNumber() })
|
||||
{
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(releaseId);
|
||||
params.setDiscNumber(discNumber);
|
||||
params.setMedium(track->getMediumId());
|
||||
params.setImageType(db::ImageType::Media);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
@@ -207,10 +208,12 @@ namespace lms::api::subsonic
|
||||
albumNode.addArrayValue("releaseTypes", releaseType);
|
||||
|
||||
albumNode.createEmptyArrayChild("discTitles");
|
||||
for (const DiscInfo& discInfo : release->getDiscs())
|
||||
for (const auto& medium : release->getMediums())
|
||||
{
|
||||
if (!discInfo.name.empty())
|
||||
albumNode.addArrayChild("discTitles", createDiscTitle(discInfo));
|
||||
if (medium->getName().empty())
|
||||
continue;
|
||||
|
||||
albumNode.addArrayChild("discTitles", createDiscTitle(medium));
|
||||
}
|
||||
|
||||
albumNode.createEmptyArrayChild("recordLabels");
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
|
||||
#include "responses/DiscTitle.hpp"
|
||||
|
||||
#include "database/objects/Medium.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createDiscTitle(const db::DiscInfo& discInfo)
|
||||
Response::Node createDiscTitle(const db::ObjectPtr<db::Medium>& medium)
|
||||
{
|
||||
Response::Node discTitleNode;
|
||||
|
||||
discTitleNode.setAttribute("disc", discInfo.position);
|
||||
discTitleNode.setAttribute("title", discInfo.name);
|
||||
discTitleNode.setAttribute("disc", medium->getPosition() ? *medium->getPosition() : 0);
|
||||
discTitleNode.setAttribute("title", medium->getName());
|
||||
|
||||
return discTitleNode;
|
||||
}
|
||||
|
||||
@@ -19,11 +19,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/Types.hpp"
|
||||
#include "database/Object.hpp"
|
||||
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Medium;
|
||||
}
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createDiscTitle(const db::DiscInfo& discInfo);
|
||||
Response::Node createDiscTitle(const db::ObjectPtr<db::Medium>& medium);
|
||||
}
|
||||
@@ -19,19 +19,23 @@
|
||||
|
||||
#include "responses/ReplayGain.hpp"
|
||||
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createReplayGainNode(const db::ObjectPtr<db::Track>& track)
|
||||
Response::Node createReplayGainNode(const db::Track::pointer& track, const db::Medium::pointer& medium)
|
||||
{
|
||||
Response::Node replayGainNode;
|
||||
|
||||
if (const auto trackReplayGain{ track->getTrackReplayGain() })
|
||||
if (const auto trackReplayGain{ track->getReplayGain() })
|
||||
replayGainNode.setAttribute("trackGain", *trackReplayGain);
|
||||
|
||||
if (const auto releaseReplayGain{ track->getReleaseReplayGain() })
|
||||
replayGainNode.setAttribute("albumGain", *releaseReplayGain);
|
||||
if (medium)
|
||||
{
|
||||
if (const auto releaseReplayGain{ medium->getReplayGain() })
|
||||
replayGainNode.setAttribute("albumGain", *releaseReplayGain);
|
||||
}
|
||||
|
||||
return replayGainNode;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,11 @@
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Medium;
|
||||
class Track;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
Response::Node createReplayGainNode(const db::ObjectPtr<db::Track>& track);
|
||||
Response::Node createReplayGainNode(const db::ObjectPtr<db::Track>& track, const db::ObjectPtr<db::Medium>& medium);
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -78,6 +79,8 @@ namespace lms::api::subsonic
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateSong");
|
||||
|
||||
const auto medium{ track->getMedium() };
|
||||
|
||||
Response::Node trackResponse;
|
||||
|
||||
if (!id3)
|
||||
@@ -91,8 +94,8 @@ namespace lms::api::subsonic
|
||||
trackResponse.setAttribute("title", track->getName());
|
||||
if (track->getTrackNumber())
|
||||
trackResponse.setAttribute("track", *track->getTrackNumber());
|
||||
if (track->getDiscNumber())
|
||||
trackResponse.setAttribute("discNumber", *track->getDiscNumber());
|
||||
if (medium && medium->getPosition())
|
||||
trackResponse.setAttribute("discNumber", *medium->getPosition());
|
||||
if (const auto originalYear{ track->getOriginalYear() })
|
||||
trackResponse.setAttribute("year", *originalYear);
|
||||
else if (const auto year{ track->getYear() })
|
||||
@@ -254,7 +257,7 @@ namespace lms::api::subsonic
|
||||
};
|
||||
trackResponse.setAttribute("explicitStatus", advisoryToExplicitStatus(track->getAdvisory()));
|
||||
|
||||
trackResponse.addChild("replayGain", createReplayGainNode(track));
|
||||
trackResponse.addChild("replayGain", createReplayGainNode(track, medium));
|
||||
|
||||
return trackResponse;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "core/Service.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackList.hpp"
|
||||
@@ -621,14 +622,20 @@ namespace lms::ui
|
||||
return std::nullopt;
|
||||
|
||||
case MediaPlayer::Settings::ReplayGain::Mode::Track:
|
||||
gain = track->getTrackReplayGain();
|
||||
gain = track->getReplayGain();
|
||||
break;
|
||||
|
||||
case MediaPlayer::Settings::ReplayGain::Mode::Release:
|
||||
gain = track->getReleaseReplayGain();
|
||||
if (!gain)
|
||||
gain = track->getTrackReplayGain();
|
||||
break;
|
||||
{
|
||||
const auto medium{ track->getMedium() };
|
||||
if (medium && medium->getReplayGain())
|
||||
gain = medium->getReplayGain();
|
||||
|
||||
if (!gain)
|
||||
gain = track->getReplayGain();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MediaPlayer::Settings::ReplayGain::Mode::Auto:
|
||||
{
|
||||
@@ -641,13 +648,15 @@ namespace lms::ui
|
||||
if ((prevTrack && prevTrack->getRelease() && prevTrack->getRelease() == track->getRelease())
|
||||
|| (nextTrack && nextTrack->getRelease() && nextTrack->getRelease() == track->getRelease()))
|
||||
{
|
||||
gain = track->getReleaseReplayGain();
|
||||
const auto medium{ track->getMedium() };
|
||||
if (medium && medium->getReplayGain())
|
||||
gain = medium->getReplayGain();
|
||||
if (!gain)
|
||||
gain = track->getTrackReplayGain();
|
||||
gain = track->getReplayGain();
|
||||
}
|
||||
else
|
||||
{
|
||||
gain = track->getTrackReplayGain();
|
||||
gain = track->getReplayGain();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -61,14 +61,13 @@ namespace lms::ui
|
||||
|
||||
std::vector<db::TrackId> getReleasesTracks(db::Session& session, const std::vector<db::ReleaseId>& releasesId, const Filters& filters, std::size_t maxTrackCount)
|
||||
{
|
||||
using namespace db;
|
||||
assert(maxTrackCount);
|
||||
|
||||
std::vector<TrackId> res;
|
||||
std::vector<db::TrackId> res;
|
||||
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
for (const ReleaseId releaseId : releasesId)
|
||||
for (const db::ReleaseId releaseId : releasesId)
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
params.setRelease(releaseId);
|
||||
@@ -88,34 +87,23 @@ namespace lms::ui
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<db::TrackId> getDiscTracks(db::Session& session, const std::vector<PlayQueueController::Disc>& discs, const Filters& filters, std::size_t maxTrackCount)
|
||||
std::vector<db::TrackId> getMediumTracks(db::Session& session, db::MediumId medium, const Filters& filters, std::size_t maxTrackCount)
|
||||
{
|
||||
using namespace db;
|
||||
assert(maxTrackCount);
|
||||
|
||||
std::vector<TrackId> res;
|
||||
std::vector<db::TrackId> res;
|
||||
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
for (const PlayQueueController::Disc& disc : discs)
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
params.setRelease(disc.releaseId);
|
||||
params.setSortMethod(db::TrackSortMethod::Release);
|
||||
params.setDiscNumber(disc.discNumber);
|
||||
params.setFilters(filters.getDbFilters());
|
||||
params.setRange(db::Range{ 0, maxTrackCount - res.size() });
|
||||
db::Track::FindParameters params;
|
||||
params.setMedium(medium);
|
||||
params.setSortMethod(db::TrackSortMethod::TrackNumber);
|
||||
params.setFilters(filters.getDbFilters());
|
||||
params.setRange(db::Range{ .offset = 0, .size = maxTrackCount });
|
||||
|
||||
const auto tracks{ db::Track::findIds(session, params) };
|
||||
const auto tracks{ db::Track::findIds(session, params) };
|
||||
|
||||
res.reserve(res.size() + tracks.results.size());
|
||||
res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results));
|
||||
|
||||
if (res.size() == maxTrackCount)
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
return tracks.results;
|
||||
}
|
||||
|
||||
std::vector<db::TrackId> getTrackListTracks(db::Session& session, db::TrackListId trackListId, const Filters& filters, std::size_t maxTrackCount)
|
||||
@@ -179,9 +167,9 @@ namespace lms::ui
|
||||
processCommand(command, tracks);
|
||||
}
|
||||
|
||||
void PlayQueueController::processCommand(Command command, const std::vector<Disc>& discs)
|
||||
void PlayQueueController::processCommand(Command command, db::MediumId medium)
|
||||
{
|
||||
const std::vector<db::TrackId> tracks{ getDiscTracks(LmsApp->getDbSession(), discs, _filters, _maxTrackCountToEnqueue) };
|
||||
const std::vector<db::TrackId> tracks{ getMediumTracks(LmsApp->getDbSession(), medium, _filters, _maxTrackCountToEnqueue) };
|
||||
processCommand(command, tracks);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "database/objects/ArtistId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/TrackListId.hpp"
|
||||
@@ -49,12 +50,7 @@ namespace lms::ui
|
||||
void processCommand(Command command, const std::vector<db::ReleaseId>& releases);
|
||||
void processCommand(Command command, const std::vector<db::TrackId>& tracks);
|
||||
|
||||
struct Disc
|
||||
{
|
||||
db::ReleaseId releaseId;
|
||||
size_t discNumber;
|
||||
};
|
||||
void processCommand(Command command, const std::vector<Disc>& discs);
|
||||
void processCommand(Command command, db::MediumId medium);
|
||||
void processCommand(Command command, db::TrackListId trackList);
|
||||
void playTrackInRelease(db::TrackId track);
|
||||
|
||||
|
||||
+128
-122
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "ReleaseView.hpp"
|
||||
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <map>
|
||||
|
||||
#include <Wt/WAnchor.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ScanSettings.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
@@ -233,6 +235,11 @@ namespace lms::ui
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WTemplate> createNoDiscContainer()
|
||||
{
|
||||
return std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-nodisc"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Release::Release(Filters& filters, PlayQueueController& playQueueController)
|
||||
@@ -275,9 +282,10 @@ namespace lms::ui
|
||||
|
||||
auto similarReleasesIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarReleases(*releaseId, 5) };
|
||||
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
auto& session{ LmsApp->getDbSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), *releaseId) };
|
||||
const db::Release::pointer release{ db::Release::find(session, *releaseId) };
|
||||
if (!release)
|
||||
throw ReleaseNotFoundException{};
|
||||
|
||||
@@ -310,7 +318,7 @@ namespace lms::ui
|
||||
|
||||
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
||||
{
|
||||
const auto clusterTypeIds{ ClusterType::findIds(LmsApp->getDbSession()).results };
|
||||
const auto clusterTypeIds{ ClusterType::findIds(session).results };
|
||||
const auto clusterGroups{ release->getClusterGroups(clusterTypeIds, 3) };
|
||||
|
||||
for (const auto& clusters : clusterGroups)
|
||||
@@ -377,113 +385,86 @@ namespace lms::ui
|
||||
});
|
||||
}
|
||||
|
||||
Wt::WContainerWidget* rootContainer{ bindNew<Wt::WContainerWidget>("container") };
|
||||
refreshDiscs(release);
|
||||
}
|
||||
|
||||
bool displayTrackArtists{ shouldDisplayTrackArtists(*releaseId) };
|
||||
const auto totalDisc{ release->getTotalDisc() };
|
||||
const std::size_t discCount{ release->getDiscCount() };
|
||||
const bool hasDiscSubtitle{ release->hasDiscSubtitle() };
|
||||
const bool useSubtitleContainers{ (discCount > 1) || (totalDisc && *totalDisc > 1) || hasDiscSubtitle };
|
||||
void Release::refreshArtwork(db::ArtworkId artworkId)
|
||||
{
|
||||
std::unique_ptr<Wt::WImage> artworkImage;
|
||||
if (artworkId.isValid())
|
||||
{
|
||||
artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large);
|
||||
artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK
|
||||
}
|
||||
else
|
||||
artworkImage = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release);
|
||||
|
||||
// Expect to be called in asc order
|
||||
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
||||
auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle, db::ArtworkId mediaArtworkId) -> Wt::WContainerWidget* {
|
||||
if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers))
|
||||
return it->second;
|
||||
|
||||
Template* disc{ rootContainer->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
||||
disc->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||
|
||||
if (mediaArtworkId.isValid())
|
||||
{
|
||||
auto image{ utils::createArtworkImage(mediaArtworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Small) };
|
||||
|
||||
disc->setCondition("if-has-artwork", true);
|
||||
|
||||
image->addStyleClass("Lms-cover-track rounded"); // HACK
|
||||
image->clicked().connect([=] {
|
||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(mediaArtworkId, ArtworkResource::DefaultArtworkType::Release) });
|
||||
});
|
||||
disc->bindWidget<Wt::WImage>("artwork", std::move(image));
|
||||
}
|
||||
|
||||
if (discSubtitle.empty())
|
||||
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
||||
else
|
||||
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
|
||||
|
||||
Wt::WPushButton* playBtn{ disc->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
|
||||
playBtn->clicked().connect([this, releaseId, discNumber] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::Play, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||
auto* image{ bindWidget<Wt::WImage>("artwork", std::move(artworkImage)) };
|
||||
if (artworkId.isValid())
|
||||
{
|
||||
image->clicked().connect([artworkId] {
|
||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Release) });
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||
disc->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
|
||||
->clicked()
|
||||
.connect([this, releaseId, discNumber] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::Play, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
|
||||
->clicked()
|
||||
.connect([this, releaseId, discNumber] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disc->bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
|
||||
->clicked()
|
||||
.connect([this, releaseId, discNumber] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||
->clicked()
|
||||
.connect([this, releaseId, discNumber] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { PlayQueueController::Disc{ releaseId, discNumber } });
|
||||
});
|
||||
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
||||
{
|
||||
auto container{ utils::createArtistsAnchorsForRelease(release) };
|
||||
if (container)
|
||||
{
|
||||
setCondition("if-has-release-artists", true);
|
||||
bindWidget("artists", std::move(container));
|
||||
}
|
||||
}
|
||||
|
||||
void Release::refreshDiscs(const db::Release::pointer& release)
|
||||
{
|
||||
Wt::WContainerWidget* discsContainer{ bindNew<Wt::WContainerWidget>("disc-container") };
|
||||
const std::vector<db::Medium::pointer> mediums{ release->getMediums() };
|
||||
if (mediums.empty())
|
||||
return;
|
||||
|
||||
const bool displayTrackArtists{ shouldDisplayTrackArtists(_releaseId) };
|
||||
const auto totalDisc{ release->getTotalDisc() };
|
||||
|
||||
const bool createDiscs{ mediums.size() > 1
|
||||
|| !mediums[0]->getName().empty()
|
||||
|| totalDisc > 1 };
|
||||
|
||||
for (const db::Medium::pointer& medium : mediums)
|
||||
{
|
||||
Wt::WTemplate* disc{ discsContainer->addWidget<Wt::WTemplate>(createDiscs ? createDisc(medium) : createNoDiscContainer()) };
|
||||
|
||||
Wt::WContainerWidget* tracksContainer{ disc->bindNew<Wt::WContainerWidget>("tracks") };
|
||||
trackContainers[discNumber] = tracksContainer;
|
||||
|
||||
return tracksContainer;
|
||||
};
|
||||
|
||||
Wt::WContainerWidget* noDiscTracksContainer{};
|
||||
auto getOrAddNoDiscContainer = [&] {
|
||||
if (noDiscTracksContainer)
|
||||
return noDiscTracksContainer;
|
||||
|
||||
Wt::WTemplate* disc{ rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-nodisc")) };
|
||||
noDiscTracksContainer = disc->bindNew<Wt::WContainerWidget>("tracks");
|
||||
|
||||
return noDiscTracksContainer;
|
||||
};
|
||||
createTracks(tracksContainer, medium, displayTrackArtists);
|
||||
}
|
||||
}
|
||||
|
||||
void Release::createTracks(Wt::WContainerWidget* tracksContainer, const db::Medium::pointer& medium, bool displayTrackArtists)
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
params.setRelease(_releaseId);
|
||||
params.setSortMethod(db::TrackSortMethod::Release);
|
||||
params.setMedium(medium->getId());
|
||||
params.setSortMethod(db::TrackSortMethod::TrackNumber);
|
||||
params.setFilters(_filters.getDbFilters()); // TODO: do we really want to hide all tracks when a release does not match the current label filter?
|
||||
|
||||
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) {
|
||||
const db::TrackId trackId{ track->getId() };
|
||||
const auto discNumber{ track->getDiscNumber() };
|
||||
|
||||
Wt::WContainerWidget* container{};
|
||||
if (useSubtitleContainers && discNumber)
|
||||
container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle(), track->getPreferredMediaArtworkId());
|
||||
else if (hasDiscSubtitle && !discNumber)
|
||||
container = getOrAddDiscContainer(0, track->getDiscSubtitle(), track->getPreferredMediaArtworkId());
|
||||
else
|
||||
container = getOrAddNoDiscContainer();
|
||||
|
||||
Template* entry{ container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
|
||||
Template* entry{ tracksContainer->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
|
||||
entry->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||
|
||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||
|
||||
const auto artists{ track->getArtistIds({ TrackArtistLinkType::Artist }) };
|
||||
if (displayTrackArtists && !artists.empty())
|
||||
if (displayTrackArtists)
|
||||
{
|
||||
entry->setCondition("if-has-artists", true);
|
||||
entry->bindWidget("artists", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||
const auto artists{ track->getArtistIds({ TrackArtistLinkType::Artist }) };
|
||||
if (!artists.empty())
|
||||
{
|
||||
entry->setCondition("if-has-artists", true);
|
||||
entry->bindWidget("artists", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||
}
|
||||
}
|
||||
|
||||
fillTrackArtistLinks(entry, track->getId());
|
||||
@@ -565,36 +546,6 @@ namespace lms::ui
|
||||
});
|
||||
}
|
||||
|
||||
void Release::refreshArtwork(db::ArtworkId artworkId)
|
||||
{
|
||||
std::unique_ptr<Wt::WImage> artworkImage;
|
||||
if (artworkId.isValid())
|
||||
{
|
||||
artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large);
|
||||
artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK
|
||||
}
|
||||
else
|
||||
artworkImage = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release);
|
||||
|
||||
auto* image{ bindWidget<Wt::WImage>("artwork", std::move(artworkImage)) };
|
||||
if (artworkId.isValid())
|
||||
{
|
||||
image->clicked().connect([artworkId] {
|
||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Release) });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
||||
{
|
||||
auto container{ utils::createArtistsAnchorsForRelease(release) };
|
||||
if (container)
|
||||
{
|
||||
setCondition("if-has-release-artists", true);
|
||||
bindWidget("artists", std::move(container));
|
||||
}
|
||||
}
|
||||
|
||||
void Release::refreshCopyright(const db::Release::pointer& release)
|
||||
{
|
||||
std::optional<std::string> copyright{ release->getCopyright() };
|
||||
@@ -680,4 +631,59 @@ namespace lms::ui
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WTemplate> Release::createDisc(const db::Medium::pointer& medium)
|
||||
|
||||
{
|
||||
const db::MediumId mediumId{ medium->getId() };
|
||||
|
||||
std::unique_ptr<Wt::WTemplate> disc{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
||||
disc->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||
|
||||
if (db::ArtworkId artworkId{ medium->getPreferredArtworkId() }; artworkId.isValid())
|
||||
{
|
||||
auto image{ utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Small) };
|
||||
|
||||
disc->setCondition("if-has-artwork", true);
|
||||
|
||||
image->addStyleClass("Lms-cover-track rounded"); // HACK
|
||||
image->clicked().connect([artworkId] {
|
||||
utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Release) });
|
||||
});
|
||||
disc->bindWidget<Wt::WImage>("artwork", std::move(image));
|
||||
}
|
||||
|
||||
if (medium->getName().empty())
|
||||
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(medium->getPosition() ? *medium->getPosition() : 1 /* TODO */));
|
||||
else
|
||||
disc->bindString("disc-title", Wt::WString::fromUTF8(std::string{ medium->getName() }), Wt::TextFormat::Plain);
|
||||
|
||||
Wt::WPushButton* playBtn{ disc->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
|
||||
playBtn->clicked().connect([this, mediumId] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::Play, mediumId);
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||
disc->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
|
||||
->clicked()
|
||||
.connect([this, mediumId] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::Play, mediumId);
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
|
||||
->clicked()
|
||||
.connect([this, mediumId] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, mediumId);
|
||||
});
|
||||
|
||||
disc->bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
|
||||
->clicked()
|
||||
.connect([this, mediumId] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, mediumId);
|
||||
});
|
||||
disc->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||
->clicked()
|
||||
.connect([this, mediumId] {
|
||||
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, mediumId);
|
||||
});
|
||||
|
||||
return disc;
|
||||
}
|
||||
} // namespace lms::ui
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Medium;
|
||||
class Release;
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
namespace lms::ui
|
||||
{
|
||||
@@ -44,11 +45,15 @@ namespace lms::ui
|
||||
void refreshView();
|
||||
void refreshArtwork(db::ArtworkId artworkId);
|
||||
void refreshReleaseArtists(const db::ObjectPtr<db::Release>& release);
|
||||
void refreshDiscs(const db::ObjectPtr<db::Release>& release);
|
||||
void createTracks(Wt::WContainerWidget* tracksContainer, const db::ObjectPtr<db::Medium>& medium, bool displayTrackArtists);
|
||||
void refreshCopyright(const db::ObjectPtr<db::Release>& release);
|
||||
void refreshLinks(const db::ObjectPtr<db::Release>& release);
|
||||
void refreshOtherVersions(const db::ObjectPtr<db::Release>& release);
|
||||
void refreshSimilarReleases(const std::vector<db::ReleaseId>& similarReleaseIds);
|
||||
|
||||
std::unique_ptr<Wt::WTemplate> createDisc(const db::ObjectPtr<db::Medium>& medium);
|
||||
|
||||
Filters& _filters;
|
||||
PlayQueueController& _playQueueController;
|
||||
db::ReleaseId _releaseId;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "core/ILogger.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackList.hpp"
|
||||
@@ -116,12 +117,13 @@ namespace lms::ui
|
||||
|
||||
namespace details
|
||||
{
|
||||
std::string getTrackPathName(db::Track::pointer track)
|
||||
std::string getTrackPathName(const db::Track::pointer& track)
|
||||
{
|
||||
std::ostringstream fileName;
|
||||
|
||||
if (auto discNumber{ track->getDiscNumber() })
|
||||
fileName << *discNumber << ".";
|
||||
const db::Medium::pointer medium{ track->getMedium() };
|
||||
if (medium && medium->getPosition())
|
||||
fileName << *medium->getPosition() << ".";
|
||||
if (auto trackNumber{ track->getTrackNumber() })
|
||||
fileName << std::setw(2) << std::setfill('0') << *trackNumber << " - ";
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -90,6 +91,8 @@ namespace lms
|
||||
const core::UUID releaseMBID{ core::UUID::generate() };
|
||||
const std::string releaseName{ "Release-" + std::string{ core::UUID::generate().getAsString() } };
|
||||
Release::pointer release{ context.session.create<Release>(releaseName, releaseMBID) };
|
||||
Medium::pointer medium{ context.session.create<Medium>(release) };
|
||||
medium.modify()->setTrackCount(params.trackCountPerRelease);
|
||||
|
||||
Artist::pointer artist{ generateArtist(context.session) };
|
||||
|
||||
@@ -106,13 +109,12 @@ namespace lms
|
||||
Track::pointer track{ context.session.create<Track>() };
|
||||
|
||||
track.modify()->setName("Track-" + std::string{ core::UUID::generate().getAsString() });
|
||||
track.modify()->setDiscNumber(1);
|
||||
track.modify()->setMedium(medium);
|
||||
track.modify()->setTrackNumber(i);
|
||||
track.modify()->setDuration(std::chrono::seconds{ core::random::getRandom(30, 300) });
|
||||
track.modify()->setRelease(release);
|
||||
track.modify()->setTrackMBID(core::UUID::generate());
|
||||
track.modify()->setRecordingMBID(core::UUID::generate());
|
||||
track.modify()->setTotalTrack(params.trackCountPerRelease);
|
||||
if (mediaLibrary)
|
||||
track.modify()->setMediaLibrary(mediaLibrary);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user