Introducing new table medium, ref #699

This commit is contained in:
emeric
2025-07-28 22:36:27 +02:00
parent 3c3e551676
commit 0ceaf4cfaa
36 changed files with 964 additions and 422 deletions
+107 -1
View File
@@ -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{};
+5 -1
View File
@@ -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"
+86
View File
@@ -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"
+13 -27
View File
@@ -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());
+17 -6
View File
@@ -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");