Deduplicate embedded images, ref #628

This commit is contained in:
emeric
2025-03-15 14:48:37 +01:00
parent 744abefc8b
commit 05000d8a1a
65 changed files with 1307 additions and 319 deletions
+1 -1
View File
@@ -29,9 +29,9 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -24,9 +24,9 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -27,10 +27,10 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -23,9 +23,9 @@
#include "database/Session.hpp"
#include "database/Types.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PathTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -26,9 +26,9 @@
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PathTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -22,9 +22,9 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+3 -3
View File
@@ -23,10 +23,10 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PathTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+34 -2
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 83 };
static constexpr Version LMS_DATABASE_VERSION{ 84 };
}
VersionInfo::VersionInfo()
@@ -1101,11 +1101,42 @@ FROM tracklist)");
void migrateFromV82(Session& session)
{
// new setting to display inline artist relationships in the release view
// New setting to display inline artist relationships in the release view
utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_enable_inline_artist_relationships BOOLEAN NOT NULL DEFAULT(false)");
utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_inline_artist_relationships BIGINT NOT NULL DEFAULT(68)"); // Composer + Performer
}
void migrateFromV83(Session& session)
{
// New embedded track image handling
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track DROP COLUMN has_cover");
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "track_embedded_image" (
"id" integer primary key autoincrement,
"version" integer not null,
"hash" text not null,
"size" integer not null,
"width" integer not null,
"height" integer not null,
"mime_type" text not null
))");
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "track_embedded_image_link" (
"id" integer primary key autoincrement,
"version" integer not null,
"index" integer not null,
"is_preferred" boolean not null,
"type" integer not null,
"description" text not null,
"track_id" bigint,
"track_embedded_image_id" bigint,
constraint "fk_track_embedded_image_link_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
constraint "fk_track_embedded_image_link_track_embedded_image" foreign key ("track_embedded_image_id") references "track_embedded_image" ("id") on delete cascade deferrable initially deferred
))");
// Just increment the scan version of the settings to make the next scan rescan everything
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1165,6 +1196,7 @@ FROM tracklist)");
{ 80, migrateFromV80 },
{ 81, migrateFromV81 },
{ 82, migrateFromV82 },
{ 83, migrateFromV83 },
};
bool migrationPerformed{};
+3 -3
View File
@@ -28,10 +28,10 @@
#include "database/TrackList.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PathTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -28,9 +28,9 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+4 -4
View File
@@ -31,12 +31,12 @@
#include "database/Types.hpp"
#include "database/User.hpp"
#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
#include "PartialDateTimeTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/EnumSetTraits.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+15 -3
View File
@@ -43,6 +43,8 @@
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/TrackBookmark.hpp"
#include "database/TrackEmbeddedImage.hpp"
#include "database/TrackEmbeddedImageLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/TrackList.hpp"
#include "database/TrackLyrics.hpp"
@@ -50,11 +52,12 @@
#include "database/UIState.hpp"
#include "database/User.hpp"
#include "EnumSetTraits.hpp"
#include "Migration.hpp"
#include "PartialDateTimeTraits.hpp"
#include "PathTraits.hpp"
#include "Utils.hpp"
#include "traits/EnumSetTraits.hpp"
#include "traits/ImageHashTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp"
namespace lms::db
{
@@ -122,6 +125,8 @@ namespace lms::db
_session.mapClass<Track>("track");
_session.mapClass<TrackBookmark>("track_bookmark");
_session.mapClass<TrackArtistLink>("track_artist_link");
_session.mapClass<TrackEmbeddedImage>("track_embedded_image");
_session.mapClass<TrackEmbeddedImageLink>("track_embedded_image_link");
_session.mapClass<TrackFeatures>("track_features");
_session.mapClass<TrackList>("tracklist");
_session.mapClass<TrackListEntry>("tracklist_entry");
@@ -209,6 +214,13 @@ namespace lms::db
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_media_library_idx ON directory(media_library_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_name_idx ON directory(name COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_id_idx ON track_embedded_image(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_hash_idx ON track_embedded_image(hash)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_id_idx ON track_embedded_image_link(track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_embedded_image_id_track_id_idx ON track_embedded_image_link(track_embedded_image_id, track_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_is_preferred_track_id_track_embedded_image_id_idx ON track_embedded_image_link(is_preferred, track_id, track_embedded_image_id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)");
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)");
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -25,8 +25,8 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+22 -7
View File
@@ -29,16 +29,18 @@
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/TrackEmbeddedImage.hpp"
#include "database/TrackEmbeddedImageLink.hpp"
#include "database/TrackFeatures.hpp"
#include "database/TrackLyrics.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "PartialDateTimeTraits.hpp"
#include "PathTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PartialDateTimeTraits.hpp"
#include "traits/PathTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
@@ -177,12 +179,15 @@ namespace lms::db
if (params.directory.isValid())
query.where("t.directory_id = ?").bind(params.directory);
if (params.hasEmbeddedImage.has_value())
query.where("t.has_cover = ?").bind(params.hasEmbeddedImage.value());
if (params.fileSize.has_value())
query.where("t.file_size = ?").bind(static_cast<long long>(params.fileSize.value()));
if (params.embeddedImageId.isValid())
{
query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_id = t.id");
query.where("t_e_i_l.track_embedded_image_id = ?").bind(params.embeddedImageId);
}
switch (params.sortMethod)
{
case TrackSortMethod::None:
@@ -469,6 +474,16 @@ namespace lms::db
_trackLyrics.insert(getDboPtr(lyrics));
}
void Track::clearEmbeddedImageLinks()
{
_embeddedImageLinks.clear();
}
void Track::addEmbeddedImageLink(const ObjectPtr<TrackEmbeddedImageLink>& image)
{
_embeddedImageLinks.insert(getDboPtr(image));
}
std::optional<int> Track::getYear() const
{
return _date.getYear();
+1 -1
View File
@@ -23,8 +23,8 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+1 -1
View File
@@ -23,8 +23,8 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
@@ -0,0 +1,124 @@
/*
* 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/Directory.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackEmbeddedImage.hpp"
#include "database/Types.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/ImageHashTypeTraits.hpp"
namespace lms::db
{
namespace
{
Wt::Dbo::Query<Wt::Dbo::ptr<TrackEmbeddedImage>> createQuery(Session& session, const TrackEmbeddedImage::FindParameters& params)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackEmbeddedImage>>("SELECT t_e_i FROM track_embedded_image t_e_i") };
if (params.isPreferred
|| params.track.isValid()
|| params.release.isValid()
|| params.trackList.isValid()
|| params.sortMethod == TrackEmbeddedImageSortMethod::FrontCoverAndSize)
{
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.isPreferred)
query.where("t_e_i_l.is_preferred = ?").bind(params.isPreferred.value());
if (params.track.isValid())
query.where("t_e_i_l.track_id = ?").bind(params.track);
if (params.release.isValid())
{
query.join("track t ON t_e_i_l.track_id = t.id");
query.where("t.release_id = ?").bind(params.release);
}
if (params.trackList.isValid())
{
query.join("tracklist_entry t_l_e ON t_l_e.track_id = t_e_i_l.track_id");
query.where("t_l_e.tracklist_id = ?").bind(params.trackList);
}
}
switch (params.sortMethod)
{
case TrackEmbeddedImageSortMethod::None:
break;
case TrackEmbeddedImageSortMethod::FrontCoverAndSize:
query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size").bind(ImageType::FrontCover);
break;
}
return query;
}
} // namespace
TrackEmbeddedImage::pointer TrackEmbeddedImage::create(Session& session)
{
return session.getDboSession()->add(std::unique_ptr<TrackEmbeddedImage>{ new TrackEmbeddedImage{} });
}
std::size_t TrackEmbeddedImage::getCount(Session& session)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_embedded_image"));
}
TrackEmbeddedImage::pointer TrackEmbeddedImage::find(Session& session, TrackEmbeddedImageId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImage>().where("id = ?").bind(id));
}
void TrackEmbeddedImage::find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func)
{
session.checkReadTransaction();
auto query{ createQuery(session, params) };
utils::forEachQueryRangeResult(query, params.range, func);
}
TrackEmbeddedImage::pointer TrackEmbeddedImage::find(Session& session, std::size_t size, ImageHashType hash)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->find<TrackEmbeddedImage>().where("size = ?").bind(static_cast<int>(size)).where("hash = ?").bind(hash) };
return utils::fetchQuerySingleResult(query);
}
RangeResults<TrackEmbeddedImageId> TrackEmbeddedImage::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackEmbeddedImageId>("SELECT t_e_i.id FROM track_embedded_image t_e_i LEFT JOIN track_embedded_image_link t_e_i_l ON t_e_i.id = t_e_i_l.track_embedded_image_id WHERE t_e_i_l.track_embedded_image_id IS NULL") };
return utils::execRangeQuery<TrackEmbeddedImageId>(query, range);
}
} // namespace lms::db
@@ -0,0 +1,66 @@
/*
* 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/TrackEmbeddedImageLink.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackEmbeddedImage.hpp"
#include "database/Types.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
TrackEmbeddedImageLink::TrackEmbeddedImageLink(ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image)
: _track{ getDboPtr(track) }
, _image{ getDboPtr(image) }
{
}
TrackEmbeddedImageLink::pointer TrackEmbeddedImageLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image)
{
return session.getDboSession()->add(std::unique_ptr<TrackEmbeddedImageLink>{ new TrackEmbeddedImageLink{ track, image } });
}
std::size_t TrackEmbeddedImageLink::getCount(Session& session)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_embedded_image_link"));
}
TrackEmbeddedImageLink::pointer TrackEmbeddedImageLink::find(Session& session, TrackEmbeddedImageLinkId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImageLink>().where("id = ?").bind(id));
}
ObjectPtr<Track> TrackEmbeddedImageLink::getTrack() const
{
return _track;
}
ObjectPtr<TrackEmbeddedImage> TrackEmbeddedImageLink::getImage() const
{
return _image;
}
} // namespace lms::db
+1 -1
View File
@@ -27,8 +27,8 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -26,10 +26,10 @@
#include "database/Track.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+2 -2
View File
@@ -28,9 +28,9 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "IdTypeTraits.hpp"
#include "PathTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/PathTraits.hpp"
namespace lms::db
{
+2 -3
View File
@@ -19,13 +19,12 @@
#include "database/UIState.hpp"
#include "core/ILogger.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
+3 -3
View File
@@ -27,10 +27,10 @@
#include "database/Track.hpp"
#include "database/UIState.hpp"
#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
#include "traits/EnumSetTraits.hpp"
#include "traits/IdTypeTraits.hpp"
#include "traits/StringViewTraits.hpp"
namespace lms::db
{
@@ -0,0 +1,62 @@
/*
* 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 <Wt/Dbo/StdSqlTraits.h>
#include "core/String.hpp"
#include "database/Types.hpp"
namespace Wt::Dbo
{
template<>
struct sql_value_traits<lms::db::ImageHashType>
{
static const bool specialized = true;
// Uses an underlying string to encode this big value
static std::string type(SqlConnection* conn, int size)
{
return sql_value_traits<std::string, void>::type(conn, size);
}
static void bind(const lms::db::ImageHashType& v, SqlStatement* statement, int column, int size)
{
std::string valueAsStr{ std::to_string(v.value()) };
sql_value_traits<std::string>::bind(valueAsStr, statement, column, size);
}
static bool read(lms::db::ImageHashType& v, SqlStatement* statement, int column, int size)
{
std::string valueAsStr;
if (sql_value_traits<std::string>::read(valueAsStr, statement, column, size))
{
if (const auto parsedValue{ lms::core::stringUtils::readAs<lms::db::ImageHashType::underlying_type>(valueAsStr) })
{
v = lms::db::ImageHashType{ *parsedValue };
return true;
}
}
v = lms::db::ImageHashType{};
return false;
}
};
} // namespace Wt::Dbo